{"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"]}}}}