openapi: "3.0.3"
info:
  title: Kipseli PropAMM
  version: "1.0.0"
  description: |
    HTTP service that signs PropAMM quote payloads for EVM verification flows
    and serves a live on-chain orderbook for configured token pairs.
  license:
    name: MIT

servers:
  - url: https://propamm.kipseli.win
    description: Production

security:
  - ApiKeyAuth: []

paths:
  /v2/swap/sign:
    post:
      operationId: signQuote
      summary: Sign a quote
      description: |
        ABI-encodes `(tokenIn, tokenOut, timestamp, fee, originRate, userAddress)`,
        Keccak256-hashes the payload, and signs it with secp256k1.
        The recovery ID is adjusted from `[0,1]` to `[27,28]` per the Ethereum Yellow Paper.

        The response contains ABI-encoded verification data `(fee, originRate, userAddress, signature)`
        ready for on-chain submission.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignQuoteRequest"
            examples:
              minimal:
                summary: Sign without calldata
                value:
                  tokenIn: "0x4200000000000000000000000000000000000006"
                  tokenOut: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                  fee: "3000"
                  userAddress: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
              with_calldata:
                summary: Sign and get swap calldata (amountIn provided)
                value:
                  tokenIn: "0x4200000000000000000000000000000000000006"
                  tokenOut: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                  fee: "3000"
                  userAddress: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
                  amountIn: "1000000000000000000"
                  minAmountOut: "3150000000"
      responses:
        "200":
          description: Quote signed successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignQuoteResponse"
              examples:
                without_calldata:
                  summary: Response without calldata
                  value:
                    verificationData: "0xabcdef..."
                    timestamp: 1741737600
                with_calldata:
                  summary: Response with calldata (amountIn was provided)
                  value:
                    verificationData: "0xabcdef..."
                    timestamp: 1741737600
                    calldata: "0x..."
        "401":
          description: Unauthorized — missing or invalid `X-API-KEY` header
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error:
                  code: UNAUTHORIZED
                  message: missing or invalid API key
                  requestId: "abc-123"
        "400":
          description: Bad request — invalid or unknown fields in JSON body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_json:
                  summary: Empty or malformed body
                  value:
                    error:
                      code: INVALID_JSON
                      message: request body is required
                      requestId: "abc-123"
                unsupported_field:
                  summary: Unknown field in body
                  value:
                    error:
                      code: UNSUPPORTED_FIELD
                      message: unknown field in request payload
                      requestId: "abc-123"
        "422":
          description: Unprocessable entity — validation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: "tokenIn: invalid EVM address"
                  requestId: "abc-123"
        "500":
          description: Internal server error — signing or encoding failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                signing_failed:
                  summary: Signing failed
                  value:
                    error:
                      code: SIGNING_FAILED
                      message: failed to sign quote
                      requestId: "abc-123"
                encoding_failed:
                  summary: Encoding failed
                  value:
                    error:
                      code: ENCODING_FAILED
                      message: failed to encode verification data
                      requestId: "abc-123"

  /v2/price:
    get:
      operationId: getPrice
      summary: Get live orderbook
      description: |
        Returns the latest cached on-chain orderbook for all configured token pairs.
        The cache refreshes on every new block via `eth_subscribe`, so the data always
        reflects the most recent chain state.

        Each pair has a `baseToken` (non-USDC token, e.g. WETH) and a `quoteToken` (USDC).
        Prices are expressed as **USDC per base token** (decimal strings).
        Amounts are **human-readable base token quantities** (decimal strings).

        Levels are **marginal** — each level is an independent price point, not cumulative depth.
        `bids` are sorted descending (best bid first); `asks` are sorted ascending (best ask first).
      responses:
        "200":
          description: Current orderbook snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PriceResponse"
              examples:
                weth_cbbtc:
                  summary: Two pairs — WETH/USDC and cbBTC/USDC
                  value:
                    blockNumber: 29500000
                    blockTime: 1741737600
                    pairs:
                      - baseToken: "0x4200000000000000000000000000000000000006"
                        quoteToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                        bids:
                          - price: "3200.50"
                            amount: "1.0"
                          - price: "3198.00"
                            amount: "5.0"
                          - price: "3190.25"
                            amount: "10.0"
                        asks:
                          - price: "3201.75"
                            amount: "1.0"
                          - price: "3204.00"
                            amount: "5.0"
                          - price: "3210.50"
                            amount: "10.0"
                      - baseToken: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"
                        quoteToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                        bids:
                          - price: "97500.00"
                            amount: "0.01"
                          - price: "97200.00"
                            amount: "0.05"
                        asks:
                          - price: "97600.00"
                            amount: "0.01"
                          - price: "97900.00"
                            amount: "0.05"
                empty_cache:
                  summary: Cache not yet populated (service just started)
                  value:
                    blockNumber: 0
                    blockTime: 0
                    pairs: []
        "401":
          description: Unauthorized — missing or invalid `X-API-KEY` header
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error:
                  code: UNAUTHORIZED
                  message: missing or invalid API key
                  requestId: "abc-123"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error:
                  code: INTERNAL_ERROR
                  message: internal server error
                  requestId: "abc-123"

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: API key issued by the Kipseli team. Required on every request.

  schemas:
    SignQuoteRequest:
      type: object
      required:
        - tokenIn
        - tokenOut
        - fee
        - userAddress
      properties:
        tokenIn:
          type: string
          description: EVM hex address of the input token (checksummed or lowercase)
          example: "0x4200000000000000000000000000000000000006"
        tokenOut:
          type: string
          description: EVM hex address of the output token (checksummed or lowercase)
          example: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
        fee:
          type: string
          description: Fee tier as a decimal string in 0.1 bps resolution(e.g. "3000" = 3%)
          example: "3000"
        userAddress:
          type: string
          description: EVM hex address of the user submitting the swap
          example: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
        amountIn:
          type: string
          description: |
            Input token amount in wei as a decimal string. When provided, the response
            will include a `calldata` field with ABI-encoded calldata for the `swap` function.
          example: "1000000000000000000"
        minAmountOut:
          type: string
          description: |
            Minimum accepted output token amount in wei as a decimal string.
            Only used when `amountIn` is provided. Defaults to `0` if omitted.
          example: "3150000000"
      additionalProperties: false

    SignQuoteResponse:
      type: object
      properties:
        verificationData:
          type: string
          description: |
            ABI-encoded hex string (with `0x` prefix) containing
            `(fee, originRate, userAddress, signature)`. Pass this directly
            to the on-chain verification contract.
          example: "0xabcdef..."
        timestamp:
          type: integer
          format: int64
          description: Unix timestamp (seconds) at which the signature was generated
          example: 1741737600
        calldata:
          type: string
          description: |
            ABI-encoded calldata (with `0x` prefix) for the `swap(address,uint256,address,uint256,uint256,bytes)`
            function. Only present when `amountIn` was provided in the request.
            The encoded arguments are `(tokenIn, amountIn, tokenOut, minAmountOut, quoteTimestamp, verificationData)`.
          example: "0x..."

    OrderbookLevel:
      type: object
      description: A single marginal price/amount level in the orderbook
      properties:
        price:
          type: string
          description: |
            USDC per base token as a decimal string. Returned as a string (not a float)
            to preserve precision for EVM-scale values.
          example: "3200.50"
        amount:
          type: string
          description: |
            Human-readable base token quantity available at this price level, as a decimal string.
            This is the marginal amount at this specific price point, not cumulative depth.
          example: "1.0"

    PairOrderbook:
      type: object
      description: Full bid/ask orderbook for one base token / USDC pair
      properties:
        baseToken:
          type: string
          description: |
            EVM hex address of the non-USDC token (e.g. WETH, cbBTC).
            This is the token whose price is quoted in USDC.
          example: "0x4200000000000000000000000000000000000006"
        quoteToken:
          type: string
          description: EVM hex address of the USDC token used as the price denomination
          example: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
        bids:
          type: array
          description: |
            Buy-side levels — prices at which the AMM will buy the base token (you sell base, receive USDC).
            Sorted by **descending** price (best bid first).
          items:
            $ref: "#/components/schemas/OrderbookLevel"
        asks:
          type: array
          description: |
            Sell-side levels — prices at which the AMM will sell the base token (you buy base, pay USDC).
            Sorted by **ascending** price (best ask first).
          items:
            $ref: "#/components/schemas/OrderbookLevel"

    PriceResponse:
      type: object
      properties:
        blockNumber:
          type: integer
          format: int64
          description: |
            The on-chain block number at which the orderbook was last refreshed.
            Updates on every new block. Zero if the cache has not been populated yet.
          example: 29500000
        blockTime:
          type: integer
          format: int64
          description: |
            Unix timestamp (seconds) of the block identified by `blockNumber`.
            Zero if the cache has not been populated yet.
          example: 1741737600
        pairs:
          type: array
          description: |
            Orderbook for each configured token/USDC pair. Empty if the cache has
            not been populated yet (e.g. service just started).
          items:
            $ref: "#/components/schemas/PairOrderbook"

    ErrorDetail:
      type: object
      properties:
        code:
          type: string
          description: Machine-readable error code
          enum:
            - INVALID_JSON
            - VALIDATION_ERROR
            - UNSUPPORTED_FIELD
            - UNAUTHORIZED
            - SIGNING_FAILED
            - ENCODING_FAILED
            - INTERNAL_ERROR
          example: VALIDATION_ERROR
        message:
          type: string
          description: Human-readable error message
          example: "tokenIn: invalid EVM address"
        requestId:
          type: string
          description: Request ID for correlation with server logs
          example: "abc-123"

    ErrorResponse:
      type: object
      properties:
        error:
          $ref: "#/components/schemas/ErrorDetail"
