# StablecoinX Treasury — Full reference > Extended reference for LLM retrieval. See [llms.txt](https://treasury.harness.stablecoinx.com/llms.txt) for the short summary, and the OpenAPI 3.x spec at [https://treasury-api.harness.stablecoinx.com/openapi.json](https://treasury-api.harness.stablecoinx.com/openapi.json) for the typed contract. ## Product StablecoinX Treasury is a non-custodial on-chain dashboard for stablecoin holders. The frontend (React SPA at `https://treasury.harness.stablecoinx.com`) reads chain state via the backend treasury-api and lets users execute deposits and withdrawals through ERC-4337 smart accounts with sponsored gas. Nothing is custodied — the dashboard tracks user-supplied wallet addresses and every state-changing action is signed by the user's smart-account wallet. Two distinct execution flows: 1. **Read-side aggregation** — given a list of wallet addresses + a `NetworkMode` (`mainnet` / `testnet` / `both`), the backend aggregates ERC-20 stablecoin balances and yield-vault positions across every chain it knows about, computes USD-denominated totals and per-position P&L, and returns a snapshot. The `/v1/treasury/aggregated` endpoint is ETag-aware so the dashboard can poll cheaply. 2. **Write-side actions** — put-to-work (deposit idle stablecoins into a yield vault) and cash-out (redeem a vault position back to stablecoins) are quoted server-side via LI.FI's routing API for arbitrary EVM vault catalogue, with dedicated adapters for Ethena sUSDe (instant ERC-4626 `redeem` or two-phase `cooldownShares` → `unstake`) and the floYSH vault. The returned calldata is signed by the user's smart account; gas is sponsored via the StablecoinX paymaster (EntryPoint v0.7). ## API base - Production: `https://treasury-api.harness.stablecoinx.com` - Dev: `https://treasury-api.dev.scx.easeflow.io` - OpenAPI: `/openapi.json`, `/openapi.yaml`, `/openapi` (Swagger UI) - All endpoints are public — no authentication. - Global rate limit: 120 req/min/IP (`@nestjs/throttler` default). - `POST /v1/treasury/wallets` overrides to 30 req/min/IP (each call can trigger a 90-day multi-chain HyperSync backfill). - Errors: standard NestJS HTTP envelope `{"statusCode":N,"message":"...","error":"..."}`. ## Endpoints — `/v1/treasury/*` (wallet lifecycle + aggregate snapshot) ### `POST /v1/treasury/wallets` — register a wallet Body: `{"address":"0x…","mode":"mainnet"|"testnet"|"both"}`. Returns `202` with `{wallet:{id,address}, status:"syncing"|"ready"}`. Idempotent: re-adding an already-onboarded wallet returns `status:"ready"` and kicks a background incremental sync; first-time wallets get `status:"syncing"` and a 90-day full backfill runs out-of-band. Poll `/wallets/{address}/sync-status` for progress. ### `GET /v1/treasury/wallets/{address}/sync-status` — onboarding progress Returns `{status:"unknown"|"pending"|"running"|"done"|"error", kind?:"onboarding"|"incremental", progressPct?:number, message?:string|null, updatedAt?:string}`. `unknown` means the address has never been registered. ### `POST /v1/treasury/wallets/{address}/refresh` — force incremental sync Bypasses the 60s cooldown. Used by deposit/withdraw dialogs after a confirmed on-chain tx so the dashboard reflects the new position within ~5s. Returns `202` with `{status:"syncing"}`. ### `POST /v1/treasury/aggregated` — read aggregate snapshot (ETag-aware) Body: `{"wallets":["0x…", …], "mode":"mainnet"|"testnet"|"both"}`. Sends `If-None-Match: ` to get 304 when unchanged; otherwise returns the full `AggregatedResult` payload with `ETag` + `Last-Modified` headers. Used by the dashboard's 10s polling loop. Snapshots are computed from the backend's `wallet_holdings` table (synced from on-chain via HyperSync) and re-aggregated when invalidated by a balance change. ## Endpoints — `/v1/dashboard/*` (history, P&L, action quotes, redeem calldata) ### `POST /v1/dashboard/history` — USD value history Body: `{"wallets":[…], "days":N (default 90, max 365), "mode":…}`. Returns daily USD value points across the requested window. ### `POST /v1/dashboard/pnl` — realised + unrealised P&L Body: same shape as `/history` but defaults to `days:30`. Returns per-position and aggregate P&L over the window. ### `POST /v1/dashboard/put-to-work` — deposit quote Body shape: `{wallets, mode, amount, fromToken, toVault, chainId}` (see OpenAPI). Returns a LI.FI-routed quote with the calldata to deposit `amount` of `fromToken` into the destination vault. ### `POST /v1/dashboard/cash-out` — withdraw quote Body: similar to put-to-work but inverted. Returns route + calldata to redeem from the vault back to a stablecoin. ### `POST /v1/dashboard/redeem-direct` — vanilla ERC-4626 redeem Body: `{"chainId":N,"vaultAddress":"0x…","shares":"…","receiver":"0x…"}`. Returns direct `redeem(shares,receiver,owner)` calldata. Used by CashOutDialog when LI.FI doesn't support a vault but it implements EIP-4626. ### `POST /v1/dashboard/redeem-cooldown-start` — Ethena two-phase exit, phase 1 Body: `{"chainId":N,"vaultAddress":"0x…","shares":"…"}`. Returns calldata for `cooldownShares(shares)` on the Ethena sUSDe contract. After the cooldown duration the user can call phase 2. ### `POST /v1/dashboard/redeem-cooldown-claim` — Ethena two-phase exit, phase 2 Body: `{"chainId":N,"vaultAddress":"0x…","receiver":"0x…"}`. Returns calldata for `unstake(receiver)`. The dialog gates the button on the live `state==="claimable"` from `/redeem-cooldown-state`. ### `GET /v1/dashboard/redeem-cooldown-state?wallet={addr}` — Ethena cooldown state Returns `CooldownState | null` for the wallet. `null` when RPC is down — the dialog then falls back to a direct Ethena UI link. ### `GET /v1/dashboard/config` — frontend bootstrap Returns supported chains, stablecoin list (from the live `stablecoins` table, sourced from LI.FI's vault catalog), floYSH deeplink URL (overridable via `FLOYSH_WEB_URL`), live floYSH vault APY (`null` when the Hasura feed is unreachable or hasn't accrued yield this window). ## Health - `GET /health` → `{"status":"ok","service":"treasury-api"}` (liveness). ## Architecture notes (for agents picking integration shape) - **Snapshot lifecycle:** `wallet_holdings` is the source of truth, fed by `TransferSyncService` (HyperSync). Aggregate snapshots are recomputed on invalidation (deposit/withdraw, new vault token discovered, watermark advance). History snapshots are kept in the DB and serve stale-while-revalidate. - **Two-tier refresh on `/aggregated`:** inline `quickRefresh` (balanceOf multicall, every 10s) for hot-path token-balance updates, plus background `runIncremental` (full HyperSync delta, every 60s) as a safety net to discover new vault tokens the user just acquired. - **Smart account fallback:** the UI uses thirdweb's in-app wallet with `entrypointAddress: ENTRYPOINT_V7` and our SingletonPaymasterV7 peer for sponsorship. On chains where our V7 peer isn't deployed yet, it falls back to thirdweb's default sponsorship transparently — the user sees no error, just a different gas-payment path. ## StablecoinX MCP server Programmatic, agent-friendly control of a StablecoinX merchant account. Published to npm as [`@stablecoinx/mcp`](https://www.npmjs.com/package/@stablecoinx/mcp); source at https://github.com/e2xlabs/stablecoinx-mcp; registered in the MCP registry as `io.github.e2xlabs/stablecoinx-mcp`. - **Install (Claude Code and clients with an `mcp add` CLI):** `npx @stablecoinx/mcp setup` — installs the bundled `stablecoinx` skill into `~/.claude/skills` and registers the MCP (user scope) with harness defaults and a freshly generated state passphrase. Restart your MCP client afterwards. - **Manual registration:** ``` claude mcp add stablecoinx-mcp -s user \ -e SCX_API_URL=https://api.harness.stablecoinx.com \ -e SCX_CHAIN_ID=84532 \ -e THIRDWEB_CLIENT_ID=830ece55cd210c34f351166a85edbd0f \ -e THIRDWEB_ORIGIN=https://business.harness.stablecoinx.com \ -e SCX_STATE_PASSPHRASE="$(openssl rand -hex 16)" \ -- npx -y @stablecoinx/mcp ``` Requires Node.js 20+. `THIRDWEB_CLIENT_ID` is a public, publishable identifier — not a secret. - **Transport:** local stdio server launched via `npx`; all credentials stay on the local machine, encrypted at rest. There is no hosted/remote MCP endpoint. - **Networks (`SCX_CHAIN_ID`):** `84532` (Base Sepolia, default), `421614` (Arbitrum Sepolia), `11155111` (Ethereum Sepolia). - **Auth:** thirdweb email-OTP → 24h JWT cached in the encrypted local state file. Run `auth_send_otp` then `auth_verify_otp`; mint an `sk_*` with `api_key_create` for server-to-server session creation. - **Tools (27):** auth & onboarding (`auth_status`, `auth_send_otp`, `auth_verify_otp`, `auth_logout`, `api_key_create`, `session_key_create`); merchant profile (`merchant_get`, `merchant_update`); API keys (`api_keys_list`, `api_keys_revoke`); sessions (`sessions_list`, `sessions_get`, `sessions_create_dashboard`, `sessions_create_s2s`); paymaster clients (`paymaster_clients_list` / `_create` / `_update` / `_revoke`); paymaster allowlist (`paymaster_allowlist_list` / `_add` / `_remove`); paymaster usage (`paymaster_usage_get`); webhooks (`webhooks_create` / `_list` / `_delete` / `_rotate_secret` / `_deliveries`). ## Security - Vulnerability disclosure: `tech@e2xlabs.com` (see [/.well-known/security.txt](https://treasury.harness.stablecoinx.com/.well-known/security.txt)). - Non-custodial — no funds held by the dashboard; all transactions are signed by the user's wallet. - Read-side API endpoints accept user-supplied wallet addresses without auth; no private data is stored beyond on-chain-derived state. ## Caveats - USD valuations depend on price oracle data freshness; momentary divergence from live AMM prices is possible. - P&L is approximate over long windows; the linear-approximation comment in `history.service.ts:612` notes a <0.5% drift over 90 days vs. proper compounding. - Yield APYs are surfaced from each protocol's published feed; not independently audited by StablecoinX. - LI.FI vault coverage drives which protocols appear in the dashboard. When LI.FI doesn't list a vault but it implements EIP-4626, the `/redeem-direct` path still works.