How DFB grades risk — provably
DeBank shows yield. DFB shows the risk behind it — provably.
Every DeFi analytics product answers "what does this earn?". Only one grades risk — and opaquely (a black-box A–F). Nobody publishes, per pool, all three of: a conservative exit-liquidity-by-size schedule; a deterministic would-the-desk-refuse-it verdict; and a reproducible per-row proof hash.
SPA already computes all three — for its own book — in the rates-desk risk engine. DFB is the generalization of that same engine from the desk's positions to any followed market. The risk path is deterministic, LLM-free, and fail-closed (auditable — unlike "AI risk scoring").
The honest frame (stated, not hidden)
- DFB is depth + risk-truth on a curated whitelist (~38 live markets today), not a 10,000-pool clone.
- Breadth grows behind the identical overlay — breadth never relaxes the grade.
- DFB is 100% read-only, advisory, a paper-project: it moves no capital and never touches the go-live track.
How to read a pool row
1 · The A/B/C/D risk class
The class is not a DFB-invented score. It is a deterministic presentation map of the engine's own verdict (approval / kill-reason / decomposition). DFB invents no score — it reads the letter off the engine's classify().
A — alpha approved · net_edge > rwa_floor
Desk would enter; structurally clean, harvestable edge over the ~3.4% RWA floor.
B — beta-floor approved · net_edge ≤ rwa_floor
Desk would enter; ~baseline yield ("own-the-floor", no real edge).
C — risk-comp REFUSED · economics / size
Desk refuses — the yield is mostly risk compensation, or unexitable at size.
D — incentive REFUSED · structural tail-veto
Desk REFUSES on structural toxicity — at ANY size (the tail-veto; cannot be sized around).
UNKNOWN — fail-closed thin / stale / unknown kind
Data too thin / stale / unresolvable — published as a visible hole, never silently graded safe.
The D-vs-C split is load-bearing: a structural refusal (D) is size-independent; a size/economics refusal (C) is not. DFB reads this off the engine's KillReason enum — it never re-derives toxicity.
2 · Structural haircut + tail-veto (size-independent toxicity)
The gate is refusal-first: it vetoes tail-comp before it ever looks at the economics. The fair-value decomposition is baseline minus five haircuts:
total_haircut = peg + funding_flip + oracle + liquidity + protocol structural_haircut = peg + funding_flip + oracle + protocol (EXCLUDES the size-dependent liquidity term)
- TAIL_VETO (toxicity, size-PROOF): structural_haircut > 0.06 (calibrated). Because the structural haircut excludes the size-dependent liquidity term, a tail-toxic book is refused at any size — sizing down only shrinks the liquidity haircut, never the toxicity verdict.
- TAIL_VETO (economics, size-aware): total_haircut > 0.12. Then UNDERLYING_DEPEG (peg > 1%), ORACLE_STALE (> 1h), STABLE_DEPEG (> 0.5%), FUNDING_FLIP (streak ≥ 5), ECONOMICS, SIZE.
3 · Exit-liquidity-by-size (a conservative lower bound)
Per pool, DFB publishes the exit schedule at $1M / $5M / $10M OUT via the engine's depth_at_size — DFB computes none of these numbers. A conservative constant-product impact fraction is applied against that market's own on-chain liquidity — never an aggregated cross-venue number — so the bound is a lower bound: the real market is at least this deep, often deeper.
fail-CLOSED is the load-bearing rule: a ticket the market cannot absorb is published as a flagged hole (absorbable_usd: null, flagged: true) — never backfilled with a fabricated fill. There is no code path that synthesizes a green exit cell over a depth hole.
The NO-FORK guarantee (AST-enforced)
DFB's verdict == the desk's verdict on the same market — to the byte.
DFB calls the engine, it does not re-implement it: the refusal verdict ← evaluate_entry, the haircuts ← GateResult.decomposition, exit-liquidity ← depth_at_size, A/B/C/D ← classify() (a map of the engine's own GateResult), and engine_proof_hash ← GateResult.proof_hash() (byte-identical to the desk).
This is AST-enforced by test_dfb_no_fork.py: no DFB module defines the engine's risk math; the entrypoints DFB uses are the engine's own objects (same module identity); and overlay(pool).engine_proof_hash == evaluate_entry(...).proof_hash(). So the two products can never drift.
Verify any pool — "don't trust us, check us"
Every row carries a reproducible proof. Re-derive it yourself — without our code.
row_hash = sha256 of the canonical JSON of the row body (the row minus prev_hash/row_hash) + its prev_hash. It binds both the published inputs (apy/tvl) and the outputs (risk_class / refusal / exit_liquidity / haircuts / engine_proof_hash). Forge any cell → the recompute diverges. prev_hash chains the rows (genesis "0"×64), so a reordered / dropped / inserted row is caught.
One file, zero dependencies, zero spa_core import. Download scripts/verify_dfb_pool.py + the public JSON and run:
# whole DFB data dir (pools.json + pool/*.json + history/*.jsonl): python3 scripts/verify_dfb_pool.py data/dfb # one pool by id, or its detail file: python3 scripts/verify_dfb_pool.py pendle__ethereum__susde
Exit 0 = every row reproduces byte-for-byte (and every chain links); 1 = any mismatch (with the precise broken_at + field); 2 = no input found (fail-closed). The verifier's recipe mirrors the risk engine to the byte — so the desk's number and an independent recompute agree with zero shared code.
The verdict the engine emits today
To not over-claim: the risk overlay today emits exactly three verdict values — SAFE, REFUSE, and UNKNOWN (a fail-closed hole). WATCH is reserved but not yet emitted by the engine.
Explore the DFB surfaces
Screener →
The risk-first board: every pool with its class, exit-by-size + verdict
Alerts →
When a watched pool becomes one the desk would refuse (REFUSAL_FLIP)
Portfolio lens →
Read-only address → each holding graded A/B/C/D. No custody, no signing
Verify →
"Don't trust us, check us" — reproduce any published proof on a clean machine