openapi: 3.1.0
info:
  title: o1 DEX Aggregator API
  version: 0.1.0
  description: |
    Quote + execution API for the o1 DEX aggregator on Base.

    Flow:
      1. POST /quote   → returns quoteId + routePlan
      2. POST /submit  → returns the calldata payload to broadcast
      3. Caller signs and broadcasts O1Router.swapExactIn().

    /execute is a one-shot convenience that combines /quote and /submit.

    All non-`/health` endpoints require the `x-api-key` header. Rate limit
    defaults to 120 requests per minute per client IP.
servers:
  - url: https://quiet-bloodhound-531.convex.site
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: meta
    description: Liveness and version probes.
  - name: quote
    description: Pricing endpoints that produce a routePlan.
  - name: submit
    description: Calldata builders that turn a quote into a broadcast-ready transaction.
  - name: aggregator-comparison
    description: >-
      Optional endpoints that fan out to upstream aggregators (0x / 1inch /
      KyberSwap / Relay / Odos / CoW). Mounted only when at least one upstream
      adapter is configured server-side.
paths:
  /health:
    get:
      tags:
        - meta
      operationId: getHealth
      summary: Liveness probe
      security: []
      responses:
        '200':
          description: Service is up
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                properties:
                  ok:
                    type: boolean
                    const: true
  /quote:
    post:
      tags:
        - quote
      operationId: postQuote
      summary: Price a swap and return a routePlan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
      responses:
        '200':
          description: Quote produced
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuoteResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
  /submit:
    post:
      tags:
        - submit
      operationId: postSubmit
      summary: Build a broadcast-ready transaction for a previously issued quote
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitRequest'
      responses:
        '200':
          description: Transaction payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: quoteId is unknown or expired (TTL elapsed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
  /execute:
    post:
      tags:
        - submit
      operationId: postExecute
      summary: One-shot quote + submit
      description: >-
        Equivalent to calling /quote then /submit. The taker address is
        required.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecuteRequest'
      responses:
        '200':
          description: Quote + transaction payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExecuteResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
  /quote/compare:
    post:
      tags:
        - aggregator-comparison
      operationId: postQuoteCompare
      summary: Local quote alongside upstream aggregator quotes
      description: |
        Only mounted when at least one upstream aggregator adapter is
        configured (0x / 1inch / KyberSwap / Relay / Odos / CoW).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
      responses:
        '200':
          description: Local + upstream quotes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompareResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
  /quote/split:
    post:
      tags:
        - aggregator-comparison
      operationId: postQuoteSplit
      summary: Split a single trade across upstream aggregators
      description: |
        Only mounted when at least one upstream aggregator adapter is
        configured. Returns the optimal allocation across upstream sources
        compared to the single best source.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
      responses:
        '200':
          description: Split allocation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SplitResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
  responses:
    BadRequest:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or incorrect x-api-key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Sliding-window rate limit exceeded (default 120/min/IP)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
    Address:
      type: string
      pattern: ^0x[a-fA-F0-9]{40}$
      description: |
        20-byte hex address. The sentinel
        `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` (or the zero
        address) signals the chain's native asset (ETH on Base) and is
        recognized in `tokenIn`/`tokenOut`.
    Hex:
      type: string
      pattern: ^0x[a-fA-F0-9]*$
      description: Hex-encoded bytes.
    AmountString:
      type: string
      pattern: ^[0-9]+$
      description: Non-negative integer encoded as a decimal string (wei).
    DexId:
      type: string
      enum:
        - UNIV2
        - UNIV3
        - UNIV4
        - AERODROME_V2LIKE
        - AERODROME_CL
        - PANCAKE_V2
        - PANCAKE_V3
        - PANCAKE_INFINITY_CL
        - HYDREX
        - QUICKSWAP_V4
        - ALIEN_BASE_V3
        - CURVE
        - PROPSWAP
        - TESSERA
        - ELFOMOFI
        - LUNARBASE
        - FELTIR
        - DODO_V2
        - WOOFI
        - GYROSCOPE_ECLP
        - MAVERICK_V2
    PermitPayload:
      type: object
      required:
        - value
        - deadline
        - v
        - r
        - s
      properties:
        value:
          $ref: '#/components/schemas/AmountString'
        deadline:
          type: integer
          format: int64
          description: Unix timestamp (seconds) at which the permit expires.
        v:
          type: integer
          minimum: 0
          maximum: 255
        r:
          $ref: '#/components/schemas/Hex'
        s:
          $ref: '#/components/schemas/Hex'
    QuoteRequest:
      type: object
      required:
        - chainId
        - tokenIn
        - tokenOut
        - amountIn
        - slippageBps
      properties:
        chainId:
          type: integer
          minimum: 1
          description: EVM chain id. 8453 for Base (current Phase-1 target).
        tokenIn:
          $ref: '#/components/schemas/Address'
        tokenOut:
          $ref: '#/components/schemas/Address'
        amountIn:
          $ref: '#/components/schemas/AmountString'
        slippageBps:
          type: integer
          minimum: 0
          maximum: 10000
        maxHops:
          type: integer
          minimum: 1
        splitEnabled:
          type: boolean
        enforcePoolDisjoint:
          type: boolean
        allowedDexes:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/DexId'
        feeBps:
          type: integer
          minimum: 0
          maximum: 10000
          description: Integrator fee in bps, charged out of the output amount.
        taker:
          $ref: '#/components/schemas/Address'
        timeBudgetMs:
          type: integer
          minimum: 50
          maximum: 10000
          description: |
            Per-request override of the optimizer's wall-clock budget.
            Omit to inherit the engine default.
      additionalProperties: false
    QuoteResponse:
      type: object
      required:
        - quoteId
        - routePlan
        - expiresAt
      properties:
        quoteId:
          type: string
          description: |
            SHA-256 (or HMAC-SHA256 if the server has `O1_QUOTE_SIGNING_KEY`
            set) of the quote payload. Echo this on /submit.
        routePlan:
          $ref: '#/components/schemas/RoutePlan'
        expiresAt:
          type: integer
          format: int64
          description: Unix epoch milliseconds at which the cached quote drops.
    SubmitRequest:
      type: object
      required:
        - quoteId
        - user
      properties:
        quoteId:
          type: string
        user:
          $ref: '#/components/schemas/Address'
        useNativeIn:
          type: boolean
          description: |
            Override `routePlan.nativeIn`. When true the router wraps
            `msg.value` to WETH before dispatching legs.
        unwrapNativeOut:
          type: boolean
          description: >-
            Override `routePlan.nativeOut`. When true the router unwraps WETH to
            ETH for the recipient.
        permit:
          $ref: '#/components/schemas/PermitPayload'
        gasPriceWei:
          $ref: '#/components/schemas/AmountString'
        maxFeePerGasWei:
          $ref: '#/components/schemas/AmountString'
        maxPriorityFeePerGasWei:
          $ref: '#/components/schemas/AmountString'
      additionalProperties: false
    SubmitResponse:
      type: object
      required:
        - quoteId
        - chainId
        - to
        - data
        - value
      properties:
        quoteId:
          type: string
        chainId:
          type: integer
        to:
          $ref: '#/components/schemas/Address'
        data:
          $ref: '#/components/schemas/Hex'
        value:
          allOf:
            - $ref: '#/components/schemas/AmountString'
          description: msg.value in wei (non-zero only for native-in swaps).
    ExecuteRequest:
      type: object
      required:
        - chainId
        - tokenIn
        - tokenOut
        - amountIn
        - slippageBps
        - user
      properties:
        chainId:
          type: integer
          minimum: 1
        tokenIn:
          $ref: '#/components/schemas/Address'
        tokenOut:
          $ref: '#/components/schemas/Address'
        amountIn:
          $ref: '#/components/schemas/AmountString'
        slippageBps:
          type: integer
          minimum: 0
          maximum: 10000
        user:
          $ref: '#/components/schemas/Address'
        maxHops:
          type: integer
          minimum: 1
        splitEnabled:
          type: boolean
        enforcePoolDisjoint:
          type: boolean
        allowedDexes:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/DexId'
        feeBps:
          type: integer
          minimum: 0
          maximum: 10000
        useNativeIn:
          type: boolean
        unwrapNativeOut:
          type: boolean
        permit:
          $ref: '#/components/schemas/PermitPayload'
      additionalProperties: false
    ExecuteResponse:
      type: object
      required:
        - quoteId
        - chainId
        - to
        - data
        - value
        - routePlan
        - expiresAt
      properties:
        quoteId:
          type: string
        chainId:
          type: integer
        to:
          $ref: '#/components/schemas/Address'
        data:
          $ref: '#/components/schemas/Hex'
        value:
          $ref: '#/components/schemas/AmountString'
        routePlan:
          $ref: '#/components/schemas/RoutePlan'
        expiresAt:
          type: integer
          format: int64
    GasEstimate:
      type: object
      required:
        - gasUnits
      properties:
        gasUnits:
          type: integer
          minimum: 0
        gasCostWei:
          $ref: '#/components/schemas/AmountString'
    FeeBreakdown:
      type: object
      required:
        - protocolFeeAmount
        - integratorFeeAmount
      properties:
        protocolFeeAmount:
          $ref: '#/components/schemas/AmountString'
        integratorFeeAmount:
          $ref: '#/components/schemas/AmountString'
    RouteCandidateMetadata:
      type: object
      required:
        - path
        - dexes
        - projectedOut
        - allocatedIn
        - score
      properties:
        path:
          type: array
          items:
            $ref: '#/components/schemas/Address'
        dexes:
          type: array
          items:
            $ref: '#/components/schemas/DexId'
        projectedOut:
          $ref: '#/components/schemas/AmountString'
        allocatedIn:
          $ref: '#/components/schemas/AmountString'
        score:
          type: number
    RouteMetadata:
      type: object
      required:
        - connectorsConsidered
        - candidatePaths
        - selectedPaths
        - candidates
      properties:
        connectorsConsidered:
          type: array
          items:
            $ref: '#/components/schemas/Address'
        candidatePaths:
          type: integer
          minimum: 0
        selectedPaths:
          type: integer
          minimum: 0
        candidates:
          type: array
          items:
            $ref: '#/components/schemas/RouteCandidateMetadata'
      additionalProperties: true
    LegData:
      description: |
        Tagged union discriminated by `kind`. Direct-pool legs (`v2_direct`,
        `v3_direct`, `pancake_v3_direct`, `algebra_direct`) carry a single
        pool address and are dispatched by the corresponding O1Router
        adapter. Multi-hop venue legs (`univ4`, `aerodrome_v2`, `curve`,
        `dodo_v2`, `woofi`, `prop_amm`) carry venue-specific routing data.
        Legacy `univ2` / `univ3` shapes are still emitted for older quotes
        in transit.
      oneOf:
        - $ref: '#/components/schemas/V2DirectLegData'
        - $ref: '#/components/schemas/V3DirectLegData'
        - $ref: '#/components/schemas/PancakeV3DirectLegData'
        - $ref: '#/components/schemas/AlgebraDirectLegData'
        - $ref: '#/components/schemas/HydrexLegData'
        - $ref: '#/components/schemas/UniV4LegData'
        - $ref: '#/components/schemas/CurveLegData'
        - $ref: '#/components/schemas/PropAmmLegData'
        - $ref: '#/components/schemas/DodoV2LegData'
        - $ref: '#/components/schemas/WooFiLegData'
        - $ref: '#/components/schemas/UniV2LegData'
        - $ref: '#/components/schemas/UniV3LegData'
        - $ref: '#/components/schemas/AerodromeLegData'
      discriminator:
        propertyName: kind
        mapping:
          v2_direct: '#/components/schemas/V2DirectLegData'
          v3_direct: '#/components/schemas/V3DirectLegData'
          pancake_v3_direct: '#/components/schemas/PancakeV3DirectLegData'
          algebra_direct: '#/components/schemas/AlgebraDirectLegData'
          hydrex: '#/components/schemas/HydrexLegData'
          univ4: '#/components/schemas/UniV4LegData'
          curve: '#/components/schemas/CurveLegData'
          prop_amm: '#/components/schemas/PropAmmLegData'
          dodo_v2: '#/components/schemas/DodoV2LegData'
          woofi: '#/components/schemas/WooFiLegData'
          univ2: '#/components/schemas/UniV2LegData'
          univ3: '#/components/schemas/UniV3LegData'
          aerodrome_v2: '#/components/schemas/AerodromeLegData'
    V2DirectLegData:
      type: object
      required:
        - kind
        - pool
        - feeBps
      properties:
        kind:
          type: string
          const: v2_direct
        pool:
          $ref: '#/components/schemas/Address'
        feeBps:
          type: integer
          minimum: 0
          maximum: 10000
    V3DirectLegData:
      type: object
      required:
        - kind
        - pool
      properties:
        kind:
          type: string
          const: v3_direct
        pool:
          $ref: '#/components/schemas/Address'
    PancakeV3DirectLegData:
      type: object
      required:
        - kind
        - pool
      properties:
        kind:
          type: string
          const: pancake_v3_direct
        pool:
          $ref: '#/components/schemas/Address'
    AlgebraDirectLegData:
      type: object
      required:
        - kind
        - pool
      properties:
        kind:
          type: string
          const: algebra_direct
        pool:
          $ref: '#/components/schemas/Address'
    HydrexLegData:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          const: hydrex
        deadline:
          type: integer
          format: int64
    UniV4LegData:
      type: object
      required:
        - kind
        - commands
        - inputs
      properties:
        kind:
          type: string
          const: univ4
        commands:
          $ref: '#/components/schemas/Hex'
        inputs:
          type: array
          items:
            $ref: '#/components/schemas/Hex'
        deadline:
          type: integer
          format: int64
    CurveLegData:
      type: object
      required:
        - kind
        - pool
        - i
        - j
      properties:
        kind:
          type: string
          const: curve
        pool:
          $ref: '#/components/schemas/Address'
        i:
          type: integer
          minimum: 0
        j:
          type: integer
          minimum: 0
    PropAmmLegData:
      type: object
      required:
        - kind
        - router
        - protocol
      properties:
        kind:
          type: string
          const: prop_amm
        router:
          $ref: '#/components/schemas/Address'
        protocol:
          type: string
          enum:
            - propswap
            - tessera
            - elfomofi
            - lunarbase
            - feltir
    DodoV2LegData:
      type: object
      required:
        - kind
        - pool
        - baseToken
      properties:
        kind:
          type: string
          const: dodo_v2
        pool:
          $ref: '#/components/schemas/Address'
        baseToken:
          $ref: '#/components/schemas/Address'
    WooFiLegData:
      type: object
      required:
        - kind
        - router
      properties:
        kind:
          type: string
          const: woofi
        router:
          $ref: '#/components/schemas/Address'
    UniV2LegData:
      type: object
      required:
        - kind
        - path
      properties:
        kind:
          type: string
          const: univ2
        path:
          type: array
          minItems: 2
          items:
            $ref: '#/components/schemas/Address'
        deadline:
          type: integer
          format: int64
    UniV3LegData:
      type: object
      required:
        - kind
        - path
      properties:
        kind:
          type: string
          const: univ3
        path:
          $ref: '#/components/schemas/Hex'
        deadline:
          type: integer
          format: int64
    AerodromeRoute:
      type: object
      required:
        - from
        - to
        - stable
        - factory
      properties:
        from:
          $ref: '#/components/schemas/Address'
        to:
          $ref: '#/components/schemas/Address'
        stable:
          type: boolean
        factory:
          $ref: '#/components/schemas/Address'
    AerodromeLegData:
      type: object
      required:
        - kind
        - routes
      properties:
        kind:
          type: string
          const: aerodrome_v2
        routes:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/AerodromeRoute'
        deadline:
          type: integer
          format: int64
    RouteLeg:
      type: object
      required:
        - dex
        - tokenIn
        - tokenOut
        - amountIn
        - minOut
        - data
      properties:
        dex:
          $ref: '#/components/schemas/DexId'
        tokenIn:
          $ref: '#/components/schemas/Address'
        tokenOut:
          $ref: '#/components/schemas/Address'
        amountIn:
          $ref: '#/components/schemas/AmountString'
        minOut:
          $ref: '#/components/schemas/AmountString'
        poolId:
          type: string
        data:
          $ref: '#/components/schemas/LegData'
    SplitRoute:
      type: object
      required:
        - amountIn
        - legs
      properties:
        amountIn:
          $ref: '#/components/schemas/AmountString'
        legs:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/RouteLeg'
    RoutePlan:
      type: object
      required:
        - chainId
        - tokenIn
        - tokenOut
        - amountIn
        - expectedAmountOut
        - minAmountOut
        - slippageBps
        - routes
        - blockNumber
      properties:
        chainId:
          type: integer
        tokenIn:
          $ref: '#/components/schemas/Address'
        tokenOut:
          $ref: '#/components/schemas/Address'
        amountIn:
          $ref: '#/components/schemas/AmountString'
        expectedAmountOut:
          $ref: '#/components/schemas/AmountString'
        minAmountOut:
          $ref: '#/components/schemas/AmountString'
        feeBps:
          type: integer
          minimum: 0
          maximum: 10000
        slippageBps:
          type: integer
          minimum: 0
          maximum: 10000
        routes:
          type: array
          items:
            $ref: '#/components/schemas/SplitRoute'
        blockNumber:
          type: integer
          minimum: 0
        gasEstimate:
          $ref: '#/components/schemas/GasEstimate'
        feeBreakdown:
          $ref: '#/components/schemas/FeeBreakdown'
        metadata:
          $ref: '#/components/schemas/RouteMetadata'
        nativeIn:
          type: boolean
          description: |
            Set when the original request used the ETH sentinel for tokenIn.
            The submit handler must send `msg.value = amountIn` and set
            `useNativeIn: true` so the router wraps to WETH before
            dispatching legs.
        nativeOut:
          type: boolean
          description: Mirror of nativeIn for tokenOut = ETH (unwrap WETH).
    UpstreamQuote:
      type: object
      required:
        - sourceId
        - outputAmount
        - netOutput
        - gasEstimate
      properties:
        sourceId:
          type: string
        outputAmount:
          $ref: '#/components/schemas/AmountString'
        netOutput:
          $ref: '#/components/schemas/AmountString'
        gasEstimate:
          $ref: '#/components/schemas/AmountString'
    CompareResponse:
      type: object
      required:
        - local
        - upstream
        - upstreamError
      properties:
        local:
          oneOf:
            - $ref: '#/components/schemas/QuoteResponse'
            - type: 'null'
        upstream:
          oneOf:
            - type: object
              required:
                - best
                - alternates
                - errors
              properties:
                best:
                  $ref: '#/components/schemas/UpstreamQuote'
                alternates:
                  type: array
                  items:
                    $ref: '#/components/schemas/UpstreamQuote'
                errors:
                  type: array
                  items:
                    type: string
            - type: 'null'
        upstreamError:
          oneOf:
            - type: string
            - type: 'null'
    SplitAllocation:
      type: object
      required:
        - sourceId
        - amountIn
        - expectedOutput
        - hasExecutableQuote
      properties:
        sourceId:
          type: string
        amountIn:
          $ref: '#/components/schemas/AmountString'
        expectedOutput:
          $ref: '#/components/schemas/AmountString'
        hasExecutableQuote:
          type: boolean
    SplitResponse:
      type: object
      required:
        - strategy
        - totalInput
        - totalOutput
        - singleBestOutput
        - singleBestSource
        - improvement
        - allocations
        - errors
        - fetchedAt
      properties:
        strategy:
          type: string
          const: split-aggregator
        totalInput:
          $ref: '#/components/schemas/AmountString'
        totalOutput:
          $ref: '#/components/schemas/AmountString'
        singleBestOutput:
          $ref: '#/components/schemas/AmountString'
        singleBestSource:
          type: string
        improvement:
          type: object
          required:
            - absolute
            - bps
          properties:
            absolute:
              $ref: '#/components/schemas/AmountString'
            bps:
              type: number
        allocations:
          type: array
          items:
            $ref: '#/components/schemas/SplitAllocation'
        errors:
          type: array
          items:
            type: string
        fetchedAt:
          type: integer
          format: int64
