> ## Documentation Index
> Fetch the complete documentation index at: https://docs.webacy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Vault Risk Intelligence

> Continuous DeFi vault risk scoring across six chains — composite scores, listing verdicts, real-time withdrawal signals, and 90-day history.

Webacy continuously scores thousands of DeFi vaults across Ethereum, Arbitrum, Base, Optimism, Polygon, and BSC (with more coverage coming soon). Every vault gets a composite risk score, a listing verdict, real-time withdrawal signals, and a 90-day history.

<Note>
  Want vault risk coverage on a chain that's not listed yet? [Contact the team](mailto:info@webacy.com) — we're actively expanding chain coverage.
</Note>

# Risk Tiers

<Frame>
  <img src="https://mintcdn.com/webacy/nHZ00OBCBxHAq-vy/images/Screenshot-2026-04-13-at-11.51.17-AM.png?fit=max&auto=format&n=nHZ00OBCBxHAq-vy&q=85&s=d55b0cc89038753dc9e4cb82a25f89d4" alt="Screenshot 2026 04 13 At 11 51 17 AM" width="1848" height="274" data-path="images/Screenshot-2026-04-13-at-11.51.17-AM.png" />
</Frame>

# Letter Grades

Each vault receives a letter grade derived directly from its composite risk score on a standard 11-band scale. It is a **risk rating**: lower risk earns a better grade. **A+ is the lowest risk**, **F is the highest risk**. The grade always points the conventional way (A+ is best), while the underlying `vault_score` is a risk magnitude where higher means worse. Bands are tighter at the safe end and widen as risk rises.

| Grade                           | Risk score | Grade                           | Risk score |
| :------------------------------ | :--------- | :------------------------------ | :--------- |
| <span class="gb gb-a">A+</span> | 0–5        | <span class="gb gb-c">C+</span> | 47–56      |
| <span class="gb gb-a">A</span>  | 6–12       | <span class="gb gb-c">C</span>  | 57–66      |
| <span class="gb gb-a">A-</span> | 13–20      | <span class="gb gb-c">C-</span> | 67–77      |
| <span class="gb gb-b">B+</span> | 21–28      | <span class="gb gb-d">D</span>  | 78–88      |
| <span class="gb gb-b">B</span>  | 29–37      | <span class="gb gb-f">F</span>  | 89–100     |
| <span class="gb gb-b">B-</span> | 38–46      |                                 |            |

The `vault_grade` field is available on all vault records alongside the numeric `vault_score` (0–100).

# Listing Verdict

Every vault receives a `listing_verdict` — a clear signal for teams deciding whether to integrate, list, or accept a vault as collateral. It's computed from the composite risk score plus hard blocking conditions, updated with every data refresh.

<Frame>
  <img src="https://mintcdn.com/webacy/nHZ00OBCBxHAq-vy/images/Screenshot-2026-04-13-at-11.47.08-AM.png?fit=max&auto=format&n=nHZ00OBCBxHAq-vy&q=85&s=b6d9b28e89f5bca9e812e56c566980e0" alt="Screenshot 2026 04 13 At 11 47 08 AM" width="1802" height="544" data-path="images/Screenshot-2026-04-13-at-11.47.08-AM.png" />
</Frame>

Available via `GET /api/vaults` → `listing_verdict` field on each vault item.

# Withdrawal Risk

The `withdrawal_risk` field tells you how freely users can exit a vault right now. For lending vaults, utilization is fetched live every \~5 minutes, so this signal reflects actual on-chain liquidity — not yesterday's data.

<Frame>
  <img src="https://mintcdn.com/webacy/nHZ00OBCBxHAq-vy/images/Screenshot-2026-04-13-at-11.47.39-AM.png?fit=max&auto=format&n=nHZ00OBCBxHAq-vy&q=85&s=ae5f9e5f46c25cab0e8bb86ed353e8e9" alt="Screenshot 2026 04 13 At 11 47 39 AM" width="1824" height="536" data-path="images/Screenshot-2026-04-13-at-11.47.39-AM.png" />
</Frame>

# Real-Time Data Signals

Several signals are fetched live at request time and overlaid on top of nightly pipeline data. This means the dashboard reflects current on-chain conditions, not just yesterday's snapshot.

| **Signal**                          | **Source**                       | **Freshness** | **Coverage**                               |
| :---------------------------------- | :------------------------------- | :------------ | :----------------------------------------- |
| **Share price**                     | Live market price feed           | \~5 min       | \~18% of vaults (those with DEX liquidity) |
| **Lending market utilization rate** | On-chain market data             | \~5 min       | All lending vaults                         |
| **Vault score (utilization)**       | Recomputed from live utilization | \~5 min       | All lending vaults                         |
| **Withdrawal risk level**           | Live utilization + stored flags  | \~5 min       | All vaults                                 |

**Price indicator:** A green dot (●) next to a share price means a live market quote was available. A grey dot means the price is from the last nightly pipeline run. Prices above \$500 or equal to zero are treated as bad data and shown as —.

# Score History & Trends

Every vault detail page includes a 90-day score sparkline so you can see whether a vault is improving, degrading, or stable over time. The delta badge shows the score change vs 30 days ago.

**Each daily snapshot records:**

• Risk score (0–100)

• Tier (low / medium / high / critical)

• Active risk flags

• Share price

• Looping exposure %

History endpoint: `GET /api/vaults/[chain:address]/history` — returns up to 90 snapshots.

# TVL & Share Price History

Two dedicated endpoints expose daily UTC-midnight samples for the chart on the vault detail page. Both accept a `range` of `7d`, `30d`, `60d`, or `3m` (defaulting to `30d`) and hoist the most recent point to a `latest` aggregate so you can render current stats without paging the full series. The `stale` flag flips `true` when the latest sample is older than 48 hours or the series is empty.

| **Endpoint**                                                                                           | **Per-point fields**                                       | **`latest` adds**  | **Notes**                                                                                                            |
| :----------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :----------------- | :------------------------------------------------------------------------------------------------------------------- |
| [`GET /vaults/{address}/tvl-history`](/api-reference/vault-risk/get-vault-tvl-history)                 | `ts`, `tvl_usd`, `quality_flag`                            | —                  | Total value locked over time                                                                                         |
| [`GET /vaults/{address}/share-price-history`](/api-reference/vault-risk/get-vault-share-price-history) | `ts`, `share_price_usd`, `apy_trailing_7d`, `quality_flag` | `apy_trailing_30d` | 7-day APY catches spikes / depegs immediately; 30-day APY is the smoother headline number, surfaced on `latest` only |

Per-point `apy_trailing_7d` is annualised against the sample 7 days earlier (`(price[t] / price[t-7d])^(365/7) - 1`) and returns `null` when the look-back sample is missing or the absolute APY exceeds 100 (clamped to suppress single-day pricing artifacts). Both endpoints return `404` when the vault is not in the verified-vaults catalog; tracked vaults with no samples yet still return `200` with `count: 0`, `latest: null`, and `stale_reason: 'no_samples_yet'`.

Available in the SDK as [`client.vaults.getTvlHistory()`](/sdk/threat-client#get-vault-tvl-history) and [`client.vaults.getSharePriceHistory()`](/sdk/threat-client#get-vault-share-price-history).

## Quality filtering

Each sample carries a `quality_flag` that captures whether the upstream producer trusted the underlying reading. By default, the chart endpoints drop samples flagged as `capped`, `diverged`, or `spike` so a single bad pricing artifact never appears as a misleading peak or trough in the line — the BSC USDX 92× over-report that prompted this change is the canonical example.

| `quality_flag` | What it means                                                                         | In default response                                                 |
| :------------- | :------------------------------------------------------------------------------------ | :------------------------------------------------------------------ |
| `ok`           | Producer-verified clean reading.                                                      | ✅ Returned                                                          |
| `unknown`      | Legacy row or sample that predates flag derivation.                                   | ✅ Returned (kept in the allowlist until backfill rollout completes) |
| `capped`       | Reading clamped at a hard ceiling — treat as a lower bound.                           | ❌ Dropped                                                           |
| `diverged`     | Multi-source readings disagreed materially.                                           | ❌ Dropped                                                           |
| `spike`        | Single-sample jump exceeded the 5× day-over-day threshold; likely a pricing artifact. | ❌ Dropped                                                           |

When samples in the window are dropped, the response is **non-silent**: the envelope reports `count < days` plus `filtered_count: N` and every returned point still carries its `quality_flag`. A visible chart kink that lines up with `filtered_count` is normal and **does not** indicate a missed cron tick. The `stale_reason` enum disambiguates the four states behind `stale: true` so consumers can render the right empty-state copy:

| `stale_reason`   | `stale` | When it fires                                                                                  |
| :--------------- | :------ | :--------------------------------------------------------------------------------------------- |
| `fresh`          | `false` | Latest passing sample is within 48 hours.                                                      |
| `pipeline_lag`   | `true`  | Latest passing sample is older than 48 hours.                                                  |
| `all_filtered`   | `true`  | Every sample in the window was dropped by the quality filter — distinct from "no samples yet". |
| `no_samples_yet` | `true`  | Vault is tracked but has no samples in the window.                                             |

Pass `?includeFlagged=true` to bypass the allowlist and receive the full raw series with every sample's `quality_flag` populated — useful when researching the cause of a flagged event or building a custom marker overlay. `filtered_count` is always `0` in that mode.

The response envelope also carries `schema_version` (currently `s3`); pin it if you're building a regression harness so additive shape changes don't fail your assertions silently.

# Share Price & Depeg Signals

`last_share_price` is the vault token price in USD. For yield-bearing vaults — sDAI, sUSDe, Yearn, Fluid — the share price legitimately drifts above \$1 as yield accumulates. This is expected behaviour, not a risk signal. Depeg flags only fire when the price falls meaningfully *below* par.

<Frame>
  <img src="https://mintcdn.com/webacy/nHZ00OBCBxHAq-vy/images/Screenshot-2026-04-13-at-11.48.41-AM.png?fit=max&auto=format&n=nHZ00OBCBxHAq-vy&q=85&s=c662ed3488680157cc507e07a5356919" alt="Screenshot 2026 04 13 At 11 48 41 AM" width="1846" height="240" data-path="images/Screenshot-2026-04-13-at-11.48.41-AM.png" />
</Frame>

# [RISK SCORE METHODOLOGY ↗️](/vault-ratings)

# Monitoring Status

Each vault is assigned a `monitoring_status` derived from TVL and trend data. Legacy and deprecated vaults are hidden by default in the dashboard (toggle with 'Show legacy (N)').

| **Status**   | **Condition**                                | **What it means**                           |
| :----------- | :------------------------------------------- | :------------------------------------------ |
| `active`     | TVL ≥ \$500K and no severe trend decline     | Fully monitored and shown by default        |
| `watch`      | TVL \$100K–\$500K OR TVL dropped >50% in 90d | At-risk; shown by default but flagged       |
| `legacy`     | TVL \< \$100K                                | Effectively dead capital; hidden by default |
| `deprecated` | Protocol officially shut down                | Hidden by default                           |

# Actionability Class

Every vault output includes an `actionability_class` that identifies the dominant risk driver and returns a targeted one-line action recommendation. The class with the highest weighted average sub-score wins.

| **Class**           | **Key sub-scores**                                                                                 | **Example action**                                                    |
| :------------------ | :------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| `smart_contract`    | Protocol risk (40%), Code (20%), Audit recency (10%), Oracle (10%), Asset (10%), Webacy code (10%) | "Reduce position: elevated code or protocol risk warrants caution."   |
| `liquidity_lock`    | Closed liquidity (50%), Utilization (30%), Utilisation risk (10%), Exit liquidity (10%)            | "Exit now: liquidity is critically constrained."                      |
| `governance`        | Centralization (40%), Upgrade (30%), Strategy (15%), Governance behavior (15%)                     | "Reduce position: centralization or upgrade risk is elevated."        |
| `market_conditions` | Depeg (35%), Looping (25%), TVL outflow (20%), Size (10%), Maturity (10%)                          | "Reduce exposure: market or collateral conditions are deteriorating." |

Action verb (*monitor / review / plan / watch / reduce / exit*) is selected by combining class × tier. The `actionability_class`, `actionability_action`, `actionability_detail`, and `actionability_class_scores` fields are all available in the API output.

# Exchange Rate Velocity

ERC-4626 vaults accumulate yield slowly — a 15% APY vault moves \~0.04% per day. A sudden large movement in the exchange rate is a direct indicator of an attack in progress or structural failure.

| **Signal**            | **Threshold**               | **Scoring Impact**                       | **Pattern**                                                                                                                                                    |
| :-------------------- | :-------------------------- | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `exchange_rate_spike` | > +2% since last checkpoint | **Hard floor at 70 (critical boundary)** | Donation attack — assets transferred directly into vault inflate convertToAssets() without minting shares. Venus wUSDM: 65% jump → \$902K bad debt (Feb 2024). |
| `exchange_rate_crash` | > −1% since last checkpoint | **Hard floor at 65 (high tier)**         | Exploit in progress, collateral collapse, or bad-debt crystallisation. Yield vaults should never go down for stablecoins.                                      |

Previous price is persisted across pipeline runs. No external API required — uses the share price already captured during enrichment.

# ERC-4626 Donation Attack Risk

When an ERC-4626 vault is used as collateral in a lending protocol and that protocol prices it via `convertToAssets()`, the vault becomes vulnerable to a **donation attack**: an attacker transfers assets directly into the vault contract (without calling `deposit()`), inflating the exchange rate without minting new shares. The lending protocol's oracle reads the inflated rate and allows overborrowing against the manipulated collateral.

<Info>
  **Venus wUSDM (Feb 27, 2024):**

  Attacker donated USDM directly into the wUSDM contract, inflating the exchange rate from 1.0694 → 1.764. Venus used `convertToAssets()` as its price oracle with no TWAP or manipulation protection. \~\$902K bad debt generated from a \$350 initial deposit.
</Info>

<Columns cols={2}>
  <Column>
    **Detection**

    Vault is ERC-4626 **AND** its address appears as `collateralAsset` in ≥ 1 active lending market. Queried from live on-chain market data at scoring time.
  </Column>

  <Column>
    **Scoring Impact**

    `erc4626_donation_risk = true` → **+15 additive penalty** + `erc4626_donation_risk` flag. This is a latent (pre-exploit) structural risk; pair with exchange rate velocity for real-time detection.
  </Column>
</Columns>

# Large Redemption Alerts

Every 6 hours, Webacy scans 24 hours of on-chain `Withdraw` events across 600+ vaults (TVL ≥ \$500K) to detect concentrated exits before they appear in price data. A vault is flagged when a single wallet redeems more than \$500K or more than 1% of vault TVL within the lookback window.

| **Chain**    | **Lookback window**       |
| :----------- | :------------------------ |
| **Ethereum** | 24h                       |
| **Arbitrum** | 2h (high-frequency chain) |
| **Base**     | 24h                       |
| **Optimism** | 24h                       |
| **Polygon**  | 24h                       |
| **BSC**      | 6h                        |

Flagged vaults receive a `large_redemption_alert` risk flag and expose four fields: `large_redemption_count_24h`, `large_redemption_usd_24h`, `large_redemption_max_pct_tvl`, and `large_redemption_wallets`. Each alert run is archived to S3 at `vaults/redemption-alerts/{date}/{timestamp}.json`.

# Risk Summary (Natural Language)

Every vault includes a `risk_summary` field — a one-sentence plain-English explanation of why the vault scored the way it did. Generated deterministically from the top weighted sub-score contributors and highest-priority active flags. No LLM required.

<Info>
  Example

  "Score 74 (HIGH). Primary drivers: restricted withdrawals, centralized governance, unverified/unaudited code. Active signals: yield trap, thin collateral market."
</Info>

Available as `risk_summary` on every vault record. Useful for surfacing in alerts, reports, and API consumers without additional processing.

# Output Fields Reference

Additional fields returned on every vault record. These fields feed into the scoring pipeline and are exposed in the API response for consumers that need to inspect data freshness, vault architecture, or audit provenance directly.

| **Field**            | **Type**           | **Description**                                                                                                                                                                                                                                                                                                                                                                                                                             |
| :------------------- | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data_as_of`         | ISO 8601 timestamp | When this vault was last scored and enriched by the pipeline. Used in the UI as a staleness indicator — an amber ⚠ badge appears when the timestamp is more than 48 hours old. Useful for API consumers to detect stale records before acting on them.                                                                                                                                                                                      |
| `ts_feature_tags`    | string\[]          | Webacy's vault architecture tags (e.g. `euler_earn_like`, `pendle_pt`, `yearn_v3_like`). Used internally to apply architecture-specific scoring rules — for example, utilization zero-out for EulerEarn and Pendle vaults, and depeg suppression for Pendle PT vaults. See *Architecture-specific scoring rules* above for how each tag affects scoring.                                                                                    |
| `last_audit_date`    | ISO date string    | Date of the most recent security audit on record (sourced from Webacy's curated audit registry). Drives the graduated audit recency sub-score: Fresh (\<6 months) → 5, Moderate (6–18 months) → 10–30, Aging (18–36 months) → 30–60, Stale (>3 years) → 70. `null` when no audit date is available.                                                                                                                                         |
| `audit_firms`        | string\[]          | Names of the audit firms that have reviewed this protocol (e.g. Trail of Bits, OpenZeppelin, Spearbit, Pashov). Reputable firms with audits within the last 12 months apply up to a −20 reduction to the audit recency sub-score. An empty array means no audit firms are on record — equivalent to `no_audits` for scoring purposes.                                                                                                       |
| `days_since_harvest` | integer (nullable) | Days since the vault's last `harvest()` keeper call. Populated from on-chain harvest data. Used to apply graduated harvest inactivity penalties for strategy/keeper vaults (Yearn V2/V3, Harvest, etc.): \< 30 days → no penalty; 30–60 days → +10, `harvest_slow`; 60–90 days → +25, `harvest_stale`; ≥ 90 days → full inactivity penalty, `harvest_dormant`. `null` when unavailable — harvest penalty is suppressed when data is absent. |
