openapi: 3.0.0
paths:
  /health:
    get:
      operationId: HealthController_check
      summary: Liveness probe.
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponseDto'
      tags:
        - Health
  /v1/dashboard/history:
    post:
      operationId: DashboardController_history_
      parameters: []
      responses:
        '201':
          description: ''
      tags:
        - Dashboard
  /v1/dashboard/pnl:
    post:
      operationId: DashboardController_pnlEndpoint
      parameters: []
      responses:
        '201':
          description: ''
      tags:
        - Dashboard
  /v1/dashboard/put-to-work:
    post:
      operationId: DashboardController_putToWork
      parameters: []
      responses:
        '201':
          description: ''
      tags:
        - Dashboard
  /v1/dashboard/cash-out:
    post:
      operationId: DashboardController_cashOut
      parameters: []
      responses:
        '201':
          description: ''
      tags:
        - Dashboard
  /v1/dashboard/redeem-direct:
    post:
      operationId: DashboardController_redeemDirect
      summary: Vanilla ERC-4626 `redeem(shares, receiver, owner)` calldata.
      description: >-
        Used by CashOutDialog when LI.FI doesn't catalog a vault but it
        implements EIP-4626 (which most do).
      parameters: []
      responses:
        '201':
          description: ''
      tags:
        - Dashboard
  /v1/dashboard/redeem-cooldown-start:
    post:
      operationId: DashboardController_cooldownStart
      summary: 'Ethena two-phase exit — phase 1: cooldownShares(shares) calldata.'
      parameters: []
      responses:
        '201':
          description: ''
      tags:
        - Dashboard
  /v1/dashboard/redeem-cooldown-claim:
    post:
      operationId: DashboardController_cooldownClaim
      summary: 'Ethena two-phase exit — phase 2: unstake(receiver) calldata.'
      description: >-
        Gate the action on the live state from /redeem-cooldown-state
        (`claimable`) before signing.
      parameters: []
      responses:
        '201':
          description: ''
      tags:
        - Dashboard
  /v1/dashboard/redeem-cooldown-state:
    get:
      operationId: DashboardController_cooldownState
      summary: >-
        Live Ethena cooldown state for a wallet — null when RPC is down (UI
        falls back to a direct Ethena UI link).
      parameters:
        - name: wallet
          required: true
          in: query
          description: EVM address
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CooldownStateDto'
      tags:
        - Dashboard
  /v1/dashboard/config:
    get:
      operationId: DashboardController_config
      summary: >-
        Frontend bootstrap — supported chains, stablecoin list, floYSH deeplink
        + APY.
      description: >-
        Stablecoin list comes from the live `stablecoins` table (StableSync
        keeps it in sync with LI.FI's vault catalog). `floyshApy` is `null` when
        the ysh-web Hasura feed is unreachable or hasn't accrued yield this
        window.
      parameters: []
      responses:
        '200':
          description: ''
      tags:
        - Dashboard
  /v1/treasury/wallets:
    post:
      operationId: TreasuryController_addWallet
      summary: >-
        Register a wallet — idempotent, kicks 90-day backfill for first-time
        addresses.
      description: >-
        Re-adding an already-onboarded wallet returns {status:'ready'} + a
        background incremental sync. Tighter 30/min/IP throttle than the
        120/min/IP global default (each first-time call triggers a 90-day
        multi-chain HyperSync backfill).
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddWalletBodyDto'
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddWalletResponseDto'
        '400':
          description: address must be a valid 0x… EVM address
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
      tags:
        - Treasury
  /v1/treasury/wallets/{address}/refresh:
    post:
      operationId: TreasuryController_refreshWallet
      summary: Force incremental sync NOW (bypasses 60s cooldown).
      description: >-
        Used by deposit/withdraw dialogs after on-chain confirmation so the
        dashboard reflects the new position within ~5s rather than waiting for
        the next poll cycle.
      parameters:
        - name: address
          required: true
          in: path
          description: >-
            EVM address (0x + 40 hex chars). Must be previously registered via
            POST /v1/treasury/wallets.
          schema:
            type: string
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefreshResponseDto'
        '400':
          description: Invalid address or wallet not registered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
      tags:
        - Treasury
  /v1/treasury/wallets/{address}/sync-status:
    get:
      operationId: TreasuryController_syncStatus
      summary: Onboarding / incremental sync progress for a registered wallet.
      parameters:
        - name: address
          required: true
          in: path
          description: EVM address. Unknown addresses return {status:'unknown'}.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncStatusResponseDto'
      tags:
        - Treasury
  /v1/treasury/aggregated:
    post:
      operationId: TreasuryController_aggregated
      summary: Aggregate snapshot across the supplied wallets — ETag-aware.
      description: >-
        Body lists wallet addresses + network mode. Send If-None-Match with the
        prior ETag to get 304 when the snapshot hasn't changed (the 10s polling
        hot path). Otherwise returns the full AggregatedResult payload with ETag
        + Last-Modified headers.
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AggregatedRequestDto'
      responses:
        '200':
          description: >-
            Full AggregatedResult — schema is large and per-protocol; see
            /openapi.yaml for the inline shape.
        '304':
          description: Snapshot unchanged since If-None-Match. Body is empty.
      tags:
        - Treasury
info:
  title: StablecoinX Treasury API
  description: >-
    Backend for the StablecoinX Treasury dashboard. Aggregates on-chain
    stablecoin balances and yield positions across EVM chains, computes history
    and P&L, and prices put-to-work / cash-out flows via LI.FI + EIP-4626 vault
    adapters (Aave, Compound, Ethena sUSDe, floYSH).
  version: '1.0'
  contact: {}
tags:
  - name: Treasury
    description: >-
      Wallet registration, snapshot-backed aggregate view (ETag-aware), sync
      status
  - name: Dashboard
    description: History, P&L, quotes (put-to-work / cash-out), redeem calldata, config
  - name: Health
    description: Liveness
servers:
  - url: https://treasury-api.harness.stablecoinx.com
    description: Production
  - url: https://treasury-api.dev.scx.easeflow.io
    description: Dev
components:
  schemas:
    HealthResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - ok
        service:
          type: string
          enum:
            - treasury-api
      required:
        - status
        - service
    CooldownStateDto:
      type: object
      properties:
        state:
          type: string
          enum:
            - none
            - pending
            - claimable
            - ready
        cooldownEnd:
          type: integer
        assets:
          type: string
      required:
        - state
    AddWalletBodyDto:
      type: object
      properties:
        address:
          type: string
          pattern: ^0x[0-9a-fA-F]{40}$
        mode:
          type: string
          enum:
            - mainnet
            - testnet
            - both
      required:
        - address
    AddWalletResponseDto:
      type: object
      properties:
        wallet:
          type: object
          properties:
            id:
              type: integer
            address:
              type: string
              pattern: ^0x[0-9a-fA-F]{40}$
          required:
            - id
            - address
        status:
          type: string
          enum:
            - syncing
            - ready
      required:
        - wallet
        - status
    ErrorResponseDto:
      type: object
      properties:
        statusCode:
          type: integer
        message:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        error:
          type: string
      required:
        - statusCode
        - message
    RefreshResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - syncing
      required:
        - status
    SyncStatusResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - unknown
            - pending
            - running
            - done
            - error
        kind:
          type: string
          enum:
            - onboarding
            - incremental
        progressPct:
          type: number
          minimum: 0
          exclusiveMinimum: false
          maximum: 100
          exclusiveMaximum: false
        message:
          type: string
          nullable: true
        updatedAt:
          type: string
          format: date-time
      required:
        - status
    AggregatedRequestDto:
      type: object
      properties:
        wallets:
          type: array
          minItems: 1
          items:
            type: string
            pattern: ^0x[0-9a-fA-F]{40}$
        mode:
          type: string
          enum:
            - mainnet
            - testnet
            - both
      required:
        - wallets
