openapi: 3.1.0
info:
  title: Qiguard API
  version: 1.0.0
  summary: Swap and gasless swap API for BSC, Ethereum, Base and Polygon through the Qiguard contracts.
  description: |
    Qiguard API prices and builds token swaps that settle on chain through the Qiguard router and settlement contracts of each chain.

    Rules that apply to every endpoint:
    - HTTPS only. A plain HTTP request gets `400 HTTPS_REQUIRED` (never a redirect) and the key it carried is revoked.
    - Keyed endpoints need the headers `qg-api-key` and `qg-version: v1`.
    - Amounts are decimal strings in base units. Addresses are 0x-prefixed hex; responses use EIP-55 checksums.
    - The native coin of every chain is `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`.
    - Every response carries the header `qg-zid` and a body field `zid` (also inside `data` of an error).
    - Clients must ignore unknown response fields; new fields can appear in v1 at any time.
    - Pairs: one side must be an anchor of the chain (USDT, USDC, the native coin or its wrapped token); the other side
      an anchor or a token listed by `GET /tokens`.
    - The on-chain fee is `fees.qiguardFee` (0.50% unless the taker wallet has a lower on-chain rate). `buyAmount` and
      `minBuyAmount` are net amounts, after the fee.
    - There are no testnets. Test on a local fork or with small amounts on Base.
    - Gateway errors on any path: 404 NOT_FOUND, 405 METHOD_NOT_ALLOWED, 413 PAYLOAD_TOO_LARGE, 414 URI_TOO_LONG.
    - Rate limit: 5 requests per second per app on the free tier (50 per 10-second window), across all keyed endpoints.
  contact:
    url: https://docs.qiguard.com
servers:
  - url: https://api.qiguard.com
tags:
  - name: Swap
    description: Allowance-holder swaps. The taker approves the settlement contract for the exact amount and sends the transaction.
  - name: Gasless
    description: The taker signs a Permit2 witness message; the Qiguard relayer sends the transaction and pays the gas.
  - name: Meta
    description: Chains, sources, tokens and live status. No key needed.

paths:
  /swap/allowance-holder/price:
    get:
      operationId: swap_allowanceholderGetPrice
      tags: [Swap]
      summary: Indicative price for an allowance-holder swap
      x-qiguard-auth: key
      x-qiguard-endpoint: swap.price
      security: [{ qgApiKey: [] }]
      parameters:
        - $ref: '#/components/parameters/QgVersion'
        - $ref: '#/components/parameters/ChainId'
        - $ref: '#/components/parameters/SellToken'
        - $ref: '#/components/parameters/BuyToken'
        - $ref: '#/components/parameters/SellAmount'
        - $ref: '#/components/parameters/BuyAmount'
        - $ref: '#/components/parameters/TakerOptional'
        - $ref: '#/components/parameters/Recipient'
        - $ref: '#/components/parameters/SlippageBps'
        - $ref: '#/components/parameters/SlippagePpm'
        - $ref: '#/components/parameters/DeadlineSeconds'
        - $ref: '#/components/parameters/ExcludedSources'
        - $ref: '#/components/parameters/GasPrice'
        - $ref: '#/components/parameters/SwapFeeRecipient'
        - $ref: '#/components/parameters/SwapFeeBps'
        - $ref: '#/components/parameters/SwapFeePpm'
        - $ref: '#/components/parameters/SwapFeeToken'
        - $ref: '#/components/parameters/TradeSurplusRecipient'
        - $ref: '#/components/parameters/TradeSurplusMaxBps'
        - $ref: '#/components/parameters/TradeSurplusMaxPpm'
        - $ref: '#/components/parameters/TxOrigin'
        - $ref: '#/components/parameters/SellEntireBalance'
        - $ref: '#/components/parameters/WrapUnwrapMode'
      responses:
        '200':
          description: A price, or `liquidityAvailable false` when no route exists.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
            RateLimit-Policy: { $ref: '#/components/headers/RateLimitPolicy' }
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/SwapPrice'
                  - $ref: '#/components/schemas/NoLiquidity'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /swap/allowance-holder/quote:
    get:
      operationId: swap_allowanceholderGetQuote
      tags: [Swap]
      summary: Firm quote with the transaction to send
      description: |
        Same parameters as the price endpoint; `taker` is required. `transaction.data` calls `marketSwap`,
        `marketSwapNativeToToken` or `marketSwapTokenToNative` on the settlement contract, followed by the 18-byte
        Qiguard tag (`qiguard.tag`). Approve `issues.allowance.spender` for the exact `sellAmount`, wait for the
        approval receipt, then quote again: `transaction` expires at `expiresAt`.
      x-qiguard-auth: key
      x-qiguard-endpoint: swap.quote
      security: [{ qgApiKey: [] }]
      parameters:
        - $ref: '#/components/parameters/QgVersion'
        - $ref: '#/components/parameters/ChainId'
        - $ref: '#/components/parameters/SellToken'
        - $ref: '#/components/parameters/BuyToken'
        - $ref: '#/components/parameters/SellAmount'
        - $ref: '#/components/parameters/BuyAmount'
        - $ref: '#/components/parameters/TakerRequired'
        - $ref: '#/components/parameters/Recipient'
        - $ref: '#/components/parameters/SlippageBps'
        - $ref: '#/components/parameters/SlippagePpm'
        - $ref: '#/components/parameters/DeadlineSeconds'
        - $ref: '#/components/parameters/ExcludedSources'
        - $ref: '#/components/parameters/GasPrice'
        - $ref: '#/components/parameters/SwapFeeRecipient'
        - $ref: '#/components/parameters/SwapFeeBps'
        - $ref: '#/components/parameters/SwapFeePpm'
        - $ref: '#/components/parameters/SwapFeeToken'
        - $ref: '#/components/parameters/TradeSurplusRecipient'
        - $ref: '#/components/parameters/TradeSurplusMaxBps'
        - $ref: '#/components/parameters/TradeSurplusMaxPpm'
        - $ref: '#/components/parameters/TxOrigin'
        - $ref: '#/components/parameters/SellEntireBalance'
        - $ref: '#/components/parameters/WrapUnwrapMode'
      responses:
        '200':
          description: A quote, or `liquidityAvailable false` when no route exists.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
            RateLimit-Policy: { $ref: '#/components/headers/RateLimitPolicy' }
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/SwapQuote'
                  - $ref: '#/components/schemas/NoLiquidity'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /gasless/price:
    get:
      operationId: gasless_getPrice
      tags: [Gasless]
      summary: Indicative price for a gasless swap
      description: |
        ERC-20 to ERC-20 only (the wrapped native token is allowed, the native coin is not). On a chain listed by
        `/gasless/chains` with `enabled: false` the price is still returned, with the warning `gasless_not_enabled`.
        With a `taker` whose balance is below `sellAmount`, the price is still returned and `issues.balance` shows
        the shortfall; only `/gasless/quote` answers `400 INSUFFICIENT_BALANCE`.
      x-qiguard-auth: key
      x-qiguard-endpoint: gasless.price
      security: [{ qgApiKey: [] }]
      parameters:
        - $ref: '#/components/parameters/QgVersion'
        - $ref: '#/components/parameters/GaslessChainId'
        - $ref: '#/components/parameters/SellToken'
        - $ref: '#/components/parameters/BuyToken'
        - $ref: '#/components/parameters/SellAmount'
        - $ref: '#/components/parameters/BuyAmount'
        - $ref: '#/components/parameters/TakerOptional'
        - $ref: '#/components/parameters/Recipient'
        - $ref: '#/components/parameters/GaslessSlippageBps'
        - $ref: '#/components/parameters/SlippagePpm'
        - $ref: '#/components/parameters/GaslessDeadlineSeconds'
        - $ref: '#/components/parameters/ExcludedSources'
        - $ref: '#/components/parameters/SwapFeeRecipient'
        - $ref: '#/components/parameters/SwapFeeBps'
        - $ref: '#/components/parameters/SwapFeePpm'
        - $ref: '#/components/parameters/SwapFeeToken'
        - $ref: '#/components/parameters/TradeSurplusRecipient'
        - $ref: '#/components/parameters/TradeSurplusMaxBps'
        - $ref: '#/components/parameters/TradeSurplusMaxPpm'
      responses:
        '200':
          description: A gasless price, or `liquidityAvailable false`.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
            RateLimit-Policy: { $ref: '#/components/headers/RateLimitPolicy' }
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/GaslessPrice'
                  - $ref: '#/components/schemas/NoLiquidity'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /gasless/quote:
    get:
      operationId: gasless_getQuote
      tags: [Gasless]
      summary: Gasless quote with the Permit2 witness message to sign
      description: |
        `taker` is required. Sign `trade.eip712` with `eth_signTypedData_v4` and send it to `POST /gasless/submit`.
        `approval` is always `null` in v1: a taker without a Permit2 allowance must first send one normal
        `approve(Permit2, amount)` transaction and pay its gas (`issues.allowance` shows it). A taker balance below
        `sellAmount` gives `400 INSUFFICIENT_BALANCE`. Before signing, check that `domain.verifyingContract` is Permit2,
        `message.spender` is the settlement contract of the chain and `witness.recipient` is the taker (the SDK's
        `assertGaslessTrade` does this).
      x-qiguard-auth: key
      x-qiguard-endpoint: gasless.quote
      security: [{ qgApiKey: [] }]
      parameters:
        - $ref: '#/components/parameters/QgVersion'
        - $ref: '#/components/parameters/GaslessChainId'
        - $ref: '#/components/parameters/SellToken'
        - $ref: '#/components/parameters/BuyToken'
        - $ref: '#/components/parameters/SellAmount'
        - $ref: '#/components/parameters/BuyAmount'
        - $ref: '#/components/parameters/TakerRequired'
        - $ref: '#/components/parameters/Recipient'
        - $ref: '#/components/parameters/GaslessSlippageBps'
        - $ref: '#/components/parameters/SlippagePpm'
        - $ref: '#/components/parameters/GaslessDeadlineSeconds'
        - $ref: '#/components/parameters/ExcludedSources'
        - $ref: '#/components/parameters/SwapFeeRecipient'
        - $ref: '#/components/parameters/SwapFeeBps'
        - $ref: '#/components/parameters/SwapFeePpm'
        - $ref: '#/components/parameters/SwapFeeToken'
        - $ref: '#/components/parameters/TradeSurplusRecipient'
        - $ref: '#/components/parameters/TradeSurplusMaxBps'
        - $ref: '#/components/parameters/TradeSurplusMaxPpm'
      responses:
        '200':
          description: A gasless quote, or `liquidityAvailable false`.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
            RateLimit-Policy: { $ref: '#/components/headers/RateLimitPolicy' }
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/GaslessQuote'
                  - $ref: '#/components/schemas/NoLiquidity'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /gasless/submit:
    post:
      operationId: gasless_submit
      tags: [Gasless]
      summary: Submit a signed gasless trade
      description: |
        Idempotent by `tradeHash` (the EIP-712 digest): the same trade from the same app returns `200` with the same
        body; from another app it returns `400 TRADE_ALREADY_SUBMITTED`. An optional `Idempotency-Key` header is also
        honoured for 24 hours. Until gasless is enabled on the chain (relayer funded and switched on), this endpoint
        returns `503 GASLESS_NOT_ENABLED` with `data.reason = "gasless_not_enabled"`.
        A `503 RELAYER_UNAVAILABLE` with `data.reason` `relayer_timeout` or `relayer_unreachable` carries
        `data.tradeHash`: the trade may still have been accepted, so read `GET /gasless/status/{tradeHash}` (404 means
        it was not) before quoting and signing again; sending the same body again returns the earlier answer.
      x-qiguard-auth: key
      x-qiguard-endpoint: gasless.submit
      security: [{ qgApiKey: [] }]
      parameters:
        - $ref: '#/components/parameters/QgVersion'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GaslessSubmitRequest' }
      responses:
        '200':
          description: Accepted (status `pending`), or the earlier result of the same trade.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
            RateLimit-Policy: { $ref: '#/components/headers/RateLimitPolicy' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GaslessSubmitResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '413': { $ref: '#/components/responses/PayloadTooLarge' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /gasless/status/{tradeHash}:
    get:
      operationId: gasless_getStatus
      tags: [Gasless]
      summary: Status of a submitted gasless trade
      description: Only the app that submitted the trade can read it. `confirmed` means 5 confirmations on BSC and 3 on Base.
      x-qiguard-auth: key
      x-qiguard-endpoint: gasless.status
      security: [{ qgApiKey: [] }]
      parameters:
        - $ref: '#/components/parameters/QgVersion'
        - $ref: '#/components/parameters/TradeHash'
        - $ref: '#/components/parameters/GaslessChainId'
      responses:
        '200':
          description: Current status.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
            RateLimit-Policy: { $ref: '#/components/headers/RateLimitPolicy' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GaslessStatus' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
        '500': { $ref: '#/components/responses/InternalError' }

  /swap/chains:
    get:
      operationId: swap_getChains
      tags: [Meta]
      summary: Chains supported by the Swap endpoints
      x-qiguard-auth: none
      x-qiguard-endpoint: meta.swap_chains
      responses:
        '200':
          description: Supported chains.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChainsResponse' }
              example: { chains: [ { chainId: 56, chainName: BSC }, { chainId: 1, chainName: Ethereum }, { chainId: 8453, chainName: Base }, { chainId: 137, chainName: Polygon } ], zid: '0x0b7a51f2e9c4d3a8812e6f40' }
        '500': { $ref: '#/components/responses/InternalError' }

  /gasless/chains:
    get:
      operationId: gasless_getChains
      tags: [Meta]
      summary: Chains supported by the Gasless endpoints
      description: "`enabled: false` means price and quote work but submit returns `503 GASLESS_NOT_ENABLED`."
      x-qiguard-auth: none
      x-qiguard-endpoint: meta.gasless_chains
      responses:
        '200':
          description: Gasless chains.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GaslessChainsResponse' }
              example: { chains: [ { chainId: 56, chainName: BSC, enabled: false }, { chainId: 8453, chainName: Base, enabled: false } ], zid: '0x0b7a51f2e9c4d3a8812e6f40' }
        '500': { $ref: '#/components/responses/InternalError' }

  /sources:
    get:
      operationId: sources_getSources
      tags: [Meta]
      summary: Liquidity sources of a chain (names usable in excludedSources)
      x-qiguard-auth: none
      x-qiguard-endpoint: meta.sources
      parameters:
        - $ref: '#/components/parameters/ChainId'
      responses:
        '200':
          description: Source names.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SourcesResponse' }
              example: { sources: [ PancakeSwap_V2, PancakeSwap_V3 ], zid: '0x0b7a51f2e9c4d3a8812e6f40' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '500': { $ref: '#/components/responses/InternalError' }

  /tokens:
    get:
      operationId: tokens_getTokens
      tags: [Meta]
      summary: Tokens and anchors supported on a chain
      x-qiguard-auth: none
      x-qiguard-endpoint: meta.tokens
      parameters:
        - $ref: '#/components/parameters/ChainId'
      responses:
        '200':
          description: Token list.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokensResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '500': { $ref: '#/components/responses/InternalError' }

  /status:
    get:
      operationId: status_getStatus
      tags: [Meta]
      summary: Live state of every chain (contract pause flag, RPC, swap and gasless availability)
      x-qiguard-auth: none
      x-qiguard-endpoint: meta.status
      responses:
        '200':
          description: Status, cached for 15 seconds.
          headers:
            qg-zid: { $ref: '#/components/headers/QgZid' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/StatusResponse' }
        '500': { $ref: '#/components/responses/InternalError' }

components:
  securitySchemes:
    qgApiKey:
      type: apiKey
      in: header
      name: qg-api-key
      description: Secret key `qg_sk_...` from dashboard.qiguard.com. Server side only; a secret key sent by a browser (Origin plus `Sec-Fetch-Mode cors`) is refused with `403 ORIGIN_NOT_ALLOWED`.

  headers:
    QgZid:
      description: Request id, the same value as the body `zid`.
      schema: { $ref: '#/components/schemas/Zid' }
    RateLimitPolicy:
      description: 'The app burst limit, for example `"burst";q=50;w=10` (5 requests per second counted over 10 seconds). A `"daily"` part is added only when the app has a daily cap.'
      schema: { type: string }
    RetryAfter:
      description: Seconds to wait before retrying.
      schema: { type: integer, minimum: 1 }
    QgQuotaRemaining:
      description: Approximate requests left today; sent only when the app has a daily cap.
      schema: { type: integer }
    QgQuotaReset:
      description: Seconds until 00:00 UTC; sent only when the app has a daily cap.
      schema: { type: integer }

  parameters:
    QgVersion:
      name: qg-version
      in: header
      required: true
      description: 'API version. Missing gives `400 API_VERSION_MISSING`; another value gives `400 INPUT_INVALID`.'
      schema: { type: string, enum: [v1] }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: 1-64 visible ASCII characters. Same key and same body within 24 h returns the stored response; same key with another body gives `400 IDEMPOTENCY_KEY_REUSED`.
      schema: { type: string, minLength: 1, maxLength: 64, pattern: '^[\x21-\x7e]{1,64}$' }
    ChainId:
      name: chainId
      in: query
      required: true
      schema: { type: integer, enum: [1, 56, 137, 8453] }
    GaslessChainId:
      name: chainId
      in: query
      required: true
      description: Gasless chains are BSC (56) and Base (8453). Other values give `400 CHAIN_NOT_SUPPORTED`.
      schema: { type: integer, enum: [56, 8453] }
    SellToken:
      name: sellToken
      in: query
      required: true
      schema: { $ref: '#/components/schemas/Address' }
    BuyToken:
      name: buyToken
      in: query
      required: true
      schema: { $ref: '#/components/schemas/Address' }
    SellAmount:
      name: sellAmount
      in: query
      required: true
      description: Amount of sellToken in base units.
      schema: { $ref: '#/components/schemas/Uint' }
    BuyAmount:
      name: buyAmount
      in: query
      required: false
      deprecated: true
      description: Exact-out is not supported in v1; sending it gives `400 EXACT_OUT_NOT_SUPPORTED`.
      schema: { $ref: '#/components/schemas/Uint' }
    TakerOptional:
      name: taker
      in: query
      required: false
      description: The wallet that holds sellToken. When present, `issues` are filled and the wallet's on-chain fee rate is used.
      schema: { $ref: '#/components/schemas/Address' }
    TakerRequired:
      name: taker
      in: query
      required: true
      description: The wallet that holds sellToken and sends (or signs) the trade.
      schema: { $ref: '#/components/schemas/Address' }
    Recipient:
      name: recipient
      in: query
      required: false
      description: Must be absent or equal to `taker`; otherwise `400 RECIPIENT_NOT_SUPPORTED` (the contract pays the taker).
      schema: { $ref: '#/components/schemas/Address' }
    SlippageBps:
      name: slippageBps
      in: query
      required: false
      description: Default 100, allowed 10-1000. Mutually exclusive with slippagePpm. `minBuyAmount = floor(buyAmount * (10000 - slippageBps) / 10000)`.
      schema: { type: integer, minimum: 10, maximum: 1000, default: 100 }
    GaslessSlippageBps:
      name: slippageBps
      in: query
      required: false
      description: Default 50, allowed 10-300. Mutually exclusive with slippagePpm.
      schema: { type: integer, minimum: 10, maximum: 300, default: 50 }
    SlippagePpm:
      name: slippagePpm
      in: query
      required: false
      description: Slippage in parts per million, rounded down to whole bps (warning `slippage_ppm_rounded`). Same limits as slippageBps.
      schema: { type: integer, minimum: 1000, maximum: 100000 }
    DeadlineSeconds:
      name: deadlineSeconds
      in: query
      required: false
      description: Seconds until the calldata deadline (`expiresAt`). Default 120, allowed 30-600.
      schema: { type: integer, minimum: 30, maximum: 600, default: 120 }
    GaslessDeadlineSeconds:
      name: deadlineSeconds
      in: query
      required: false
      description: Seconds until the signed deadline. Default 180, allowed 60-600.
      schema: { type: integer, minimum: 60, maximum: 600, default: 180 }
    ExcludedSources:
      name: excludedSources
      in: query
      required: false
      description: Comma-separated names from `/sources`. Unknown names are listed in `issues.invalidSourcesPassed`.
      schema: { type: string }
    GasPrice:
      name: gasPrice
      in: query
      required: false
      description: Gas price in wei for the estimate. Default is the chain's current gas price.
      schema: { $ref: '#/components/schemas/Uint' }
    SwapFeeRecipient:
      name: swapFeeRecipient
      in: query
      required: false
      description: Your fee wallet, or up to 3 wallets separated by commas. Sent together with swapFeeBps; the router pays each wallet its share on chain in the same transaction.
      schema: { type: string }
    SwapFeeBps:
      name: swapFeeBps
      in: query
      required: false
      description: The fee of each swapFeeRecipient in basis points, in the same order (for example `30` or `20,10`). The total is at most 300.
      schema: { type: string }
    SwapFeePpm:
      name: swapFeePpm
      in: query
      required: false
      deprecated: true
      description: Not accepted (`400 INPUT_INVALID`); use swapFeeBps.
      schema: { type: string }
    SwapFeeToken:
      name: swapFeeToken
      in: query
      required: false
      description: The token the integrator fee is taken in, sellToken or buyToken. Default buyToken.
      schema: { $ref: '#/components/schemas/Address' }
    TradeSurplusRecipient:
      name: tradeSurplusRecipient
      in: query
      required: false
      deprecated: true
      description: Accepted and ignored (warning `trade_surplus_ignored`); the whole output always goes to the taker.
      schema: { type: string }
    TradeSurplusMaxBps:
      name: tradeSurplusMaxBps
      in: query
      required: false
      deprecated: true
      description: Accepted and ignored (warning `trade_surplus_ignored`).
      schema: { type: string }
    TradeSurplusMaxPpm:
      name: tradeSurplusMaxPpm
      in: query
      required: false
      deprecated: true
      description: Accepted and ignored (warning `trade_surplus_ignored`).
      schema: { type: string }
    TxOrigin:
      name: txOrigin
      in: query
      required: false
      deprecated: true
      description: Accepted and ignored (warning `tx_origin_ignored`). The contract pays msg.sender, so a contract taker must call the contract itself.
      schema: { $ref: '#/components/schemas/Address' }
    SellEntireBalance:
      name: sellEntireBalance
      in: query
      required: false
      description: '`false` is accepted; `true` gives `400 INPUT_INVALID` (reason `not_supported`).'
      schema: { type: string, enum: ['true', 'false'] }
    WrapUnwrapMode:
      name: wrapUnwrapMode
      in: query
      required: false
      description: '`direct` is accepted and ignored; `settler` gives `400 INPUT_INVALID`. Wrap and unwrap pairs give `TOKEN_PAIR_NOT_SUPPORTED`.'
      schema: { type: string, enum: [direct, settler] }
    TradeHash:
      name: tradeHash
      in: path
      required: true
      schema: { $ref: '#/components/schemas/Bytes32' }

  schemas:
    Address:
      type: string
      pattern: '^0x[0-9a-fA-F]{40}$'
    Uint:
      type: string
      pattern: '^[0-9]{1,78}$'
      description: Unsigned integer as a decimal string.
    Hex:
      type: string
      pattern: '^0x([0-9a-fA-F]{2})*$'
    Bytes32:
      type: string
      pattern: '^0x[0-9a-fA-F]{64}$'
    Zid:
      type: string
      pattern: '^0x[0-9a-f]{24}$'
      description: Request id, 0x + 12 random bytes.

    Error:
      type: object
      required: [name, message, data]
      properties:
        name:
          type: string
          description: Error code, see the error table in the docs.
          examples: [INPUT_INVALID]
        message: { type: string }
        data:
          type: object
          required: [zid]
          additionalProperties: true
          properties:
            zid: { $ref: '#/components/schemas/Zid' }
            details:
              type: array
              items:
                type: object
                required: [field, reason]
                properties:
                  field: { type: string }
                  reason: { type: string }
            reason: { type: string, description: 'Short machine-readable sub-reason, for example gasless_not_enabled, gasless_blocked, price_moved.' }
      example:
        name: INPUT_INVALID
        message: sellAmount must be a positive integer string
        data: { zid: '0x7d1e0c55a9b2f4e83c61d0aa', details: [ { field: sellAmount, reason: not an integer } ] }

    Warning:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          enum: [trade_surplus_ignored, tx_origin_ignored, wrap_unwrap_mode_ignored, slippage_ppm_rounded, secret_key_origin_header, gasless_not_enabled]
        message: { type: string }

    NoLiquidity:
      type: object
      required: [liquidityAvailable, zid]
      properties:
        liquidityAvailable: { type: boolean, const: false }
        zid: { $ref: '#/components/schemas/Zid' }

    QiguardFee:
      type: object
      required: [amount, token, type, bps, takenFrom]
      description: The Qiguard fee of the app's plan (Free 25 bps, paid plans 0), taken on chain from the gross output in the buy token. On a chain without a router it is the settlement fee (USDT-input trades pay it from the sell amount in USDT).
      properties:
        amount: { $ref: '#/components/schemas/Uint' }
        token: { $ref: '#/components/schemas/Address' }
        type: { type: string, const: volume }
        bps: { type: integer, minimum: 0, maximum: 100 }
        takenFrom: { type: string, enum: [sell, buy] }

    IntegratorFee:
      type: object
      required: [amount, token, type, recipient, bps]
      description: One integrator fee (swapFeeRecipient / swapFeeBps), paid on chain to the recipient in the same transaction.
      properties:
        amount: { $ref: '#/components/schemas/Uint' }
        token: { $ref: '#/components/schemas/Address' }
        type: { type: string, const: volume }
        recipient: { $ref: '#/components/schemas/Address' }
        bps: { type: integer, minimum: 1, maximum: 300 }

    GasFee:
      type: object
      required: [amount, token, type, sponsored]
      description: Gasless only. In v1 the gas is sponsored, so amount is always "0".
      properties:
        amount: { $ref: '#/components/schemas/Uint' }
        token: { $ref: '#/components/schemas/Address' }
        type: { type: string, const: gas }
        sponsored: { type: boolean }
        estimatedCostUsd: { type: string, description: 'Estimated gas cost in USD, decimal string.' }

    Fees:
      type: object
      required: [integratorFee, integratorFees, qiguardFee, gasFee]
      properties:
        integratorFee:
          description: The first integrator fee, or null.
          oneOf:
            - { $ref: '#/components/schemas/IntegratorFee' }
            - { type: 'null' }
        integratorFees: { type: array, maxItems: 3, items: { $ref: '#/components/schemas/IntegratorFee' } }
        qiguardFee: { $ref: '#/components/schemas/QiguardFee' }
        gasFee:
          oneOf:
            - { $ref: '#/components/schemas/GasFee' }
            - { type: 'null' }

    Issues:
      type: object
      required: [allowance, balance, simulationIncomplete, invalidSourcesPassed]
      properties:
        allowance:
          oneOf:
            - type: object
              required: [actual, spender]
              properties:
                actual: { $ref: '#/components/schemas/Uint' }
                spender: { $ref: '#/components/schemas/Address' }
                resetRequired: { type: boolean, description: 'True for Ethereum USDT with a non-zero allowance below sellAmount; approve 0 first.' }
            - { type: 'null' }
        balance:
          oneOf:
            - type: object
              required: [token, actual, expected]
              properties:
                token: { $ref: '#/components/schemas/Address' }
                actual: { $ref: '#/components/schemas/Uint' }
                expected: { $ref: '#/components/schemas/Uint' }
            - { type: 'null' }
        simulationIncomplete: { type: boolean }
        invalidSourcesPassed: { type: array, items: { type: string } }

    RouteFill:
      type: object
      required: [from, to, source, proportionBps]
      properties:
        from: { $ref: '#/components/schemas/Address' }
        to: { $ref: '#/components/schemas/Address' }
        source: { type: string, examples: [Uniswap_V3] }
        proportionBps: { type: string, const: '10000' }
        feeTier: { type: integer, description: 'V3 pool fee in hundredths of a bip; absent on V2 legs.' }

    Route:
      type: object
      required: [fills, tokens]
      description: One fill per hop. Phase 1 routes use one source for the whole trade, at most 2 hops.
      properties:
        fills: { type: array, items: { $ref: '#/components/schemas/RouteFill' } }
        tokens:
          type: array
          items:
            type: object
            required: [address, symbol]
            properties:
              address: { $ref: '#/components/schemas/Address' }
              symbol: { type: string }

    TaxBps:
      type: object
      required: [buyTaxBps, sellTaxBps, transferTaxBps]
      description: Not measured in v1 (null). Taxed tokens fail simulation and return TOKEN_NOT_SUPPORTED.
      properties:
        buyTaxBps: { type: ['string', 'null'] }
        sellTaxBps: { type: ['string', 'null'] }
        transferTaxBps: { type: ['string', 'null'] }

    TokenMetadata:
      type: object
      required: [buyToken, sellToken]
      properties:
        buyToken: { $ref: '#/components/schemas/TaxBps' }
        sellToken: { $ref: '#/components/schemas/TaxBps' }

    Transaction:
      type: object
      required: [to, data, gas, gasPrice, value]
      properties:
        to: { $ref: '#/components/schemas/Address' }
        data: { $ref: '#/components/schemas/Hex' }
        gas: { $ref: '#/components/schemas/Uint' }
        gasPrice: { $ref: '#/components/schemas/Uint' }
        value: { $ref: '#/components/schemas/Uint' }

    Protection:
      type: object
      required: [settlementPaused, simulation, quoteIntegrity, minBuyAmountEnforcedOnchain, priceBand, mev]
      properties:
        settlementPaused: { type: boolean, description: 'Live paused() of the settlement contract (cached 15 s). While true, price and quote return 503 SETTLEMENT_PAUSED.' }
        simulation:
          type: string
          enum: [passed, not_run, skipped_allowance, skipped_balance, at_submit]
          description: 'Swap quote: eth_call of the exact transaction from the taker (passed), or skipped because the allowance or balance is short. Swap price: not_run. Gasless price and quote: at_submit (the relayer simulates before sending).'
        quoteIntegrity:
          type: object
          required: [rpcAgreement, block]
          properties:
            rpcAgreement: { type: integer, description: 'Number of independent RPC operators that agreed (1 on price, 2 on quote).' }
            maxDeviationBps: { type: ['integer', 'null'] }
            block: { type: integer, description: 'Block the quote was read at.' }
        minBuyAmountEnforcedOnchain: { type: boolean, const: true }
        priceBand:
          type: object
          required: [onchainEnforced, priceImpactBps]
          properties:
            onchainEnforced: { type: boolean, description: 'priceBandEnabled() of the contract.' }
            priceImpactBps: { type: ['integer', 'null'], description: 'Execution price against a small reference trade on the same route; quote only, null on price.' }
        mev:
          type: object
          required: [submission]
          properties:
            submission: { type: string, enum: [taker_submits, private, sequencer] }
            channels: { type: array, items: { type: string } }
        routeBoundBySignature: { type: boolean, description: 'Gasless only; false in v1, the signed message does not fix the route.' }
        signatureExposure: { type: array, items: { type: string }, description: 'Gasless only; who sees the signed message before inclusion. `qiguard` includes the private (keyed) RPC endpoint the relayer uses to simulate the transaction; no public RPC receives it.' }

    QiguardCall:
      type: object
      required: [function, selector, adapter, deadline, tag]
      properties:
        function: { type: string, enum: [marketSwap, marketSwapNativeToToken, marketSwapTokenToNative] }
        selector: { type: string, enum: ['0xb5019436', '0x6f1190c5', '0x1f60a2c7'] }
        adapter: { $ref: '#/components/schemas/Address' }
        deadline: { type: integer }
        tag:
          oneOf:
            - { type: string, pattern: '^0x71673031[0-9a-f]{28}$', description: '18 bytes appended to transaction.data, "qg01" + 8 bytes of sha256(appId) + 6 bytes of zid.' }
            - { type: 'null' }

    SwapPrice:
      type: object
      required: [liquidityAvailable, blockNumber, sellToken, buyToken, sellAmount, buyAmount, minBuyAmount, allowanceTarget, fees, issues, warnings, route, tokenMetadata, totalNetworkFee, gas, gasPrice, protection, mode, zid]
      properties:
        liquidityAvailable: { type: boolean, const: true }
        blockNumber: { $ref: '#/components/schemas/Uint' }
        sellToken: { $ref: '#/components/schemas/Address' }
        buyToken: { $ref: '#/components/schemas/Address' }
        sellAmount: { $ref: '#/components/schemas/Uint' }
        buyAmount: { $ref: '#/components/schemas/Uint', description: 'Net amount the taker receives.' }
        minBuyAmount: { $ref: '#/components/schemas/Uint' }
        allowanceTarget:
          description: The settlement contract; null when selling the native coin.
          oneOf:
            - { $ref: '#/components/schemas/Address' }
            - { type: 'null' }
        fees: { $ref: '#/components/schemas/Fees' }
        issues: { $ref: '#/components/schemas/Issues' }
        warnings: { type: array, items: { $ref: '#/components/schemas/Warning' } }
        route: { $ref: '#/components/schemas/Route' }
        tokenMetadata: { $ref: '#/components/schemas/TokenMetadata' }
        totalNetworkFee: { $ref: '#/components/schemas/Uint', description: 'gas x gasPrice, plus the L1 data fee on Base.' }
        gas: { $ref: '#/components/schemas/Uint' }
        gasPrice: { $ref: '#/components/schemas/Uint' }
        protection: { $ref: '#/components/schemas/Protection' }
        mode: { type: string, const: exact-in }
        zid: { $ref: '#/components/schemas/Zid' }

    SwapQuote:
      type: object
      required: [liquidityAvailable, blockNumber, expiresAt, sellToken, buyToken, sellAmount, buyAmount, minBuyAmount, allowanceTarget, fees, issues, warnings, route, tokenMetadata, totalNetworkFee, transaction, protection, qiguard, mode, zid]
      properties:
        liquidityAvailable: { type: boolean, const: true }
        blockNumber: { $ref: '#/components/schemas/Uint' }
        expiresAt: { type: integer, description: 'Unix seconds; the deadline inside transaction.data.' }
        sellToken: { $ref: '#/components/schemas/Address' }
        buyToken: { $ref: '#/components/schemas/Address' }
        sellAmount: { $ref: '#/components/schemas/Uint' }
        buyAmount: { $ref: '#/components/schemas/Uint' }
        minBuyAmount: { $ref: '#/components/schemas/Uint' }
        allowanceTarget:
          oneOf:
            - { $ref: '#/components/schemas/Address' }
            - { type: 'null' }
        fees: { $ref: '#/components/schemas/Fees' }
        issues: { $ref: '#/components/schemas/Issues' }
        warnings: { type: array, items: { $ref: '#/components/schemas/Warning' } }
        route: { $ref: '#/components/schemas/Route' }
        tokenMetadata: { $ref: '#/components/schemas/TokenMetadata' }
        totalNetworkFee: { $ref: '#/components/schemas/Uint' }
        transaction: { $ref: '#/components/schemas/Transaction' }
        protection: { $ref: '#/components/schemas/Protection' }
        qiguard: { $ref: '#/components/schemas/QiguardCall' }
        mode: { type: string, const: exact-in }
        zid: { $ref: '#/components/schemas/Zid' }
      example:
        liquidityAvailable: true
        blockNumber: '129508144'
        expiresAt: 1793000120
        sellToken: '0x55d398326f99059fF775485246999027B3197955'
        buyToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
        sellAmount: '100000000000000000000'
        buyAmount: '158640000000000000'
        minBuyAmount: '157053600000000000'
        allowanceTarget: '0x79A76e54f354AC7567f97d979EC1b785c15e37d8'
        fees:
          integratorFee: null
          integratorFees: []
          qiguardFee: { amount: '500000000000000000', token: '0x55d398326f99059fF775485246999027B3197955', type: volume, bps: 50, takenFrom: sell }
          gasFee: null
        issues: { allowance: { actual: '0', spender: '0x79A76e54f354AC7567f97d979EC1b785c15e37d8' }, balance: null, simulationIncomplete: true, invalidSourcesPassed: [] }
        warnings: []
        route:
          fills: [ { from: '0x55d398326f99059fF775485246999027B3197955', to: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', source: PancakeSwap_V3, proportionBps: '10000', feeTier: 100 } ]
          tokens: [ { address: '0x55d398326f99059fF775485246999027B3197955', symbol: USDT }, { address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', symbol: WBNB } ]
        tokenMetadata: { buyToken: { buyTaxBps: null, sellTaxBps: null, transferTaxBps: null }, sellToken: { buyTaxBps: null, sellTaxBps: null, transferTaxBps: null } }
        totalNetworkFee: '26000000000000'
        transaction: { to: '0x79A76e54f354AC7567f97d979EC1b785c15e37d8', data: '0x1f60a2c7...71673031a4c2e09b5d7f1e335f1c9a7e2b44', gas: '260000', gasPrice: '100000000', value: '0' }
        protection:
          settlementPaused: false
          simulation: skipped_allowance
          quoteIntegrity: { rpcAgreement: 2, maxDeviationBps: 4, block: 129508144 }
          minBuyAmountEnforcedOnchain: true
          priceBand: { onchainEnforced: false, priceImpactBps: 3 }
          mev: { submission: taker_submits }
        qiguard: { function: marketSwapTokenToNative, selector: '0x1f60a2c7', adapter: '0xb8dfc084c3912B71662044092722fdDA40b15Ab3', deadline: 1793000120, tag: '0x71673031a4c2e09b5d7f1e335f1c9a7e2b44' }
        mode: exact-in
        zid: '0x5f1c9a7e2b44d0c3a1e8f6b2'

    PermitWitnessMessage:
      type: object
      required: [permitted, spender, nonce, deadline, witness]
      additionalProperties: false
      properties:
        permitted:
          type: object
          required: [token, amount]
          additionalProperties: false
          properties:
            token: { $ref: '#/components/schemas/Address' }
            amount: { $ref: '#/components/schemas/Uint' }
        spender: { $ref: '#/components/schemas/Address', description: 'The settlement contract of the chain.' }
        nonce: { $ref: '#/components/schemas/Uint', description: 'Random unordered Permit2 nonce.' }
        deadline: { $ref: '#/components/schemas/Uint' }
        witness:
          type: object
          required: [tokenOut, minAmountOut, recipient, adapter, deadline]
          additionalProperties: false
          properties:
            tokenOut: { $ref: '#/components/schemas/Address' }
            minAmountOut: { $ref: '#/components/schemas/Uint' }
            recipient: { $ref: '#/components/schemas/Address', description: 'Must be the taker (the signer).' }
            adapter: { $ref: '#/components/schemas/Address' }
            deadline: { $ref: '#/components/schemas/Uint', description: 'Equal to the outer deadline.' }

    PermitWitnessTypedData:
      type: object
      required: [types, primaryType, domain, message]
      additionalProperties: false
      description: |
        Must be sent back exactly as returned by /gasless/quote: any change to types, primaryType or domain gives
        META_TRANSACTION_INVALID. The digest of this object is the tradeHash.
      properties:
        types:
          type: object
          required: [EIP712Domain, PermitWitnessTransferFrom, TokenPermissions, OrderWitness]
          additionalProperties: false
          properties:
            EIP712Domain: { $ref: '#/components/schemas/Eip712Fields' }
            PermitWitnessTransferFrom: { $ref: '#/components/schemas/Eip712Fields' }
            TokenPermissions: { $ref: '#/components/schemas/Eip712Fields' }
            OrderWitness: { $ref: '#/components/schemas/Eip712Fields' }
        primaryType: { type: string, const: PermitWitnessTransferFrom }
        domain:
          type: object
          required: [name, chainId, verifyingContract]
          additionalProperties: false
          properties:
            name: { type: string, const: Permit2 }
            chainId: { type: integer, enum: [56, 8453] }
            verifyingContract: { type: string, const: '0x000000000022D473030F116dDEE9F6B43aC78BA3' }
        message: { $ref: '#/components/schemas/PermitWitnessMessage' }
      example:
        types:
          EIP712Domain: [ { name: name, type: string }, { name: chainId, type: uint256 }, { name: verifyingContract, type: address } ]
          PermitWitnessTransferFrom: [ { name: permitted, type: TokenPermissions }, { name: spender, type: address }, { name: nonce, type: uint256 }, { name: deadline, type: uint256 }, { name: witness, type: OrderWitness } ]
          TokenPermissions: [ { name: token, type: address }, { name: amount, type: uint256 } ]
          OrderWitness: [ { name: tokenOut, type: address }, { name: minAmountOut, type: uint256 }, { name: recipient, type: address }, { name: adapter, type: address }, { name: deadline, type: uint256 } ]
        primaryType: PermitWitnessTransferFrom
        domain: { name: Permit2, chainId: 8453, verifyingContract: '0x000000000022D473030F116dDEE9F6B43aC78BA3' }
        message:
          permitted: { token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '25000000' }
          spender: '0x67433187Db1e57f66E88880b6Be833f6759362b1'
          nonce: '123456789012345678901234567890'
          deadline: '1793000120'
          witness: { tokenOut: '0x4200000000000000000000000000000000000006', minAmountOut: '5981940000000000', recipient: '0x1111111111111111111111111111111111111111', adapter: '0xB47011d787B432ccF7A53284a74432a0De4397a3', deadline: '1793000120' }

    Eip712Fields:
      type: array
      items:
        type: object
        required: [name, type]
        additionalProperties: false
        properties:
          name: { type: string }
          type: { type: string }

    GaslessTrade:
      type: object
      required: [type, hash, eip712]
      properties:
        type: { type: string, const: qiguard_permit2_witness }
        hash: { $ref: '#/components/schemas/Bytes32', description: 'EIP-712 digest of eip712; equals the tradeHash returned by submit.' }
        eip712: { $ref: '#/components/schemas/PermitWitnessTypedData' }

    GaslessPrice:
      type: object
      required: [liquidityAvailable, blockNumber, sellToken, buyToken, sellAmount, buyAmount, minBuyAmount, target, fees, issues, warnings, route, tokenMetadata, protection, zid]
      properties:
        liquidityAvailable: { type: boolean, const: true }
        blockNumber: { $ref: '#/components/schemas/Uint' }
        sellToken: { $ref: '#/components/schemas/Address' }
        buyToken: { $ref: '#/components/schemas/Address' }
        sellAmount: { $ref: '#/components/schemas/Uint' }
        buyAmount: { $ref: '#/components/schemas/Uint' }
        minBuyAmount: { $ref: '#/components/schemas/Uint' }
        target: { $ref: '#/components/schemas/Address', description: 'The settlement contract (the Permit2 spender).' }
        fees: { $ref: '#/components/schemas/Fees' }
        issues: { $ref: '#/components/schemas/Issues' }
        warnings: { type: array, items: { $ref: '#/components/schemas/Warning' } }
        route: { $ref: '#/components/schemas/Route' }
        tokenMetadata: { $ref: '#/components/schemas/TokenMetadata' }
        protection: { $ref: '#/components/schemas/Protection' }
        zid: { $ref: '#/components/schemas/Zid' }

    GaslessQuote:
      type: object
      required: [liquidityAvailable, blockNumber, expiresAt, sellToken, buyToken, sellAmount, buyAmount, minBuyAmount, target, approval, trade, fees, issues, warnings, route, tokenMetadata, protection, zid]
      properties:
        liquidityAvailable: { type: boolean, const: true }
        blockNumber: { $ref: '#/components/schemas/Uint' }
        expiresAt: { type: integer, description: 'The signed deadline (unix seconds).' }
        sellToken: { $ref: '#/components/schemas/Address' }
        buyToken: { $ref: '#/components/schemas/Address' }
        sellAmount: { $ref: '#/components/schemas/Uint' }
        buyAmount: { $ref: '#/components/schemas/Uint' }
        minBuyAmount: { $ref: '#/components/schemas/Uint' }
        target: { $ref: '#/components/schemas/Address' }
        approval: { type: 'null', description: 'Always null in v1.' }
        trade: { $ref: '#/components/schemas/GaslessTrade' }
        fees: { $ref: '#/components/schemas/Fees' }
        issues: { $ref: '#/components/schemas/Issues' }
        warnings: { type: array, items: { $ref: '#/components/schemas/Warning' } }
        route: { $ref: '#/components/schemas/Route' }
        tokenMetadata: { $ref: '#/components/schemas/TokenMetadata' }
        protection: { $ref: '#/components/schemas/Protection' }
        zid: { $ref: '#/components/schemas/Zid' }

    Signature:
      type: object
      required: [signatureType, v, r, s]
      additionalProperties: false
      properties:
        signatureType: { type: integer, const: 2, description: '2 = EIP-712 ECDSA signature of an EOA.' }
        v: { type: integer, enum: [27, 28] }
        r: { $ref: '#/components/schemas/Bytes32' }
        s: { $ref: '#/components/schemas/Bytes32' }

    GaslessSubmitRequest:
      type: object
      required: [chainId, trade]
      additionalProperties: false
      properties:
        chainId: { type: integer, enum: [56, 8453] }
        trade:
          type: object
          required: [type, eip712, signature]
          additionalProperties: false
          properties:
            type: { type: string, const: qiguard_permit2_witness }
            eip712: { $ref: '#/components/schemas/PermitWitnessTypedData' }
            signature: { $ref: '#/components/schemas/Signature' }
        approval: { type: 'null', description: 'Must be absent or null in v1; anything else gives INVALID_APPROVAL.' }

    GaslessSubmitResponse:
      type: object
      required: [tradeHash, type, zid]
      properties:
        tradeHash: { $ref: '#/components/schemas/Bytes32' }
        type: { type: string, const: qiguard_permit2_witness }
        zid: { $ref: '#/components/schemas/Zid' }

    GaslessStatus:
      type: object
      required: [status, transactions, approvalTransactions, zid]
      properties:
        status: { type: string, enum: [pending, submitted, succeeded, confirmed, failed] }
        transactions:
          type: array
          description: Every hash broadcast for this trade (rebroadcasts and replacements included).
          items:
            type: object
            required: [hash, timestamp]
            properties:
              hash: { $ref: '#/components/schemas/Bytes32' }
              timestamp: { type: integer }
        approvalTransactions: { type: array, maxItems: 0, items: {} }
        reason:
          type: string
          description: Present when status is failed.
          enum: [transaction_simulation_failed, order_expired, transaction_reverted, insufficient_allowance, insufficient_balance, internal_error]
        detail: { type: string, description: 'Optional extra detail, for example slippage or permit_consumed_by_other_tx.' }
        zid: { $ref: '#/components/schemas/Zid' }

    ChainsResponse:
      type: object
      required: [chains, zid]
      properties:
        chains:
          type: array
          items:
            type: object
            required: [chainId, chainName]
            properties:
              chainId: { type: integer }
              chainName: { type: string }
        zid: { $ref: '#/components/schemas/Zid' }

    GaslessChainsResponse:
      type: object
      required: [chains, zid]
      properties:
        chains:
          type: array
          items:
            type: object
            required: [chainId, chainName, enabled]
            properties:
              chainId: { type: integer }
              chainName: { type: string }
              enabled: { type: boolean, description: 'False until the relayer is funded and gasless is switched on for the chain.' }
        zid: { $ref: '#/components/schemas/Zid' }

    SourcesResponse:
      type: object
      required: [sources, zid]
      properties:
        sources: { type: array, items: { type: string } }
        zid: { $ref: '#/components/schemas/Zid' }

    TokensResponse:
      type: object
      required: [tokens, zid]
      properties:
        tokens:
          type: array
          items:
            type: object
            required: [address, symbol, decimals, anchor, gasless]
            properties:
              address: { $ref: '#/components/schemas/Address' }
              symbol: { type: string }
              decimals: { type: integer }
              anchor: { type: boolean, description: 'USDT, USDC, the native coin or the wrapped native token. Every pair needs an anchor on one side.' }
              gasless: { type: boolean, description: 'Usable on the gasless endpoints of this chain (ERC-20 on a gasless chain).' }
        zid: { $ref: '#/components/schemas/Zid' }

    StatusResponse:
      type: object
      required: [version, time, chains, zid]
      properties:
        version: { type: string }
        time: { type: string, format: date-time }
        chains:
          type: array
          items:
            type: object
            required: [chainId, chainName, settlement, paused, priceBandEnforced, feeRecipient, headBlock, rpc, swap, gasless]
            properties:
              chainId: { type: integer }
              chainName: { type: string }
              settlement: { $ref: '#/components/schemas/Address' }
              paused: { type: boolean }
              priceBandEnforced: { type: boolean }
              feeRecipient: { $ref: '#/components/schemas/Address' }
              headBlock: { type: integer }
              rpc: { type: string, enum: [ok, degraded, down] }
              swap: { type: string, enum: [operational, paused, switched_off, degraded] }
              gasless:
                type: object
                required: [state]
                properties:
                  state: { type: string, enum: [not_listed, not_enabled, operational, switched_off, relayer_low, relayer_busy, relayer_stalled, paused, degraded] }
                  relayer: { type: string, enum: [ok, low, empty, unknown] }
                  queued: { type: integer }
        zid: { $ref: '#/components/schemas/Zid' }

  responses:
    BadRequest:
      description: |
        INPUT_INVALID, HTTPS_REQUIRED, API_VERSION_MISSING, CHAIN_NOT_SUPPORTED, TOKEN_NOT_SUPPORTED, TOKEN_PAIR_NOT_SUPPORTED,
        RECIPIENT_NOT_SUPPORTED, SELL_AMOUNT_TOO_SMALL, INSUFFICIENT_BALANCE (gasless quote only), SWAP_VALIDATION_FAILED, UNABLE_TO_CALCULATE_GAS_FEE,
        SMART_WALLET_NOT_SUPPORTED, INTEGRATOR_FEE_NOT_SUPPORTED, EXACT_OUT_NOT_SUPPORTED, IDEMPOTENCY_KEY_REUSED;
        on submit also INSUFFICIENT_BALANCE_OR_ALLOWANCE, INVALID_APPROVAL, INVALID_SIGNATURE, INVALID_SIGNER,
        META_TRANSACTION_EXPIRY_TOO_SOON, META_TRANSACTION_INVALID, PENDING_TRADES_ALREADY_EXIST, TRADE_ALREADY_SUBMITTED.
      headers: { qg-zid: { $ref: '#/components/headers/QgZid' } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: API_KEY_MISSING (message "No API key found in request") or API_KEY_INVALID (unknown, malformed or revoked key).
      headers: { qg-zid: { $ref: '#/components/headers/QgZid' } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: APP_NOT_APPROVED (data.status pending or suspended), PRODUCT_NOT_ENABLED (data.reason product_not_enabled or chain_not_enabled), ORIGIN_NOT_ALLOWED, TAKER_NOT_AUTHORIZED_FOR_TRADE (data.reason denied or gasless_blocked).
      headers: { qg-zid: { $ref: '#/components/headers/QgZid' } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: META_TRANSACTION_STATUS_NOT_FOUND, or NOT_FOUND for an unknown path.
      headers: { qg-zid: { $ref: '#/components/headers/QgZid' } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    PayloadTooLarge:
      description: PAYLOAD_TOO_LARGE (body above 256 KB).
      headers: { qg-zid: { $ref: '#/components/headers/QgZid' } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    TooManyRequests:
      description: RATE_LIMITED (Retry-After 10), QUOTA_EXCEEDED (daily request cap or gasless submits per day; Retry-After = seconds to 00:00 UTC), GAS_BUDGET_EXCEEDED (sponsored-gas budget of the app, the chain or the free tier).
      headers:
        qg-zid: { $ref: '#/components/headers/QgZid' }
        Retry-After: { $ref: '#/components/headers/RetryAfter' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    InternalError:
      description: INTERNAL_SERVER_ERROR or UNCATEGORIZED.
      headers: { qg-zid: { $ref: '#/components/headers/QgZid' } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unavailable:
      description: |
        SETTLEMENT_PAUSED (contract paused), SWAP_NOT_ENABLED (swap switched off for the chain), GASLESS_NOT_ENABLED
        (data.reason gasless_not_enabled: relayer not set up or funded yet, or switched off), RELAYER_UNAVAILABLE
        (data.reason relayer_low_balance, relayer_busy, relayer_stalled, gas_price_above_cap, relayer_timeout or
        relayer_unreachable; Retry-After when busy or timed out; relayer_timeout and relayer_unreachable add
        data.tradeHash: read the status of that trade before quoting again), RPC_UNAVAILABLE.
      headers:
        qg-zid: { $ref: '#/components/headers/QgZid' }
        Retry-After: { $ref: '#/components/headers/RetryAfter' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example: { name: GASLESS_NOT_ENABLED, message: Gasless is not enabled on chain 56 yet, data: { zid: '0x5f1c9a7e2b44d0c3a1e8f6b2', reason: gasless_not_enabled, chainId: 56 } }
