API · contract v1.8.0
17 read-only endpoints, every money value an exact decimal string.
No key or sign-in; requests are rate-limited per client. Every sample is the contract's own example or a committed client fixture, and a CI test fails if this page drifts from either.
15 GET · 2 POST · error responses 400 · 404 · 409 · 429 · 500 · 503
TypeScript@solvent/client
import { SolventClient } from "@solvent/client";
const client = new SolventClient({ baseUrl: "https://solventrisk.com" });
// Aggregates. Every money quantity is a DECIMAL STRING, exactly as the wire
// carried it — nothing here converts, rounds, or floats.
const book = await client.book();
// Three-valued lookup: the wire's found true/false/null arrives as a sealed
// outcome union — `if (!result.found)` does not compile, so a withheld
// answer can never read as "no position".
const result = await client.address("0xAAaA000000000000000000000000000000000001");
switch (result.outcome) {
case "found": /* result.response.positions */ break;
case "not-found": /* definitive: no position in batch */ break;
case "unknowable": /* withheld engine — NOT "none" */ break;
}
// The deploy-bound evidence manifest: the proof subject and the live subject, split.
const evidence = await client.evidence();Endpointsapi/openapi.yaml, verbatim
Verification →Per-engine aggregates with named refusal and flag breakdowns, a health-factor histogram bucketed on each engine's OWN comparator, and a single-factor down-grid waterfall whose first point is the UNSHOCKED book (which is where the standing bad-debt census comes from).
curl -s "https://solventrisk.com/v1/book"
200 response · packages/client-ts/test/fixtures/book.json (contract-validated 200 body, byte-identical)
{
"served_at": "2026-07-29T10:00:00Z",
"batch": {
"id": 1,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 0,
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"refused_engines": [],
"flagged_count": 1,
"watermarks": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "aave_param",
"chain_id": 1,
"last_block": 25635600,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "prices:poll:1",
"chain_id": 1,
"last_block": 25635610,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1200,
"generation": 4,
"generation_open": false
}
},
{
"engine": "prices:poll:10",
"chain_id": 10,
"last_block": 154796540,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
}
],
"supersession": {
"superseded": false,
"legs": [],
"note": "a superseded batch is still served: the flag is the contract and it heals at the next materializer pass (design spec §4). The legs are evaluated against a LIVE read of the cursor and epoch tables inside the same snapshot as the database clock."
}
},
"refused_engines": [],
"engines": [
{
"engine": "aave_v3_etherfi",
"value_decimals": 8,
"positions": 2,
"computed_positions": 1,
"refused_positions": 1,
"flagged_positions": 1,
"liquidatable_positions": 0,
"sweep": null,
"refused": false,
"refusal": null,
"total_collateral": "800000000000",
"total_debt": "600000000000",
"refusals": [
{
"key": "G1",
"count": 1
}
],
"flags": [
{
"key": "stale_price",
"count": 1
}
],
"unit_note": "values are integers at 8 decimals in this engine's own unit (Aave: the pool's base currency; Debt Manager: USD)"
},
{
"engine": "debt_manager",
"value_decimals": 6,
"positions": 2,
"computed_positions": 1,
"refused_positions": 1,
"flagged_positions": 0,
"liquidatable_positions": 1,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1200,
"generation": 4,
"generation_open": false
},
"refused": false,
"refusal": null,
"total_collateral": "4000000000",
"total_debt": "4200000000",
"refusals": [
{
"key": "SWEEP_NEVER",
"count": 1
}
],
"flags": [],
"unit_note": "values are integers at 6 decimals in this engine's own unit (Aave: the pool's base currency; Debt Manager: USD)"
}
],
"hf_histogram": {
"wad_scale": "1000000000000000000",
"engines": [
{
"refused": false,
"refusal": null,
"engine": "aave_v3_etherfi",
"comparator": "hf_wad",
"buckets": [
{
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000",
"count": 0
},
{
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000",
"count": 0
},
{
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000",
"count": 0
},
{
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000",
"count": 1
},
{
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000",
"count": 0
},
{
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000",
"count": 0
},
{
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000",
"count": 0
},
{
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null,
"count": 0
}
],
"infinite_count": 0,
"refused_count": 1,
"note": "buckets are the pool's own health-factor WAD. Aave liquidates STRICTLY BELOW 1e18, so `< 1.00` is the eligible set and exactly 1.00 is healthy."
},
{
"refused": false,
"refusal": null,
"engine": "debt_manager",
"comparator": "hf_num/hf_den",
"buckets": [
{
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000",
"count": 1
},
{
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000",
"count": 0
},
{
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000",
"count": 0
},
{
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000",
"count": 0
},
{
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000",
"count": 0
},
{
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000",
"count": 0
},
{
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000",
"count": 0
},
{
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null,
"count": 0
}
],
"infinite_count": 0,
"refused_count": 1,
"note": "the Debt Manager has no health-factor wad: its liquidation test is the strict boolean `debt > maxBorrowLT`. These buckets are the EXACT rational maxBorrowLT/borrowings, a disclosure only — take eligibility from `liquidatable_positions`."
}
]
},
"waterfall": {
"scenario_id": "eth_minus_30",
"scenario_version": "v1",
"axis": "eth_usd",
"grid_scale": "1000000000000000000",
"points": [
{
"index": 0,
"factor": "1000000000000000000",
"engines": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 0,
"cumulative_debt_eligible_usd": "0",
"cumulative_collateral_at_risk_usd": "0",
"insolvent_if_liquidated_accounts": 0,
"cumulative_bad_debt_usd": "0"
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"newly_eligible_accounts": 1,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "4200000000",
"cumulative_collateral_at_risk_usd": "4000000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "239603961"
}
]
},
{
"index": 1,
"factor": "900000000000000000",
"engines": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"newly_eligible_accounts": 1,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "600000000000",
"cumulative_collateral_at_risk_usd": "630000000000",
"insolvent_if_liquidated_accounts": 0,
"cumulative_bad_debt_usd": "0"
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "4200000000",
"cumulative_collateral_at_risk_usd": "3600000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "635643565"
}
]
},
{
"index": 2,
"factor": "800000000000000000",
"engines": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "600000000000",
"cumulative_collateral_at_risk_usd": "630000000000",
"insolvent_if_liquidated_accounts": 0,
"cumulative_bad_debt_usd": "0"
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "4200000000",
"cumulative_collateral_at_risk_usd": "3200000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "1031683169"
}
]
},
{
"index": 3,
"factor": "700000000000000000",
"engines": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "600000000000",
"cumulative_collateral_at_risk_usd": "560000000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "66666666667"
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "4200000000",
"cumulative_collateral_at_risk_usd": "2800000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "1427722773"
}
]
},
{
"index": 4,
"factor": "600000000000000000",
"engines": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "600000000000",
"cumulative_collateral_at_risk_usd": "480000000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "142857142858"
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "4200000000",
"cumulative_collateral_at_risk_usd": "2400000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "1823762377"
}
]
},
{
"index": 5,
"factor": "500000000000000000",
"engines": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "600000000000",
"cumulative_collateral_at_risk_usd": "400000000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "219047619048"
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"newly_eligible_accounts": 0,
"cumulative_eligible_accounts": 1,
"cumulative_debt_eligible_usd": "4200000000",
"cumulative_collateral_at_risk_usd": "2000000000",
"insolvent_if_liquidated_accounts": 1,
"cumulative_bad_debt_usd": "2219801981"
}
]
}
],
"held_flat": [
{
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chain_id": 1,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"value": "100000000"
}
],
"eligibility_note": "debt ELIGIBLE for liquidation; realized ≤ eligible (the Debt Manager closes in two passes, 50% then remainder)",
"monotonicity": {
"ok": true
},
"at_risk_note": "cumulative_collateral_at_risk_usd carries NO monotonicity invariant: it is measured AT each grid point, so it legitimately falls once already-crossed accounts are worth less. Do not render it as a monotone accumulation.",
"excluded_engines": []
},
"bad_debt": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"current_bad_debt_usd": "0",
"insolvent_positions": 0,
"eligible_positions": 0,
"eligible_debt_usd": "0",
"collateral_at_risk_usd": "0",
"refused": false,
"refusal": null
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"current_bad_debt_usd": "239603961",
"insolvent_positions": 1,
"eligible_positions": 1,
"eligible_debt_usd": "4200000000",
"collateral_at_risk_usd": "4000000000",
"refused": false,
"refusal": null
}
],
"coverage": {
"batch_positions": 4,
"in_book": 2,
"refused_in_batch": 2,
"excluded_by_this_layer": 0,
"excluded": [],
"withheld_engines": [],
"stress_coverage_is_full": true,
"note": "every position the batch carries is on the wire. `excluded` lists positions this layer could not rebuild into the pure library's input form — they are absent from the stress and waterfall arithmetic and are named here rather than dropped."
},
"notes": [
"Aave base values are 8-decimal and Debt Manager USD is 6-decimal: the two engines are reported separately and are NEVER summed.",
"Refused positions are counted in `refused_positions` and broken down by code in `refusals`. The book is served with its refusals, never without the positions that produced them."
]
}A page of the requested ENGINE's book, drawn entirely from ONE batch.
The response pins the batch every row came from, and next_cursor is BOUND to that batch: it encodes (batch id, rank) opaquely, and a client must treat it as a token, not a structure. When the pinned batch is no longer the newest servable batch, presenting its cursor answers 409 batch_superseded and the client restarts from page one — an honest restart, never a page silently mixing two materializations.
Engines are never blended, so engine is REQUIRED: a single ranked list over two comparators (Aave's wad vs the Debt Manager's strict boolean) would be a cross-engine ordering this service refuses to invent.
Refused rows are ROWS here, exactly as they are in the aggregates: filtering them out would un-count the positions the book could not honestly value.
Rows are the LEAN PositionSummary (AMENDMENT 1/E): the ranked page's own fields — status, refusal, health factor, verdict, totals, boundary distance, as-of marks — without the per-leg and per-price fan-out. The FULL Position (legs, price inputs, liquidation-price solve, per-input provenance) stays on /v1/address/{addr}.
Which engine's book to page. The two books are never blended into one ranking.
The next_cursor of the previous page, verbatim. OPAQUE — it encodes (batch id, rank) and is bound to the batch AND the exact query that minted it: (engine, sort, dir, min_value). A cursor whose batch has been superseded answers 409 rather than a mixed-batch page; a cursor presented under a different (engine, sort, dir, min_value) than it was minted for is a 400 — replaying a rank into a different ranking would silently serve garbage. A malformed cursor is a 400.
Page size. Defaults to 50.
Ranking within the engine's own comparator. Defaults to liq_distance (unchanged). Ties break on (account) so the order is total and the cursor deterministic.
PER-ENGINE VOCABULARY. headroom, debt and status are defined on BOTH engines. hf is aave_v3_etherfi ONLY — the Debt Manager publishes a strict liquidatable boolean, not a health factor, and this service refuses that pair with a 400 rather than invent an ordering. liq_distance remains defined on both engines.
headroom(ADDED 1.5.0) — the SHARE of the account's borrowing capacity still unused before liquidation, i.e. the RATIO(threshold_value − debt) / threshold_value, ascending: least capacity left first. Ondebt_managerthat is(max_borrow_lt − borrowings) / max_borrow_lt, ordered as an exact ratio; an account with no published borrowing capacity has NO ratio and ranks last under the canonical direction rather than faking either extreme. Onaave_v3_etherfiheadroom is1 − 1/HF, which is strictly increasing in the health factor, soheadroom,hfandliq_distanceare ONE total order there — the same rows in the same sequence, under three names.liq_distance(DEPRECATED 1.5.0, STILL SERVED, ORDERING UNCHANGED) — distance to the engine's own boundary. Ondebt_managerthis ranks the ABSOLUTE room in the engine's value unit (max_borrow_lt − borrowings), which is a DIFFERENT quantity from the ratio and disagrees with it wherever capacity differs: a large account with a small percentage of room outranks a small account with a large percentage. Consumers that DISPLAY a percentage should rank byheadroom. This key keeps its exact ordering — existing links and in-flight cursors were minted against it and continue to mean what they meant.debt— the engine's own debt total, descending.hf— health factor ascending (aave_v3_etherfionly).status— refused rows first, for triage.
Ranking direction. ABSENT means the sort's own CANONICAL direction, which this contract states explicitly: headroom → asc (least capacity left first), liq_distance → asc (nearest to the boundary first), debt → desc (largest debt first), hf → asc (lowest health factor first), status → refused-first (desc on the refused axis). The non-canonical direction serves the EXACT REVERSE of the canonical ranking — except the account tie-break, which ALWAYS ranks ascending, so equal sort keys order identically in both directions and the cursor stays deterministic.
headroom IS THE ONE CARVE-OUT FROM THAT REVERSAL LAW (1.5.0). Only the KNOWN-VALUE axis flips: rows carrying NO headroom value at all — refused rows on either engine, and debt_manager rows with no published borrowing capacity — rank LAST in BOTH directions. UNKNOWN IS NOT MAXIMAL: a page asking for the GREATEST headroom first must not be led by accounts whose headroom this service could not derive, so the unknowns stay where the canonical direction also puts them instead of being flipped to the front. Every other key — liq_distance, debt, hf, status — reverses whole, refused axis included.
Position-size floor, a decimal integer string in the ENGINE'S OWN value unit at its value_decimals (Aave base-currency 8, Debt Manager USD 6 — the same unit as the row's total_collateral / total_debt pair). THE EXCLUSION LAW: a row is excluded iff status = computed AND both totals are non-null AND max(total_collateral, total_debt) < min_value. REFUSED rows and rows with a NULL total are NEVER excluded — an unknowable is not a small number, and a size filter that hid refusals would un-count the positions the book could not honestly value. When min_value is present, total_positions counts QUALIFYING rows only (it is this walk's denominator); the UNFILTERED engine total remains aggregate.positions on /v1/book.
curl -s "https://solventrisk.com/v1/positions?engine=aave_v3_etherfi"
200 response · api/openapi.yaml — GET /v1/positions 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"batch": {
"id": 1,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 5,
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"refused_engines": [],
"flagged_count": 1,
"watermarks": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1205,
"generation": 4,
"generation_open": false
}
}
],
"supersession": {
"superseded": false,
"legs": [],
"note": "a superseded batch is still served: the flag is the contract and it heals at the next materializer pass."
}
},
"engine": "aave_v3_etherfi",
"sort": "liq_distance",
"limit": 1,
"refused": false,
"refusal": null,
"total_positions": 2,
"positions": [
{
"engine": "aave_v3_etherfi",
"account": "0xAAaA000000000000000000000000000000000001",
"status": "computed",
"value_decimals": 8,
"refusal": null,
"flags": [
"stale_price"
],
"health_factor": {
"wad": "1080000000000000000",
"num": "6480000000000000",
"den": "6000000000000000",
"infinite": false,
"note": "compare against 1e18 ON THE WAD; do not re-derive a float from num/den to decide eligibility."
},
"liquidatable": null,
"total_collateral": "800000000000",
"total_debt": "600000000000",
"liq_distance": {
"kind": "distance",
"scale_factor_num": "6000000000000000",
"scale_factor_den": "6480000000000000",
"factor_asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"factor_symbol": "weETH",
"reason": null
},
"balances_block": 25635618,
"params_block": 25635600,
"sweep_block": 0
}
],
"next_cursor": "MS5saXFfZGlzdGFuY2UuMQ",
"notes": [
"every row on this page was drawn from batch 1; `next_cursor` is bound to that batch and answers 409 once it is superseded as the newest servable batch.",
"refused rows are ROWS: they stay on the page, named, and counted.",
"rows are the lean PositionSummary; the FULL Position (legs, price inputs, liquidation-price solve) is served by /v1/address/{addr}."
]
}A 0x-prefixed 20-byte address. Validated strictly; a malformed address is a 400, never a silently different account.
curl -s "https://solventrisk.com/v1/address/0xAAaA000000000000000000000000000000000001"
200 response · packages/client-ts/test/fixtures/address-aave.json (contract-validated 200 body, byte-identical)
{
"served_at": "2026-07-29T10:00:00Z",
"batch": {
"id": 1,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 0,
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"refused_engines": [],
"flagged_count": 1,
"watermarks": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "aave_param",
"chain_id": 1,
"last_block": 25635600,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "prices:poll:1",
"chain_id": 1,
"last_block": 25635610,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1200,
"generation": 4,
"generation_open": false
}
},
{
"engine": "prices:poll:10",
"chain_id": 10,
"last_block": 154796540,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
}
],
"supersession": {
"superseded": false,
"legs": [],
"note": "a superseded batch is still served: the flag is the contract and it heals at the next materializer pass (design spec §4). The legs are evaluated against a LIVE read of the cursor and epoch tables inside the same snapshot as the database clock."
}
},
"address": "0xAAaA000000000000000000000000000000000001",
"positions": [
{
"engine": "aave_v3_etherfi",
"account": "0xAAaA000000000000000000000000000000000001",
"status": "computed",
"value_decimals": 8,
"refusal": null,
"flags": [
"stale_price"
],
"health_factor": {
"wad": "1080000000000000000",
"num": "6480000000000000",
"den": "6000000000000000",
"infinite": false,
"note": "`wad` is the pool's own health factor, a half-up composite over the exact weighted sum. Compare against 1e18 on the WAD; do not re-derive a float from num/den to decide eligibility."
},
"liquidatable": null,
"total_collateral_base": "800000000000",
"total_debt_base": "600000000000",
"weighted_lt_sum": "6480000000000000",
"avg_lt_bps": "8100",
"collateral_value_usd": null,
"max_borrow_lt": null,
"borrowings": null,
"legs": [
{
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"decimals": 6,
"live_debt": "6000000000",
"live_collateral": null,
"debt_base": "600000000000",
"collateral_base": null,
"weighted_lt": null,
"used_as_collateral": false,
"debt_index_block": 25635618,
"collateral_index_block": null,
"amount": null,
"value_usd": null,
"max_borrow_contribution": null,
"liq_threshold": null,
"liq_bonus": null
},
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"decimals": 18,
"live_debt": null,
"live_collateral": "2000000000000000000",
"debt_base": null,
"collateral_base": "800000000000",
"weighted_lt": "6480000000000000",
"used_as_collateral": true,
"debt_index_block": null,
"collateral_index_block": 25635618,
"amount": null,
"value_usd": null,
"max_borrow_contribution": null,
"liq_threshold": "8100",
"liq_bonus": "10500"
}
],
"price_inputs": [
{
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chain_id": 1,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"provenance": "adapter-output",
"value": "100000000",
"decimals": 8,
"block_number": 25635610,
"source_as_of": "2026-07-29T09:59:48Z",
"budget_seconds": 180,
"verdict": "fresh",
"age_seconds": 12,
"fresh": true,
"note": "within this input's own budget at compute time."
},
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"chain_id": 1,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"provenance": "adapter-output",
"value": "400000000000",
"decimals": 8,
"block_number": 25635610,
"source_as_of": "2026-07-29T09:56:30Z",
"budget_seconds": 180,
"verdict": "stale",
"age_seconds": 210,
"fresh": false,
"note": "older than its budget but within the ceiling: COMPUTED AND FLAGGED (G4), and the flag propagates into every aggregate containing it."
}
],
"as_of": {
"balances_block": 25635618,
"params_block": 25635600,
"sweep_block": 0,
"oldest_price_input": "2026-07-29T09:56:30Z",
"stale_price_inputs": true,
"note": "each leg additionally carries its OWN rate-index as-of block: `rate_indexes` updates only on ReserveDataUpdated and can trail the balances cursor, so one balances watermark over an old index would hide the debt leg's true shelf life (design spec §5, Codex round 1 [H5])."
},
"liquidation_price": {
"in_factor": true,
"never_liquidatable": false,
"scale_factor_num": "6000000000000000",
"scale_factor_den": "6480000000000000",
"already_breached": false,
"prices": [
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"current_price": "400000000000",
"price_decimals": 8,
"price_floor": "370370370370",
"lowest_healthy_price": "370370370371"
}
],
"factor_assets": [
"0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee"
],
"held_assets": [
"0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee"
],
"boundary_is_healthy": true,
"per_token_floor_omitted": false,
"diagnostic": false,
"axis": "eth_usd",
"note": "at exactly this price the position is HEALTHY on both engines — liquidation begins strictly below it. Render `lowest_healthy_price`, the conservative ceil."
}
}
],
"found": true,
"lookup_complete": true,
"withheld_engines": [],
"lookup_complete_note": "every engine was available to be consulted for this lookup, so `found` is a definitive answer.",
"notes": [
"Every price disclosure below is the SNAPSHOT the batch persisted — value, decimals, block, as-of, source, provenance, budget and verdict. Nothing here is re-read or re-judged at request time (design spec §7).",
"`source_as_of` is the chain-asserted as-of (a poll anchor's block timestamp or an AnswerUpdated updatedAt). Database insert time is NEVER substituted for it.",
"Debt Manager collateral comes from a sweep with a worst case of 5580 seconds; its prices are 60-second samples. Read `sweep_block` for the collateral as-of, never the price age.",
"The two engines are never blended: Aave publishes a continuous health factor, the Debt Manager a strict liquidatable boolean."
]
}Every scenario in the committed set, evaluated from THIS BATCH's persisted rows so the results describe the same materialization as /v1/book. Shocks are applied to primitive axes and propagated through each engine's actual pricing transforms; assets the propagation matrix does not cover are named in held_flat rather than silently held at their pre-shock price.
A 0x-prefixed 20-byte address. Validated strictly; a malformed address is a 400, never a silently different account.
curl -s "https://solventrisk.com/v1/address/0xAAaA000000000000000000000000000000000001/stress"
200 response · packages/client-ts/test/fixtures/stress-aave.json (contract-validated 200 body, byte-identical)
{
"served_at": "2026-07-29T10:00:00Z",
"batch": {
"id": 1,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 0,
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"refused_engines": [],
"flagged_count": 1,
"watermarks": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "aave_param",
"chain_id": 1,
"last_block": 25635600,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "prices:poll:1",
"chain_id": 1,
"last_block": 25635610,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1200,
"generation": 4,
"generation_open": false
}
},
{
"engine": "prices:poll:10",
"chain_id": 10,
"last_block": 154796540,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
}
],
"supersession": {
"superseded": false,
"legs": [],
"note": "a superseded batch is still served: the flag is the contract and it heals at the next materializer pass (design spec §4). The legs are evaluated against a LIVE read of the cursor and epoch tables inside the same snapshot as the database clock."
}
},
"address": "0xAAaA000000000000000000000000000000000001",
"scenario_config_version": "v1",
"found": true,
"lookup_complete": true,
"withheld_engines": [],
"lookup_complete_note": "every engine was available to be consulted for this lookup, so `found` is a definitive answer.",
"scenarios": [
{
"id": "eth_minus_30",
"version": "v1",
"label": "ETH -30 percent",
"description": "Factor shock on ETH/USD. Every ETH-linked collateral moves jointly because each one's USD price is composed from ETH/USD by construction; weETH additionally carries its redemption rate, held flat here.",
"path_assumption": "instantaneous mark at the shocked level; single-step, no path, no cascade feedback, no partial closes",
"engines": [
"aave_v3_etherfi",
"debt_manager"
],
"shocks": [
{
"axis": "eth_usd",
"factor_num": 70,
"factor_den": 100
}
],
"out_of_model": [
"oracle lag and heartbeat behaviour during the move: the shock is applied as an instantaneous mark, while the real feeds update on deviation or heartbeat",
"deviation-trigger discreteness (a feed moves in rounds, not continuously)",
"liquidator liquidity, gas costs, execution latency and cascade dynamics",
"market correlations not mechanically implied by the read paths recorded in recon/derivation-notes.md",
"intra-sample price wicks: prices are 60-second point samples and an intra-interval spike is invisible by construction (D-012)",
"Aave price caps are checked, not assumed: a down-shock leaves them slack, and any cap that did bind is reported per input"
],
"results": [
{
"engine": "aave_v3_etherfi",
"account": "0xAAaA000000000000000000000000000000000001",
"applicable": true,
"before": {
"health_factor_wad": "1080000000000000000",
"health_factor_num": "6480000000000000",
"health_factor_den": "6000000000000000",
"infinite": false,
"liquidatable": null,
"eligible": false,
"collateral_usd": "800000000000",
"debt_usd": "600000000000",
"max_borrow_lt": null
},
"after": {
"health_factor_wad": "756000000000000000",
"health_factor_num": "4536000000000000",
"health_factor_den": "6000000000000000",
"infinite": false,
"liquidatable": null,
"eligible": true,
"collateral_usd": "560000000000",
"debt_usd": "600000000000",
"max_borrow_lt": null
},
"applied_shocks": [
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"chain_id": 1,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"factor_num": "70",
"factor_den": "100",
"before": "400000000000",
"after": "280000000000",
"snapped": false,
"base_snapped": false,
"cap_bound": false
}
],
"held_flat": [
{
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chain_id": 1,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"value": "100000000"
}
],
"market_realization": null,
"projection": null
}
]
},
{
"id": "weeth_market_depeg_oracles_held",
"version": "v1",
"label": "weETH market depeg to 0.95 (oracles held)",
"description": "weETH trades 5 percent below its redemption value while the redemption rate is unchanged. This is EXPLICITLY NOT a health-factor event: neither protocol reads a secondary-market weETH price, so every health factor is bit-identical and no position becomes liquidatable.",
"path_assumption": "oracle marks held exactly; market value is a separate axis applied only to realized proceeds",
"engines": [
"aave_v3_etherfi",
"debt_manager"
],
"shocks": [],
"out_of_model": [
"oracle lag and heartbeat behaviour during the move: the shock is applied as an instantaneous mark, while the real feeds update on deviation or heartbeat",
"deviation-trigger discreteness (a feed moves in rounds, not continuously)",
"liquidator liquidity, gas costs, execution latency and cascade dynamics",
"market correlations not mechanically implied by the read paths recorded in recon/derivation-notes.md",
"intra-sample price wicks: prices are 60-second point samples and an intra-interval spike is invisible by construction (D-012)",
"seizure is modeled PRO-RATA over a position's counted collateral; a real liquidator picks a preference order and would take the least-impaired asset first, so this is neutral rather than conservative"
],
"results": [
{
"engine": "aave_v3_etherfi",
"account": "0xAAaA000000000000000000000000000000000001",
"applicable": true,
"before": {
"health_factor_wad": "1080000000000000000",
"health_factor_num": "6480000000000000",
"health_factor_den": "6000000000000000",
"infinite": false,
"liquidatable": null,
"eligible": false,
"collateral_usd": "800000000000",
"debt_usd": "600000000000",
"max_borrow_lt": null
},
"after": {
"health_factor_wad": "1080000000000000000",
"health_factor_num": "6480000000000000",
"health_factor_den": "6000000000000000",
"infinite": false,
"liquidatable": null,
"eligible": false,
"collateral_usd": "800000000000",
"debt_usd": "600000000000",
"max_borrow_lt": null
},
"applied_shocks": [],
"held_flat": [],
"market_realization": {
"hfs_unchanged": true,
"execution_shortfall_usd": "0",
"bad_debt_at_liquidation_usd": "0",
"usd_decimals": 8,
"seizure_model": "pro-rata-over-counted-collateral",
"note": "market value is NOT an oracle mark: neither protocol reads a secondary-market price, so this scenario moves NO health factor (`hfs_unchanged` asserts it). The output is the gap the protocol is not seeing."
},
"projection": null
}
]
},
{
"id": "dm_rate_horizon_plus_200bps",
"version": "v1",
"label": "Debt Manager borrow APY +200bps (PROJECTION)",
"description": "A rate change does not move a spot health factor, so this ships as a closed-form HORIZON PROJECTION, never as a spot shock.",
"path_assumption": "collateral prices held flat at the current sample; the APY steps once, at t=0, and holds for the whole horizon",
"engines": [
"debt_manager"
],
"shocks": [
{
"axis": "borrow_apy",
"factor_num": 1,
"factor_den": 1
}
],
"out_of_model": [
"oracle lag and heartbeat behaviour during the move: the shock is applied as an instantaneous mark, while the real feeds update on deviation or heartbeat",
"deviation-trigger discreteness (a feed moves in rounds, not continuously)",
"liquidator liquidity, gas costs, execution latency and cascade dynamics",
"market correlations not mechanically implied by the read paths recorded in recon/derivation-notes.md",
"intra-sample price wicks: prices are 60-second point samples and an intra-interval spike is invisible by construction (D-012)",
"borrower behaviour: no repayment, no top-up, no new borrowing over the horizon",
"the Aave engine is excluded (utilization-driven rates over a residual dust book)"
],
"results": [
{
"engine": "aave_v3_etherfi",
"account": "0xAAaA000000000000000000000000000000000001",
"applicable": false,
"reason": "scenario dm_rate_horizon_plus_200bps is not defined for engine aave_v3_etherfi",
"before": null,
"after": null,
"applied_shocks": [],
"held_flat": [],
"market_realization": null,
"projection": null
}
]
}
],
"notes": [
"Shocks are EXACT rationals applied to primitive axes and propagated through each engine's actual pricing transforms.",
"Assets the propagation matrix does not cover are named in `held_flat` rather than silently held at their pre-shock price."
]
}Per-batch points for one address, newest batch first, one series per engine. Every point is a PERSISTED row from a retained batch — nothing is recomputed for this response, so the history is exactly what was served at the time, including the batches that REFUSED the position.
found carries the same three-valued contract as /v1/address, and a withheld engine leaves a NAMED hole: a batch in which an engine's whole book was withheld has no row for any account on that engine, so such batches are listed per series in withheld_batch_ids — a missing point there means "could not be established at that batch", never "no position at that batch".
A 0x-prefixed 20-byte address. Validated strictly; a malformed address is a 400, never a silently different account.
Number of retained batches to cover, newest first. Defaults to 100.
curl -s "https://solventrisk.com/v1/address/0xAAaA000000000000000000000000000000000001/history"
200 response · api/openapi.yaml — GET /v1/address/{addr}/history 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"batch": {
"id": 2,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 5,
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"refused_engines": [],
"flagged_count": 1,
"watermarks": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
}
],
"supersession": {
"superseded": false,
"legs": [],
"note": "evaluated against a live read of the cursor and epoch tables."
}
},
"address": "0xAAaA000000000000000000000000000000000001",
"limit": 100,
"engines": [
{
"engine": "aave_v3_etherfi",
"value_decimals": 8,
"points": [
{
"batch_id": 2,
"computed_at": "2026-07-29T10:00:00Z",
"balances_block": 25635618,
"sweep_block": 0,
"status": "computed",
"refusal": null,
"health_factor": {
"wad": "1080000000000000000",
"num": "6480000000000000",
"den": "6000000000000000",
"infinite": false,
"note": "compare against 1e18 ON THE WAD."
},
"liquidatable": null,
"total_collateral_base": "800000000000",
"total_debt_base": "600000000000"
},
{
"batch_id": 1,
"computed_at": "2026-07-29T09:45:00Z",
"balances_block": 25635540,
"sweep_block": 0,
"status": "refused",
"refusal": {
"code": "G1",
"detail": "weETH/aaveoracle price input missing at compute time",
"note": "a refused batch is a POINT in this history, not a gap: the position existed and could not honestly be valued."
},
"health_factor": null,
"liquidatable": null,
"total_collateral_base": null,
"total_debt_base": null
}
],
"withheld_batch_ids": [],
"note": "points are persisted rows from retained batches, newest first; nothing is recomputed for this response."
}
],
"found": true,
"lookup_complete": true,
"withheld_engines": [],
"lookup_complete_note": "every engine was available in the newest servable batch, so `found` is a definitive answer there; older points speak only for their own batches.",
"notes": [
"retention bounds this history: batches outside the retention window are gone from this surface, and their absence is stated by `limit`, never rendered as a flat line."
]
}Number of batches to return, newest first.
curl -s "https://solventrisk.com/v1/observatory"
200 response · packages/client-ts/test/fixtures/observatory.json (contract-validated 200 body, byte-identical)
{
"served_at": "2026-07-29T10:00:00Z",
"limit": 50,
"series": [
{
"batch_id": 1,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 0,
"watermarks": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "aave_param",
"chain_id": 1,
"last_block": 25635600,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "prices:poll:1",
"chain_id": 1,
"last_block": 25635610,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1200,
"generation": 4,
"generation_open": false
}
},
{
"engine": "prices:poll:10",
"chain_id": 10,
"last_block": 154796540,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
}
],
"engines": [
{
"engine": "aave_v3_etherfi",
"value_decimals": 8,
"positions": 2,
"computed_positions": 1,
"refused_positions": 1,
"flagged_positions": 1,
"liquidatable_positions": 0,
"sweep": null,
"refused": false,
"refusal": null,
"total_collateral": "800000000000",
"total_debt": "600000000000",
"refusals": [
{
"key": "G1",
"count": 1
}
],
"flags": [
{
"key": "stale_price",
"count": 1
}
],
"unit_note": "values are integers at 8 decimals in this engine's own unit (Aave: the pool's base currency; Debt Manager: USD)"
},
{
"engine": "debt_manager",
"value_decimals": 6,
"positions": 2,
"computed_positions": 1,
"refused_positions": 1,
"flagged_positions": 0,
"liquidatable_positions": 1,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1200,
"generation": 4,
"generation_open": false
},
"refused": false,
"refusal": null,
"total_collateral": "4000000000",
"total_debt": "4200000000",
"refusals": [
{
"key": "SWEEP_NEVER",
"count": 1
}
],
"flags": [],
"unit_note": "values are integers at 6 decimals in this engine's own unit (Aave: the pool's base currency; Debt Manager: USD)"
}
]
}
],
"rate_indexes": [
{
"engine": "aave_v3_etherfi",
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"kind": "liquidity_index",
"scale": "ray-1e27",
"value": "1023456789012345678901234567",
"as_of_block": 25634718,
"note": "as-of-last-ReserveDataUpdated: this index can trail the derive cursor. `as_of_block` is its OWN as-of and is the only honest freshness statement about it."
}
],
"notes": [
"Each point is one materialization. The series is newest first and is never interpolated.",
"Per-engine aggregates are in each engine's own unit and are NEVER summed across engines."
]
}Points from the observatory_points rollup, oldest first: per-engine aggregates captured on an hourly bucket so the migration record persists beyond the batch-retention window. Each point states what the engine's book looked like AT ITS BUCKET — a bucket captured while the engine's whole book was withheld carries null totals and names the refusal, never zeros.
A step_seconds larger than the native bucket serves every Nth bucket VERBATIM. Nothing here is averaged, interpolated or smoothed: a served point is always an exact captured row.
Inclusive lower bound on bucket start. Unbounded when absent.
Inclusive upper bound on bucket start. Unbounded when absent.
Stride in seconds. The rollup's native bucket is hourly (3600); a larger step serves every Nth captured bucket verbatim — never an average of the buckets it skips.
curl -s "https://solventrisk.com/v1/observatory/series?engine=debt_manager"
200 response · api/openapi.yaml — GET /v1/observatory/series 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"engine": "debt_manager",
"usd_decimals": 6,
"from": "2026-07-29T08:00:00Z",
"to": null,
"step_seconds": null,
"points": [
{
"bucket_start": "2026-07-29T08:00:00Z",
"last_block": 154794000,
"batch_id": 41,
"materialization_key": "9a4a7c1de00b53219a6f2f41c86a77025f0b3e2a4c1d99e21b7a30e12cf5a2b9",
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep_recorded": true,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "464379000",
"max_updated_at": "2026-07-29T07:58:40Z",
"age_seconds": 7285,
"generation": 12,
"generation_open": false
},
"refused": false,
"refusal_code": null,
"accounts": 3,
"refused_positions": 1,
"liquidatable_positions": 1,
"debt_usd": "309593004",
"collateral_usd": "412790672",
"rates": [
{
"engine": "debt_manager",
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"kind": "borrow_index",
"scale": "index-1e18",
"value": "1050000000000000000",
"as_of_block": 154793990,
"note": "as of its OWN last update, which can trail the bucket."
}
]
},
{
"bucket_start": "2026-07-29T09:00:00Z",
"last_block": 154796552,
"batch_id": 42,
"materialization_key": "e2a4c1d99e21b7a30e12cf5a2b9a4a7c1de00b53219a6f2f41c86a77025f0b3",
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep_recorded": true,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "464379000",
"max_updated_at": "2026-07-29T08:58:40Z",
"age_seconds": 3685,
"generation": 13,
"generation_open": true
},
"refused": true,
"refusal_code": "FLAG_CUSTODY_UNPROVEN",
"accounts": null,
"refused_positions": 0,
"liquidatable_positions": null,
"debt_usd": null,
"collateral_usd": null,
"rates": []
}
],
"notes": [
"a withheld bucket carries NULL totals and names its refusal code: a rollup that rendered an unproven book as zero debt would fabricate the exact reassurance this surface exists to withhold."
]
}A cursor-paginated read of position_events — durably custodied, reorg-aware chain actions, NEVER invented events.
THE TWO-MODE ORDERING LAW: block heights are incomparable ACROSS chains (ETH ~25.6M and OP ~154M say nothing about time relative to each other), so ordering depends on the request's scope:
- an ENGINE-SCOPED page (
enginenames exactly one engine, hence one chain) is ordered (block_number, tx_hash, log_index, seq) DESC — heights are comparable within one chain; exact and complete. - a CROSS-ENGINE page (no
engine) is ordered by custodiedblock_timeDESC with the deterministic chain-aware tiebreak (chain_id, block_number, tx_hash, log_index, seq) DESC; rows WITHOUT a custodied block_time sort AFTER every timed row in a DISCLOSED untimed tail — never interleaved by incomparable heights, never given an invented time. The tail's internal order is the tiebreak alone, NOT chronology.
Header custody can land between two pages of one cross-engine walk, promoting a row from the untimed tail into the timed section: each page is internally exact, but the feed is a live surface, not a pinned batch.
since_block is meaningful ONLY engine-scoped: a height bound across two chains with incomparable heights means nothing, and a cross-engine request carrying it is refused with 400.
type is the closed DISPLAY vocabulary; raw_type is the engine's own event name, verbatim. Every raw event type either maps to a display type or is EXPLICITLY excluded from this feed with its reason recorded (see EventDisplayType); parameter-change events are served by /v1/params, not here. That mapping is total and welded to the closed per-engine type sets — an unmapped event type is a service failure, not a silent omission.
amount is the engine's own CUSTODIED accounting delta, verbatim, and amount_unit names its closed semantic unit — it is NOT a display-ready token amount (see EventAmountUnit).
block_time is the header time of the event's block under block-header custody. It is NULL until the header is custodied — NEVER fabricated, never substituted with a database clock. Render the block number when it is null.
The next_cursor of the previous page, verbatim. OPAQUE. A malformed cursor is a 400.
Page size. Defaults to 50.
Restrict to one account's own actions.
Display-vocabulary filter, comma-separated. Absent means every display type.
Inclusive lower bound on block_number. ENGINE-SCOPED ONLY: it requires engine, because a height bound across two chains with incomparable heights is meaningless. A cross-engine request carrying it is a 400, never a silently misapplied filter.
curl -s "https://solventrisk.com/v1/events"
200 response · api/openapi.yaml — GET /v1/events 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"filter": {
"engine": null,
"account": null,
"types": [],
"since_block": null
},
"limit": 2,
"events": [
{
"chain_id": 1,
"engine": "aave_v3_etherfi",
"block_number": 25635601,
"block_time": "2026-07-29T09:57:11Z",
"tx_hash": "0x9c33269e9d8f01d1db494ca4cf693c99377f78f9628c1b74d5a1a8592e0f2a11",
"log_index": 42,
"seq": 0,
"type": "liquidation",
"raw_type": "aave_liquidation_call",
"account": "0xAAaA000000000000000000000000000000000001",
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"amount": "-2499100000",
"amount_unit": "aave_scaled",
"amount_decimals": null,
"liquidation": {
"liquidator": "0xBBbB000000000000000000000000000000000002",
"debt_asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"debt_repaid": "2500000000",
"debt_decimals": 6,
"deficit_paired": false,
"before_debt_usd": null,
"interest_index": null,
"seized": [
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"amount": "656250000000000000",
"decimals": 18,
"bonus": null
}
],
"realized_bonus_bps": null,
"configured_bonus_bps": "500",
"note": "extracted from the event's own structured payload; amounts are in each asset's own token units. `configured_bonus_bps` is the ledger's bonus AT THIS EVENT's effective params; `realized_bonus_bps` would need event-time prices this service does not re-read, so it is null — never estimated."
}
},
{
"chain_id": 10,
"engine": "debt_manager",
"block_number": 154796490,
"block_time": null,
"tx_hash": "0x51f0b3e2a4c1d99e21b7a30e12cf5a2b9a4a7c1de00b53219a6f2f41c86a7702",
"log_index": 7,
"seq": 0,
"type": "borrow",
"raw_type": "borrow",
"account": "0xccCc000000000000000000000000000000000003",
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"symbol": "USDC",
"amount": "1199403000",
"amount_unit": "dm_normalized_debt",
"amount_decimals": null,
"liquidation": null
}
],
"next_cursor": "MTU0Nzk2NDkwLjB4NTFmMC4zLjA",
"notes": [
"`block_time` is null until the block's header is custodied — header time is chain-asserted or absent, never fabricated, and no database clock is ever substituted. Render the block number while it is null.",
"engine-scoped pages order by (block_number, tx_hash, log_index, seq) DESC — exact within one chain. Cross-engine pages order by custodied block_time DESC; rows without a custodied time sort AFTER every timed row (their order there is a deterministic tiebreak, NOT chronology — two chains' block heights are incomparable as time).",
"`amount` is the engine's own CUSTODIED accounting delta, verbatim, and `amount_unit` names its closed semantic unit: normalized debt units on the Debt Manager (USD-6 view = value x interest index / 1e18), ray-scaled token units on Aave. It is not a display-ready token amount, which is why `amount_decimals` is null.",
"a cross-engine page's untimed tail can shrink between pages while header backfill runs: a row acquiring its time moves into the timed section. Each page is internally exact; the feed is a live surface, not a pinned batch."
]
}A cursor-paginated read of the parameter ledger: Aave collateral configuration changes from param_history and Debt Manager configuration events, each pinned to its (effective_block, effective_log_index) and tx hash. The ledger is APPEND-ONLY: each row records only what its own event said. A field the event did not speak to is simply absent from fields — never back-filled.
prior is the prior value THE EVENT ITSELF carried, when it carried one; null otherwise. A prior is never reconstructed by ledger lookback into this field — the timeline shows what the chain said, and a consumer wanting the preceding value reads the preceding row.
Rows are ordered (effective_block, effective_log_index) DESC per engine, interleaved across engines by block DESC.
Restrict to one asset's parameter changes.
The next_cursor of the previous page, verbatim. OPAQUE. A malformed cursor is a 400.
curl -s "https://solventrisk.com/v1/params"
200 response · api/openapi.yaml — GET /v1/params 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"engine": null,
"asset": null,
"params": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"fields": [
{
"name": "ltv",
"value": "7800",
"address": null,
"prior": null,
"unit": "bps"
},
{
"name": "liq_threshold",
"value": "8100",
"address": null,
"prior": null,
"unit": "bps"
},
{
"name": "liq_bonus",
"value": "10500",
"address": null,
"prior": null,
"unit": "bps"
}
],
"effective_block": 25635600,
"effective_log_index": 12,
"source_event": "CollateralConfigurationChanged",
"tx_hash": "0x2b8a1f0e6c33269e9d8f01d1db494ca4cf693c99377f78f9628c1b74d5a1a859",
"block_time": "2026-07-29T09:57:00Z"
},
{
"engine": "debt_manager",
"chain_id": 10,
"asset": null,
"fields": [
{
"name": "borrow_apy",
"value": "50000000000000000",
"address": null,
"prior": "40000000000000000",
"unit": "per-second-1e18"
}
],
"effective_block": 154790000,
"effective_log_index": 3,
"source_event": "borrow_apy_set",
"tx_hash": "0x7702a4c1d99e21b7a30e12cf5a2b9a4a7c1de00b53219a6f2f41c86a51f0b3e2",
"block_time": null
}
],
"next_cursor": null,
"notes": [
"denominations are the ENGINE's own and are named per field: Aave publishes bps (1e4 scale); the Debt Manager's percent scale is 100e18. No cross-engine normalization exists.",
"`block_time` is null until the block's header is custodied — never fabricated."
]
}The retained price ledger for one asset address, grouped into one series per (chain_id, source) key. Every row carries its own validity verdict: quarantined rows (valid: false) are RETAINED and SERVED with their reasons — a quarantine is evidence, not garbage — and each series summarizes its quarantined block ranges so a renderer cannot miss them.
Downsampling (step) never averages: each returned point is an exact custodied row, taken every step blocks. In particular no served value is ever synthesized across a VALIDITY BOUNDARY — a quarantined observation never contributes to a valid-looking point, and a valid-invalid-valid sequence downsamples to exact rows from each segment, never to a blend that papers over the quarantine.
source_as_of is the chain-asserted as-of. Database insert time is never substituted; a row with no as-of says so with null.
The asset's address. Strictly validated; a malformed address is a 400.
Restrict to one price source key, verbatim (e.g. aaveoracle:0x..., priceproviderv2, chainlink:0x...).
Block stride. Serves every row whose block clears the previous served row by at least step — exact rows, never averages.
curl -s "https://solventrisk.com/v1/prices/0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee"
200 response · api/openapi.yaml — GET /v1/prices/{asset} 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"source": null,
"from_block": 25635500,
"to_block": null,
"step": null,
"chains": [
1,
10
],
"series": [
{
"chain_id": 1,
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"owner_engine": "aave_v3_etherfi",
"provenance": "adapter-output",
"decimals": 8,
"points": [
{
"block_number": 25635550,
"value": "399800000000",
"anchor_block": null,
"source_as_of": "2026-07-29T09:44:30Z",
"valid": false,
"invalid_reason": "unverifiable after a reorg: no surviving poll anchor covers this observation"
},
{
"block_number": 25635610,
"value": "400000000000",
"anchor_block": 25635609,
"source_as_of": "2026-07-29T09:56:30Z",
"valid": true,
"invalid_reason": ""
}
],
"quarantined_ranges": [
{
"from_block": 25635550,
"to_block": 25635550,
"rows": 1,
"reasons": [
{
"key": "unverifiable after a reorg: no surviving poll anchor covers this observation",
"count": 1
}
]
}
],
"note": "quarantined rows are retained and served with their reasons; they are never deleted and never smoothed into the valid series."
}
],
"notes": [
"an asset with no custodied observations answers with an empty `series` — an ANSWER about custody, not a claim that the asset has no price.",
"downsampling serves exact rows and never averages, least of all across a validity boundary."
]
}The COMMITTED scenario set as definitions: what each scenario shocks, on which primitive axes, for which engines, under which path assumption, and what it explicitly does NOT model.
This is CONFIGURATION, not batch data — the set is compiled into the binary — so the response carries no batch envelope and answers 200 with no servable batch in the database at all. That is the point of the route: the same definitions ride on every /v1/address/{addr}/stress response, but reading them from there requires naming an address and asking what would happen to it, and that route answers 503 before the materializer's first pass. A client rendering scenario controls needs the vocabulary, not a verdict about somebody's position.
engines is the set of engines a scenario is DEFINED for. An engine absent from it is outside the scenario's model — a property of the definition, and NOT the same statement as a withheld engine, which is a refusal and is named as one on the surfaces that evaluate. Collapsing the two would make an undefined cell and a censored cell look alike.
Shocks are EXACT rationals (factor_num/factor_den), never floats, and a 1/1 shock is a scenario whose information lives on another axis — a projection over time, or a market realization the oracles do not see.
Every id here is exactly what POST /v1/scenarios/{id}/run-book accepts; an id absent from this listing is a 404 there rather than a silently empty run.
The entries are byte-for-byte the entries a stress response's scenarios array carries, minus that array's per-address results — one serializer, asserted on the wire by cmd/api's suite, so the two surfaces cannot describe different scenarios.
The example below shows TWO of the committed set's entries (an asset_usd shock and the rate-axis projection); a served listing carries the whole set.
curl -s "https://solventrisk.com/v1/scenarios"
200 response · api/openapi.yaml — GET /v1/scenarios 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"scenario_config_version": "v1",
"scenarios": [
{
"id": "ethfi_minus_50",
"version": "v1",
"label": "ETHFI -50 percent",
"description": "Idiosyncratic own-ecosystem-token shock. ETHFI collateral is correlated with exactly the stress states in which liquidations happen, and sETHFI is composed from ETHFI, so both move on one axis.",
"path_assumption": "instantaneous mark at the shocked level; single-step, no path, no cascade feedback, no partial closes",
"engines": [
"debt_manager"
],
"shocks": [
{
"axis": "asset_usd",
"asset": "0xe0080d2F853ecDdbd81A643dC10DA075Df26fD3f",
"factor_num": 50,
"factor_den": 100
}
],
"out_of_model": [
"oracle lag and heartbeat behaviour during the move: the shock is applied as an instantaneous mark, while the real feeds update on deviation or heartbeat",
"deviation-trigger discreteness (a feed moves in rounds, not continuously)",
"liquidator liquidity, gas costs, execution latency and cascade dynamics",
"market correlations not mechanically implied by the read paths recorded in recon/derivation-notes.md",
"intra-sample price wicks: prices are 60-second point samples and an intra-interval spike is invisible by construction (D-012)",
"per-asset base_asset claims are the DEPLOYED composition graph read from PriceProviderV2.tokenConfig(token).baseAsset at the accept-r4 OP pin (block 154938071, hash 0xaf91dd4ba1975fc3b93e411586ce267892406ed8cb7152c5cefe1c368696c6bc, provider 0x44dd2372FE7B97C4B4D6a7d4DeCf72466485BAcB) - a hash-pinned SAMPLE with no continuity claim (chain-truth R3.1), fixture-welded in internal/risk/testdata/tokenconfig_accept_r4.json",
"the sETHFI/ETHFI accountant rate is held flat; a 50 percent ETHFI drawdown is assumed not to change the staking rate",
"reflexivity between the token price and the protocol's own risk parameters"
]
},
{
"id": "dm_rate_horizon_plus_200bps",
"version": "v1",
"label": "Debt Manager borrow APY +200bps (PROJECTION)",
"description": "A rate change does not move a spot health factor, so this ships as a closed-form HORIZON PROJECTION, never as a spot shock. The Debt Manager's interest index is linear per second and borrowApy is admin-set, so debt(t) is closed-form: debt0 + floor(debt0 x apy x dt / 100e18). Collateral prices are held flat. The Aave engine is excluded: its rates are utilization-driven and its book is residual dust.",
"path_assumption": "collateral prices held flat at the current sample; the APY steps once, at t=0, and holds for the whole horizon",
"engines": [
"debt_manager"
],
"shocks": [
{
"axis": "borrow_apy",
"factor_num": 1,
"factor_den": 1
}
],
"out_of_model": [
"oracle lag and heartbeat behaviour during the move: the shock is applied as an instantaneous mark, while the real feeds update on deviation or heartbeat",
"deviation-trigger discreteness (a feed moves in rounds, not continuously)",
"liquidator liquidity, gas costs, execution latency and cascade dynamics",
"market correlations not mechanically implied by the read paths recorded in recon/derivation-notes.md",
"intra-sample price wicks: prices are 60-second point samples and an intra-interval spike is invisible by construction (D-012)",
"borrower behaviour: no repayment, no top-up, no new borrowing over the horizon",
"the closed form floors ONCE on the debt, while the chain floors the INDEX and then the debt - the two differ by at most a couple of units of USD 6-dec, which is why this result is labeled PROJECTION and never gated exact",
"the Aave engine is excluded (utilization-driven rates over a residual dust book)",
"governance behaviour: the APY is admin-set and could move again inside the horizon"
]
}
],
"notes": [
"this is the COMMITTED set, compiled into the binary — configuration, not batch data. It carries no batch envelope and is servable before any batch exists; a definition here says what WOULD be evaluated, never what the book is.",
"the SAME definitions ride on every /v1/address/{addr}/stress response's `scenarios` array, serialized by the same code. That array carries per-address `results` in addition; nothing else differs, so the two surfaces cannot describe different scenarios.",
"every `id` here is exactly what POST /v1/scenarios/{id}/run-book accepts. An id absent from this listing is a 404 there, never a silently empty run.",
"`engines` is the set of engines a scenario is DEFINED for. An engine missing from it is out of the scenario's model — a property of the definition, and not the same statement as a withheld engine, which is a refusal and is named as one on the surfaces that evaluate.",
"shocks are EXACT rationals: -30% is `factor_num` 70 over `factor_den` 100, never 0.7. There are no floats in this pipeline. A shock of 1/1 is a scenario whose information lives on another axis — a projection over time, or a market realization the oracles do not see.",
"`out_of_model` is part of the definition, not a footnote: it names what the scenario does NOT model, and a shocked number read without it is a number read wrong.",
"`scenario_config_version` versions the set as a whole, so a client caching results has one token to invalidate on. Files that disagree report MIXED(...) rather than a silently chosen version."
]
}Book-wide aggregate stress: the named scenario from the COMMITTED set — the same eleven /v1/address/{addr}/stress serves, no arbitrary user scenarios — evaluated over every reconstructable position of the newest servable batch, reduced to per-engine aggregates.
POST because the evaluation is computed on request over the whole book; it writes nothing. The scenario id must name a committed scenario: anything else is a 404, never a silently empty run.
Deltas are labeled DELTA-ONLY: they are the scenario's own contribution, after minus before, over the positions in the run. Scenarios whose axis moves no oracle mark assert hfs_unchanged in market_realization; rate scenarios ship as PROJECTIONS with the same delta-only basis as the address-level surface. Engines whose whole book is withheld are named in excluded_engines and appear nowhere else — an absence with no name is exactly the silent hole this surface must not have.
ADDED 1.6.0, all additive: each aggregate carries hf_histogram (the distribution at that side of the shock, in the SAME buckets /v1/book serves — so BEFORE and AFTER can be read against each other) and collateral_by_asset (that side's collateral decomposed per asset, with holdings the engine does not count listed at a null value_usd rather than a zero). Each engine carries movers / movers_total / movers_note — the accounts the scenario moved, ranked by that engine's own definition of movement, bounded to the top 20 with the truncation stated rather than silent.
ADDED 1.7.0, additive: each engine carries hf_transitions, the BEFORE-to-AFTER flow of its POSITION ROWS over the same lanes the two histograms beside it are stated in. The two histograms cannot produce it — two marginals do not determine a joint, and a row that fell below 1.00 and another that rose above it cancel exactly in their difference — so the joint is computed on the server, which is the only place that holds both sides of the same row. Its margins ARE those two histograms, lane for lane; the two lanes past the buckets are infinite_count's and refused_count's populations, and the second of those is split by cause so a reader can tell which coverage field holds those rows. The same release CORRECTS RunBookHistogram.refused_count's description, which defined that tally as "positions carrying no comparator ... also in coverage.excluded" — a sentence that was wrong in both halves and, on this route's own example, pointed at an empty array.
A committed scenario id, e.g. weeth_market_depeg_oracles_held. An unknown id is a 404.
curl -s -X POST "https://solventrisk.com/v1/scenarios/weeth_market_depeg_oracles_held/run-book"
200 response · api/openapi.yaml — POST /v1/scenarios/{id}/run-book 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"batch": {
"id": 1,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 5,
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"refused_engines": [],
"flagged_count": 1,
"watermarks": [
{
"engine": "aave_param",
"chain_id": 1,
"last_block": 25635600,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1205,
"generation": 4,
"generation_open": false
}
},
{
"engine": "prices:poll:1",
"chain_id": 1,
"last_block": 25635610,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "prices:poll:10",
"chain_id": 10,
"last_block": 154796540,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
}
],
"supersession": {
"superseded": false,
"legs": [],
"note": "a superseded batch is still served: the flag is the contract and it heals at the next materializer pass (design spec §4). The legs are evaluated against a LIVE read of the cursor and epoch tables inside the same snapshot as the database clock."
}
},
"scenario_config_version": "v1",
"scenario_id": "weeth_market_depeg_oracles_held",
"scenario_version": "v1",
"label": "weETH market depeg to 0.95 (oracles held)",
"description": "weETH trades 5 percent below its redemption value while the redemption rate is unchanged. This is EXPLICITLY NOT a health-factor event: neither protocol reads a secondary-market weETH price, so every health factor is bit-identical and no position becomes liquidatable. Its output is liquidator-side - execution shortfall on seized collateral and bad debt at liquidation. Implemented on the market-realization axis, never as a price shock.",
"path_assumption": "oracle marks held exactly; market value is a separate axis applied only to realized proceeds",
"shocks": [],
"out_of_model": [
"oracle lag and heartbeat behaviour during the move: the shock is applied as an instantaneous mark, while the real feeds update on deviation or heartbeat",
"deviation-trigger discreteness (a feed moves in rounds, not continuously)",
"liquidator liquidity, gas costs, execution latency and cascade dynamics",
"market correlations not mechanically implied by the read paths recorded in recon/derivation-notes.md",
"intra-sample price wicks: prices are 60-second point samples and an intra-interval spike is invisible by construction (D-012)",
"seizure is modeled PRO-RATA over a position's counted collateral; a real liquidator picks a preference order and would take the least-impaired asset first, so this is neutral rather than conservative",
"market depth: the 0.95 ratio is a flat haircut, not a slippage curve",
"the possibility that a sustained market depeg eventually becomes a rate event"
],
"applied_shocks": [],
"held_flat": [
{
"asset": "0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF",
"chain_id": 10,
"source": "priceproviderv2",
"value": "4000000000"
},
{
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chain_id": 1,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"value": "100000000"
},
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"chain_id": 1,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"value": "400000000000"
}
],
"engines": [
{
"engine": "aave_v3_etherfi",
"usd_decimals": 8,
"before": {
"accounts": 1,
"eligible_accounts": 0,
"total_collateral_usd": "800000000000",
"total_debt_usd": "600000000000",
"eligible_debt_usd": "0",
"collateral_at_risk_usd": "0",
"bad_debt_usd": "0",
"hf_histogram": {
"comparator": "hf_wad",
"wad_scale": "1000000000000000000",
"buckets": [
{
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000",
"count": 0
},
{
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000",
"count": 0
},
{
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000",
"count": 0
},
{
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000",
"count": 1
},
{
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000",
"count": 0
},
{
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000",
"count": 0
},
{
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000",
"count": 0
},
{
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null,
"count": 0
}
],
"infinite_count": 0,
"refused_count": 1,
"note": "buckets are the pool's own health-factor WAD. Aave liquidates STRICTLY BELOW 1e18, so `< 1.00` is the eligible set and exactly 1.00 is healthy. This is ONE SIDE of the shock over the positions in the run, in the SAME buckets /v1/book's histogram serves; the after-side is bucketed on the SHOCKED states. `infinite_count` is accounts with no debt and `refused_count` is the positions this run measured on NEITHER side — both are counted here rather than dropped, so the buckets plus these two account for the whole run. `hf_transitions` states that same population as its last lane and splits it by cause."
},
"collateral_by_asset": [
{
"asset": "0x0000000000000000000000000000000000000Bad",
"decimals": 18,
"amount": "5000000000000000000",
"value_usd": null,
"unpriced": true,
"note": "UNPRICED: the account holds this token and NO price witness describes it on this side, so its USD value is UNKNOWABLE — not zero. `amount` is exact; none of this holding is inside `total_collateral_usd`."
},
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"decimals": 18,
"amount": "2000000000000000000",
"value_usd": "800000000000",
"unpriced": false,
"note": "COUNTED: this value is inside `total_collateral_usd` on this side. The counted entries sum to it EXACTLY."
}
]
},
"after": {
"accounts": 1,
"eligible_accounts": 0,
"total_collateral_usd": "800000000000",
"total_debt_usd": "600000000000",
"eligible_debt_usd": "0",
"collateral_at_risk_usd": "0",
"bad_debt_usd": "0",
"hf_histogram": {
"comparator": "hf_wad",
"wad_scale": "1000000000000000000",
"buckets": [
{
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000",
"count": 0
},
{
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000",
"count": 0
},
{
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000",
"count": 0
},
{
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000",
"count": 1
},
{
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000",
"count": 0
},
{
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000",
"count": 0
},
{
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000",
"count": 0
},
{
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null,
"count": 0
}
],
"infinite_count": 0,
"refused_count": 1,
"note": "buckets are the pool's own health-factor WAD. Aave liquidates STRICTLY BELOW 1e18, so `< 1.00` is the eligible set and exactly 1.00 is healthy. This is ONE SIDE of the shock over the positions in the run, in the SAME buckets /v1/book's histogram serves; the after-side is bucketed on the SHOCKED states. `infinite_count` is accounts with no debt and `refused_count` is the positions this run measured on NEITHER side — both are counted here rather than dropped, so the buckets plus these two account for the whole run. `hf_transitions` states that same population as its last lane and splits it by cause."
},
"collateral_by_asset": [
{
"asset": "0x0000000000000000000000000000000000000Bad",
"decimals": 18,
"amount": "5000000000000000000",
"value_usd": null,
"unpriced": true,
"note": "UNPRICED: the account holds this token and NO price witness describes it on this side, so its USD value is UNKNOWABLE — not zero. `amount` is exact; none of this holding is inside `total_collateral_usd`."
},
{
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"decimals": 18,
"amount": "2000000000000000000",
"value_usd": "800000000000",
"unpriced": false,
"note": "COUNTED: this value is inside `total_collateral_usd` on this side. The counted entries sum to it EXACTLY."
}
]
},
"hf_transitions": {
"comparator": "hf_wad",
"wad_scale": "1000000000000000000",
"lanes": [
{
"index": 0,
"kind": "bucket",
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000"
},
{
"index": 1,
"kind": "bucket",
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000"
},
{
"index": 2,
"kind": "bucket",
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000"
},
{
"index": 3,
"kind": "bucket",
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000"
},
{
"index": 4,
"kind": "bucket",
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000"
},
{
"index": 5,
"kind": "bucket",
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000"
},
{
"index": 6,
"kind": "bucket",
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000"
},
{
"index": 7,
"kind": "bucket",
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null
},
{
"index": 8,
"kind": "infinite",
"label": "no debt (unbounded)",
"lower_wad": null,
"upper_wad": null
},
{
"index": 9,
"kind": "unmeasured",
"label": "not measured",
"lower_wad": null,
"upper_wad": null
}
],
"outflows": [
{
"from": 0,
"cells": []
},
{
"from": 1,
"cells": []
},
{
"from": 2,
"cells": []
},
{
"from": 3,
"cells": [
{
"to": 3,
"rows": 1,
"debt_before_usd": "600000000000",
"debt_after_usd": "600000000000"
}
]
},
{
"from": 4,
"cells": []
},
{
"from": 5,
"cells": []
},
{
"from": 6,
"cells": []
},
{
"from": 7,
"cells": []
},
{
"from": 8,
"cells": []
},
{
"from": 9,
"cells": [
{
"to": 9,
"rows": 1,
"debt_before_usd": null,
"debt_after_usd": null
}
]
}
],
"from_rows": [
0,
0,
0,
1,
0,
0,
0,
0,
0,
1
],
"to_rows": [
0,
0,
0,
1,
0,
0,
0,
0,
0,
1
],
"total_rows": 2,
"measured_rows": 1,
"unmeasured_rows": 1,
"unmeasured_refused_in_batch_rows": 1,
"unmeasured_excluded_by_this_layer_rows": 0,
"held_rows": 1,
"lane_changed_rows": 0,
"note": "ROWS ARE THE BEFORE LANE AND COLUMNS ARE THE AFTER LANE, over the POSITION ROWS of this engine in this run. Every count here counts rows, the same unit `coverage.batch_positions` uses, never distinct addresses; on this batch one row IS one account on this engine, and the server CHECKED that over every row in this matrix, measured and unmeasured alike, rather than assuming it. Lanes 0 to 7 are the SAME buckets, on the SAME comparator and the SAME edges, that the two `hf_histogram`s beside them serve. Lane 8 is rows with NO DEBT, unbounded and never a bucket, and IS `infinite_count`'s population. Lane 9 is rows this run measured on NEITHER side, and IS `refused_count`'s population. `from_rows` IS the before histogram and `to_rows` IS the after histogram, lane for lane: a weld, not a hope. A cell absent from `cells` holds ZERO rows. The dense `lanes`, `outflows`, `from_rows` and `to_rows` arrays are what make that omission complete. Lane 9's rows reached no arithmetic here: `unmeasured_refused_in_batch_rows` of them riskd refused (counted in `coverage.refused_in_batch`, and served per row with its refusal code by `/v1/positions` and `/v1/address/{addr}`), and `unmeasured_excluded_by_this_layer_rows` of them this service could not rebuild or verify (listed per row in `coverage.excluded`). Their persisted numbers still exist on those surfaces. THIS RUN measured none of them, which is why their cell's two debts are null and never \"0\". Debt is in THIS engine's own 8-decimal unit and is never summed with another engine's. The two sides are SEPARATE figures because Aave's debt is a PRICED sum a shock can move, while the Debt Manager's is USD-normalized and copied across the shock unchanged. A no-debt row's \"0\" is a knowable zero; a null is an unknowable. `lane_changed_rows` counts rows whose LANE changed. A move of any size INSIDE one lane is not counted and a move of one wei across an edge is, so it follows the histogram's edges rather than the scenario's magnitude. It is NOT `movers_total` (Aave ranks strict health-factor drops; the Debt Manager counts eligibility flips) and NOT `newly_eligible_accounts` (a signed net), and it is not a crossing count of any particular edge: derive that from the cells. Under this scenario no row can enter or leave lane 8 or lane 9, so every lane change here is between buckets. On the Debt Manager these lanes are the exact rational maxBorrowLT/borrowings, a DISCLOSURE and not the liquidation verdict: take eligibility from `newly_eligible_accounts` and `movers`. When `measured_rows` is 0 both `held_rows` and `lane_changed_rows` are NULL rather than 0, because a zero there would claim a measurement nobody made. `total_rows` is THIS ENGINE's whole book in this run. It is not `coverage.batch_positions`, which counts the whole batch including engines this scenario does not cover, and summing it across engines does not give `coverage.in_book`: a withheld engine's rebuildable rows are inside `coverage.in_book` with no `engines[]` entry at all, and this engine's unmeasured rows are inside `total_rows` and outside `coverage.in_book`. The two differences can cancel on a given book, so an equality seen once is not a law."
},
"newly_eligible_accounts": 0,
"eligible_debt_delta_usd": "0",
"bad_debt_delta_usd": "0",
"movers": [],
"movers_total": 0,
"movers_note": "RANKED BY HEALTH-FACTOR DROP: before minus after, in the pool's own WAD, largest drop first. Only accounts whose health factor STRICTLY DROPPED are movers. An account with no debt has an unbounded health factor on either side, so it has no drop to rank and is not counted here — it is not a quiet zero. `movers` carries all 0 of them.",
"market_realization": {
"hfs_unchanged": true,
"execution_shortfall_usd": "0",
"bad_debt_at_liquidation_usd": "0",
"usd_decimals": 8,
"seizure_model": "pro-rata-over-counted-collateral",
"note": "market value is NOT an oracle mark: this scenario moves NO health factor (`hfs_unchanged` asserts it, computed not promised). The output is the gap the protocol is not seeing, under the disclosed seizure model."
},
"projection": null,
"note": "oracle marks held: before and after aggregates are identical by construction; the shortfall axis is where this scenario's information lives."
},
{
"engine": "debt_manager",
"usd_decimals": 6,
"before": {
"accounts": 1,
"eligible_accounts": 1,
"total_collateral_usd": "4000000000",
"total_debt_usd": "4620000000",
"eligible_debt_usd": "4620000000",
"collateral_at_risk_usd": "4000000000",
"bad_debt_usd": "659603961",
"hf_histogram": {
"comparator": "hf_num/hf_den",
"wad_scale": "1000000000000000000",
"buckets": [
{
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000",
"count": 1
},
{
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000",
"count": 0
},
{
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000",
"count": 0
},
{
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000",
"count": 0
},
{
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000",
"count": 0
},
{
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000",
"count": 0
},
{
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000",
"count": 0
},
{
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null,
"count": 0
}
],
"infinite_count": 0,
"refused_count": 1,
"note": "the Debt Manager has no health-factor wad: its liquidation test is the strict boolean `debt > maxBorrowLT`. These buckets are the EXACT rational maxBorrowLT/borrowings, a disclosure only — take eligibility from `liquidatable_positions`. This is ONE SIDE of the shock over the positions in the run, in the SAME buckets /v1/book's histogram serves; the after-side is bucketed on the SHOCKED states. `infinite_count` is accounts with no debt and `refused_count` is the positions this run measured on NEITHER side — both are counted here rather than dropped, so the buckets plus these two account for the whole run. `hf_transitions` states that same population as its last lane and splits it by cause."
},
"collateral_by_asset": [
{
"asset": "0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF",
"symbol": "weETH",
"decimals": 18,
"amount": "1000000000000000000",
"value_usd": "4000000000",
"unpriced": false,
"note": "COUNTED: this value is inside `total_collateral_usd` on this side. The counted entries sum to it EXACTLY."
}
]
},
"after": {
"accounts": 1,
"eligible_accounts": 1,
"total_collateral_usd": "4000000000",
"total_debt_usd": "4620000000",
"eligible_debt_usd": "4620000000",
"collateral_at_risk_usd": "4000000000",
"bad_debt_usd": "659603961",
"hf_histogram": {
"comparator": "hf_num/hf_den",
"wad_scale": "1000000000000000000",
"buckets": [
{
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000",
"count": 1
},
{
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000",
"count": 0
},
{
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000",
"count": 0
},
{
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000",
"count": 0
},
{
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000",
"count": 0
},
{
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000",
"count": 0
},
{
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000",
"count": 0
},
{
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null,
"count": 0
}
],
"infinite_count": 0,
"refused_count": 1,
"note": "the Debt Manager has no health-factor wad: its liquidation test is the strict boolean `debt > maxBorrowLT`. These buckets are the EXACT rational maxBorrowLT/borrowings, a disclosure only — take eligibility from `liquidatable_positions`. This is ONE SIDE of the shock over the positions in the run, in the SAME buckets /v1/book's histogram serves; the after-side is bucketed on the SHOCKED states. `infinite_count` is accounts with no debt and `refused_count` is the positions this run measured on NEITHER side — both are counted here rather than dropped, so the buckets plus these two account for the whole run. `hf_transitions` states that same population as its last lane and splits it by cause."
},
"collateral_by_asset": [
{
"asset": "0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF",
"symbol": "weETH",
"decimals": 18,
"amount": "1000000000000000000",
"value_usd": "4000000000",
"unpriced": false,
"note": "COUNTED: this value is inside `total_collateral_usd` on this side. The counted entries sum to it EXACTLY."
}
]
},
"hf_transitions": {
"comparator": "hf_num/hf_den",
"wad_scale": "1000000000000000000",
"lanes": [
{
"index": 0,
"kind": "bucket",
"label": "< 0.90",
"lower_wad": null,
"upper_wad": "900000000000000000"
},
{
"index": 1,
"kind": "bucket",
"label": "0.90 – 1.00",
"lower_wad": "900000000000000000",
"upper_wad": "1000000000000000000"
},
{
"index": 2,
"kind": "bucket",
"label": "1.00 – 1.05",
"lower_wad": "1000000000000000000",
"upper_wad": "1050000000000000000"
},
{
"index": 3,
"kind": "bucket",
"label": "1.05 – 1.10",
"lower_wad": "1050000000000000000",
"upper_wad": "1100000000000000000"
},
{
"index": 4,
"kind": "bucket",
"label": "1.10 – 1.25",
"lower_wad": "1100000000000000000",
"upper_wad": "1250000000000000000"
},
{
"index": 5,
"kind": "bucket",
"label": "1.25 – 1.50",
"lower_wad": "1250000000000000000",
"upper_wad": "1500000000000000000"
},
{
"index": 6,
"kind": "bucket",
"label": "1.50 – 2.00",
"lower_wad": "1500000000000000000",
"upper_wad": "2000000000000000000"
},
{
"index": 7,
"kind": "bucket",
"label": ">= 2.00",
"lower_wad": "2000000000000000000",
"upper_wad": null
},
{
"index": 8,
"kind": "infinite",
"label": "no debt (unbounded)",
"lower_wad": null,
"upper_wad": null
},
{
"index": 9,
"kind": "unmeasured",
"label": "not measured",
"lower_wad": null,
"upper_wad": null
}
],
"outflows": [
{
"from": 0,
"cells": [
{
"to": 0,
"rows": 1,
"debt_before_usd": "4620000000",
"debt_after_usd": "4620000000"
}
]
},
{
"from": 1,
"cells": []
},
{
"from": 2,
"cells": []
},
{
"from": 3,
"cells": []
},
{
"from": 4,
"cells": []
},
{
"from": 5,
"cells": []
},
{
"from": 6,
"cells": []
},
{
"from": 7,
"cells": []
},
{
"from": 8,
"cells": []
},
{
"from": 9,
"cells": [
{
"to": 9,
"rows": 1,
"debt_before_usd": null,
"debt_after_usd": null
}
]
}
],
"from_rows": [
1,
0,
0,
0,
0,
0,
0,
0,
0,
1
],
"to_rows": [
1,
0,
0,
0,
0,
0,
0,
0,
0,
1
],
"total_rows": 2,
"measured_rows": 1,
"unmeasured_rows": 1,
"unmeasured_refused_in_batch_rows": 1,
"unmeasured_excluded_by_this_layer_rows": 0,
"held_rows": 1,
"lane_changed_rows": 0,
"note": "ROWS ARE THE BEFORE LANE AND COLUMNS ARE THE AFTER LANE, over the POSITION ROWS of this engine in this run. Every count here counts rows, the same unit `coverage.batch_positions` uses, never distinct addresses; on this batch one row IS one account on this engine, and the server CHECKED that over every row in this matrix, measured and unmeasured alike, rather than assuming it. Lanes 0 to 7 are the SAME buckets, on the SAME comparator and the SAME edges, that the two `hf_histogram`s beside them serve. Lane 8 is rows with NO DEBT, unbounded and never a bucket, and IS `infinite_count`'s population. Lane 9 is rows this run measured on NEITHER side, and IS `refused_count`'s population. `from_rows` IS the before histogram and `to_rows` IS the after histogram, lane for lane: a weld, not a hope. A cell absent from `cells` holds ZERO rows. The dense `lanes`, `outflows`, `from_rows` and `to_rows` arrays are what make that omission complete. Lane 9's rows reached no arithmetic here: `unmeasured_refused_in_batch_rows` of them riskd refused (counted in `coverage.refused_in_batch`, and served per row with its refusal code by `/v1/positions` and `/v1/address/{addr}`), and `unmeasured_excluded_by_this_layer_rows` of them this service could not rebuild or verify (listed per row in `coverage.excluded`). Their persisted numbers still exist on those surfaces. THIS RUN measured none of them, which is why their cell's two debts are null and never \"0\". Debt is in THIS engine's own 6-decimal unit and is never summed with another engine's. The two sides are SEPARATE figures because Aave's debt is a PRICED sum a shock can move, while the Debt Manager's is USD-normalized and copied across the shock unchanged. A no-debt row's \"0\" is a knowable zero; a null is an unknowable. `lane_changed_rows` counts rows whose LANE changed. A move of any size INSIDE one lane is not counted and a move of one wei across an edge is, so it follows the histogram's edges rather than the scenario's magnitude. It is NOT `movers_total` (Aave ranks strict health-factor drops; the Debt Manager counts eligibility flips) and NOT `newly_eligible_accounts` (a signed net), and it is not a crossing count of any particular edge: derive that from the cells. Under this scenario no row can enter or leave lane 8 or lane 9, so every lane change here is between buckets. On the Debt Manager these lanes are the exact rational maxBorrowLT/borrowings, a DISCLOSURE and not the liquidation verdict: take eligibility from `newly_eligible_accounts` and `movers`. When `measured_rows` is 0 both `held_rows` and `lane_changed_rows` are NULL rather than 0, because a zero there would claim a measurement nobody made. `total_rows` is THIS ENGINE's whole book in this run. It is not `coverage.batch_positions`, which counts the whole batch including engines this scenario does not cover, and summing it across engines does not give `coverage.in_book`: a withheld engine's rebuildable rows are inside `coverage.in_book` with no `engines[]` entry at all, and this engine's unmeasured rows are inside `total_rows` and outside `coverage.in_book`. The two differences can cancel on a given book, so an equality seen once is not a law."
},
"newly_eligible_accounts": 0,
"eligible_debt_delta_usd": "0",
"bad_debt_delta_usd": "0",
"movers": [],
"movers_total": 0,
"movers_note": "RANKED BY THE DEBT THAT BECAME ELIGIBLE: only accounts whose Debt Manager eligibility FLIPPED false -> true under this scenario are movers, ranked by their debt in this engine's 6-decimal USD, largest first. The Debt Manager has no health-factor wad, so `hf_before_num/den` and `hf_after_num/den` are the EXACT rational maxBorrowLT/borrowings, a disclosure only. `movers_total` counts flips to eligible ONLY, so it is not `newly_eligible_accounts`, which is a NET count and also subtracts any flip back to healthy. `movers` carries all 0 of them.",
"market_realization": {
"hfs_unchanged": true,
"execution_shortfall_usd": "200000000",
"bad_debt_at_liquidation_usd": "820000000",
"usd_decimals": 6,
"seizure_model": "pro-rata-over-counted-collateral",
"note": "market value is NOT an oracle mark: this scenario moves NO health factor (`hfs_unchanged` asserts it, computed not promised). The output is the gap the protocol is not seeing, under the disclosed seizure model."
},
"projection": null,
"note": "oracle marks held: before and after aggregates are identical by construction; the shortfall axis is where this scenario's information lives."
}
],
"excluded_engines": [],
"coverage": {
"batch_positions": 4,
"in_book": 2,
"refused_in_batch": 2,
"excluded_by_this_layer": 0,
"excluded": [],
"withheld_engines": [],
"stress_coverage_is_full": true,
"note": "every position the batch carries is on the wire. `excluded` lists positions this layer could not rebuild into the pure library's input form; `withheld_engines` lists ENGINES whose whole book is refused and therefore absent from the stress and waterfall arithmetic entirely. `stress_coverage_is_full` accounts for both — it is a book-wide claim, not a per-position one."
},
"notes": [
"aggregates are per engine in each engine's OWN unit and decimals; they are never summed across engines.",
"deltas are DELTA-ONLY: after minus before, the scenario's own contribution over the positions in the run.",
"eligibility, collateral-at-risk and bad-debt measures are the SAME arithmetic as /v1/book's unshocked waterfall point, evaluated on each side of the shock."
]
}ADDED 1.7.0, additive. The named committed scenarios — the same set GET /v1/scenarios publishes — evaluated against a SINGLE batch, resolved once inside one database snapshot before any arithmetic runs.
The point is not speed, though it is roughly two to three times faster than the same ids fetched one at a time. The point is that a cross-scenario comparison is TRUE BY THE SHAPE OF THIS RESPONSE: one batch, one census, one envelope, N results. A serial sweep of the committed set takes about as long as the interval between materializations, so it straddles a batch boundary roughly half the time — and a comparison assembled from bodies measured on two different books is not the comparison that was asked for.
ALL OR NOTHING. There is no partial 200 and no per-scenario refusal register. An id this deployment does not publish, a duplicate, a malformed id or an over-cap set refuses the WHOLE request before any batch read, naming every offender; any arithmetic refusal is a 500 for the whole set. In a 200, requested_scenario_ids and results[].scenario_id are the same multiset, which is the strongest membership law available: one line to check, impossible to satisfy while hiding a hole.
WHAT IT DOES NOT CARRY. This is a SUMMARY: no histograms, no collateral_by_asset, no movers, no held_flat values, no out_of_model. Drill-down is POST /v1/scenarios/{id}/run-book's job. out_of_model in particular is published verbatim and versioned on GET /v1/scenarios, and scenario_id + scenario_version + scenario_config_version make that join exact — reading a shocked number without it is reading it wrong.
WHAT IT DOES CARRY, AND WHY. Every zero on this surface carries a published cause. Three committed scenarios produce all-zero deltas for three DIFFERENT reasons — an oracle snap band that swallowed the move, a definition that declared all its shocks at the identity factor 1/1, and a projection that ran no spot pass at all — and without shock_reach all three render as the same three zero-length bars. An engine with nothing measurable is a NAMED ABSENCE in unmeasurable_engines rather than a row of zeros with a "0" denominator. The movement count carries its own denominator, because hf_dropped_accounts: 0 beside accounts: 46 reads as "no health factor dropped" when the truth can be "44 of the 46 carry no health factor to drop".
COST AND CONCURRENCY. The limiter is charged one token per scenario (set_run_token_cost_per_scenario on /v1/meta), 1 in the middleware and the remainder in the handler after validation, so a malformed or unknown-id request costs one token rather than N. At most max_inflight_set_runs evaluations run at once; the overflow is refused IMMEDIATELY with 503 set_run_busy rather than queued.
curl -s -X POST "https://solventrisk.com/v1/scenarios/run-book-set"
200 response · api/openapi.yaml — POST /v1/scenarios/run-book-set 200 example (verbatim)
{
"batch": {
"age_seconds": 5,
"computed_at": "2026-07-29T10:00:00Z",
"flagged_count": 1,
"id": 1,
"position_count": 4,
"producer": "riskd",
"refused_count": 2,
"refused_engines": [],
"status": "complete",
"supersession": {
"legs": [],
"note": "a superseded batch is still served: the flag is the contract and it heals at the next materializer pass (design spec §4). The legs are evaluated against a LIVE read of the cursor and epoch tables inside the same snapshot as the database clock.",
"superseded": false
},
"watermarks": [
{
"acked_epoch": 0,
"chain_id": 1,
"engine": "aave_param",
"last_block": 25635600,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"acked_epoch": 0,
"chain_id": 1,
"engine": "aave_v3_etherfi",
"last_block": 25635618,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"acked_epoch": 0,
"chain_id": 10,
"engine": "debt_manager",
"last_block": 154796552,
"max_epoch_at_compute": 0,
"sweep": {
"age_seconds": 1205,
"failed": 1,
"generation": 4,
"generation_open": false,
"max_updated_at": "2026-07-29T09:40:00Z",
"rows": 3,
"success_sum": "309593004"
}
},
{
"acked_epoch": 0,
"chain_id": 1,
"engine": "prices:poll:1",
"last_block": 25635610,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"acked_epoch": 0,
"chain_id": 10,
"engine": "prices:poll:10",
"last_block": 154796540,
"max_epoch_at_compute": 0,
"sweep": null
}
]
},
"coverage": {
"batch_positions": 4,
"book_is_measurable": true,
"engines": [
{
"engine": "aave_v3_etherfi",
"measurable": 1,
"positions_in_batch": 2,
"refused_in_batch": 1,
"unrebuildable": 0,
"withheld": false
},
{
"engine": "debt_manager",
"measurable": 1,
"positions_in_batch": 2,
"refused_in_batch": 1,
"unrebuildable": 0,
"withheld": false
}
],
"excluded": [],
"excluded_by_this_layer": 0,
"in_book": 2,
"note": "THIS CENSUS IS BOOK-SCOPED, not run-scoped: `in_book` is every position THIS LAYER rebuilt over the WHOLE batch, whatever any scenario's model reaches — which is why it is a different component from /v1/book's `coverage`, whose `in_book` counts one run. Every position row is classified by the POSITIVE predicate of the counter it feeds, into exactly one of three classes, so `batch_positions == in_book + refused_in_batch + excluded_by_this_layer` EXACTLY; a row matching none of them refuses the whole request with a named 500 rather than being served under an equality it breaks. `refused_in_batch` is riskd's own refusals, served per row by /v1/positions and /v1/address/{addr}; `excluded_by_this_layer` is rows this service could not rebuild or verify, listed in `excluded`. Neither is ever reconciled against a run-book histogram's `refused_count`, which counts a WIDER population (both of these plus rebuilt rows carrying no comparator) under a label naming only the third.",
"refused_in_batch": 2
},
"evaluation": {
"freshness": "still_newest",
"newest_servable_batch_id": 1,
"note": "Batch 1 was STILL the newest complete servable batch at `probed_at`. That is what was true when this response was built, never a promise about the reader's present.",
"probed_at": "2026-07-29T10:00:07Z",
"resolved_at": "2026-07-29T10:00:05Z",
"scenarios_evaluated": 3
},
"excluded_engines": [],
"notes": [
"ONE BATCH: every result here was measured against the single `batch` above, resolved once inside one database snapshot before any arithmetic ran. Cross-scenario comparison is therefore exact by the SHAPE of this response rather than by a client-side check over N independently resolved bodies.",
"aggregates are per engine in each engine's OWN unit and decimals; they are never summed, averaged or plotted on one axis across engines. `usd_decimals` on each engine row is that engine's scale.",
"deltas are DELTA-ONLY: after minus before, the scenario's own contribution over that scenario's own covered, non-withheld book. The sanctioned denominator is `total_debt_usd_before`; the after side is a different book and a ratio against it answers a question this surface does not ask.",
"`out_of_model` is deliberately NOT carried here: it is published verbatim and versioned on GET /v1/scenarios, which a client necessarily already has (that is where these ids came from), and `scenario_id` + `scenario_version` + `scenario_config_version` make the join exact. READING A SHOCKED NUMBER WITHOUT IT IS READING IT WRONG.",
"`coverage` is a census of the whole BATCH, not of any one run: it is a different component from /v1/book's `coverage` for exactly that reason. `shock_reach` on each result says whether the declared shock reached a mark at all and, when it did not, WHICH cause held it — a zero delta on this surface always carries a published cause."
],
"requested_scenario_ids": [
"eth_minus_30",
"stable_depeg_0995_in_band",
"dm_composition_census"
],
"results": [
{
"covered_engines": [
"aave_v3_etherfi",
"debt_manager"
],
"engines": [
{
"accounts": 1,
"after_collateral_at_risk_usd": "560000000000",
"after_eligible_accounts": 1,
"bad_debt_delta_usd": "66666666667",
"before_bad_debt_usd": "0",
"before_collateral_at_risk_usd": "0",
"before_eligible_accounts": 0,
"before_eligible_debt_usd": "0",
"eligible_accounts_delta": 1,
"eligible_debt_delta_usd": "600000000000",
"engine": "aave_v3_etherfi",
"flipped_to_eligible": null,
"hf_dropped_accounts": 1,
"infinite_accounts": 0,
"market_realization": null,
"movement_excluded_accounts": 0,
"movement_rule": "hf_strictly_dropped",
"note": "UNIT: every money figure on this row is an exact integer in THIS engine's own 8-decimal USD and is never summed, averaged or plotted on one axis with another engine's. `movement_rule` is hf_strictly_dropped: `hf_dropped_accounts` counts accounts whose HEALTH FACTOR STRICTLY DROPPED under this scenario, and it is NOT a count of accounts that became eligible for liquidation. `flipped_to_eligible` is null here because this engine does not speak it. THE MOVEMENT COUNT'S DENOMINATOR IS `accounts` MINUS `movement_excluded_accounts` (1 minus 0 = 1), never `accounts`: An account with no debt has an unbounded health factor on either side, so it has no drop to rank and is not counted here — it is not a quiet zero. THE SANCTIONED DENOMINATOR is `total_debt_usd_before`. The share a reader may legitimately compute is `eligible_debt_delta_usd` over `total_debt_usd_before` — this scenario's contribution as a fraction of the book as it stands. Aave debt is PRICED and moves under a price shock, so `total_debt_usd_after` is a DIFFERENT book and a ratio against it answers a different question. COLLATERAL AT RISK is served as TWO SIDES and never as a delta: it carries no monotonicity invariant and legitimately FALLS when already-crossed accounts are worth less, so a difference on that axis is not a ranking key and is not offered as one.",
"projection": null,
"refused_in_batch_positions": 1,
"total_collateral_usd_after": "560000000000",
"total_collateral_usd_before": "800000000000",
"total_debt_usd_after": "600000000000",
"total_debt_usd_before": "600000000000",
"unrebuildable_positions": 0,
"usd_decimals": 8
},
{
"accounts": 1,
"after_collateral_at_risk_usd": "2800000000",
"after_eligible_accounts": 1,
"bad_debt_delta_usd": "1188118812",
"before_bad_debt_usd": "239603961",
"before_collateral_at_risk_usd": "4000000000",
"before_eligible_accounts": 1,
"before_eligible_debt_usd": "4200000000",
"eligible_accounts_delta": 0,
"eligible_debt_delta_usd": "0",
"engine": "debt_manager",
"flipped_to_eligible": 0,
"hf_dropped_accounts": null,
"infinite_accounts": 0,
"market_realization": null,
"movement_excluded_accounts": 0,
"movement_rule": "eligibility_flipped_false_to_true",
"note": "UNIT: every money figure on this row is an exact integer in THIS engine's own 6-decimal USD and is never summed, averaged or plotted on one axis with another engine's. `movement_rule` is eligibility_flipped_false_to_true: `flipped_to_eligible` counts eligibility flips FALSE to TRUE only, so it is not `eligible_accounts_delta`, which is a NET and also subtracts any flip back to healthy — five accounts flipping in and five flipping out is a net of 0 and is not \"nothing happened\". `hf_dropped_accounts` is null here because the Debt Manager has no health-factor wad. THE MOVEMENT COUNT'S DENOMINATOR IS `accounts` MINUS `movement_excluded_accounts` (1 minus 0 = 1), never `accounts`: The eligibility flip is testable for every account this run measured on both sides, so the only accounts excluded from it are before-side rows carrying no after-side state at all — it is not a quiet zero. THE SANCTIONED DENOMINATOR is `total_debt_usd_before`. The share a reader may legitimately compute is `eligible_debt_delta_usd` over `total_debt_usd_before` — this scenario's contribution as a fraction of the book as it stands. Aave debt is PRICED and moves under a price shock, so `total_debt_usd_after` is a DIFFERENT book and a ratio against it answers a different question. COLLATERAL AT RISK is served as TWO SIDES and never as a delta: it carries no monotonicity invariant and legitimately FALLS when already-crossed accounts are worth less, so a difference on that axis is not a ranking key and is not offered as one.",
"projection": null,
"refused_in_batch_positions": 1,
"total_collateral_usd_after": "2800000000",
"total_collateral_usd_before": "4000000000",
"total_debt_usd_after": "4200000000",
"total_debt_usd_before": "4200000000",
"unrebuildable_positions": 0,
"usd_decimals": 6
}
],
"label": "ETH -30 percent",
"note": "IDENTITY: this result is scenario eth_minus_30 at version v1, under the envelope's `scenario_config_version`. Join those three against GET /v1/scenarios for this scenario's `out_of_model`, which is NOT carried here and without which a shocked number is being read wrong. SHOCK REACH: EVERY MARK MOVED: the shock reached every mark this scenario's propagation matrix describes (2 of 2). An all-zero delta under THIS arm is a real finding about the book: the prices moved and the engines' arithmetic did not change these figures.",
"path_assumption": "instantaneous mark at the shocked level; single-step, no path, no cascade feedback, no partial closes",
"positions_answered": 2,
"positions_withheld": 0,
"scenario_id": "eth_minus_30",
"scenario_version": "v1",
"shock_reach": {
"applied_shocks": [
{
"after": "2800000000",
"asset": "0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF",
"base_snapped": false,
"before": "4000000000",
"cap_bound": false,
"chain_id": 10,
"factor_den": "100",
"factor_num": "70",
"snapped": false,
"source": "priceproviderv2"
},
{
"after": "280000000000",
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"base_snapped": false,
"before": "400000000000",
"cap_bound": false,
"chain_id": 1,
"factor_den": "100",
"factor_num": "70",
"snapped": false,
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f"
}
],
"declared_shocks": 1,
"declared_shocks_at_identity": 0,
"held_flat_assets": [
{
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chain_id": 1
},
{
"asset": "0x08c6F91e2B681FaF5e17227F2a44C307b3C1364C",
"chain_id": 10
},
{
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"chain_id": 10
},
{
"asset": "0x80Eede496655FB9047dd39d9f418d5483ED600df",
"chain_id": 10
},
{
"asset": "0x939778D83b46B456224A33Fb59630B11DEC56663",
"chain_id": 10
},
{
"asset": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
"chain_id": 10
}
],
"held_flat_marks": 6,
"marks_base_snapped": 0,
"marks_cap_bound": 0,
"marks_held_by_arithmetic": 0,
"marks_held_by_declared_factor": 0,
"marks_held_by_transform": 0,
"marks_moved": 2,
"marks_snapped": 0,
"note": "EVERY MARK MOVED: the shock reached every mark this scenario's propagation matrix describes (2 of 2). An all-zero delta under THIS arm is a real finding about the book: the prices moved and the engines' arithmetic did not change these figures.",
"reach": "every_mark_moved"
},
"shocks": [
{
"axis": "eth_usd",
"factor_den": 100,
"factor_num": 70
}
],
"unmeasurable_engines": [],
"withheld_engines": []
},
{
"covered_engines": [
"debt_manager"
],
"engines": [
{
"accounts": 1,
"after_collateral_at_risk_usd": "4000000000",
"after_eligible_accounts": 1,
"bad_debt_delta_usd": "0",
"before_bad_debt_usd": "239603961",
"before_collateral_at_risk_usd": "4000000000",
"before_eligible_accounts": 1,
"before_eligible_debt_usd": "4200000000",
"eligible_accounts_delta": 0,
"eligible_debt_delta_usd": "0",
"engine": "debt_manager",
"flipped_to_eligible": 0,
"hf_dropped_accounts": null,
"infinite_accounts": 0,
"market_realization": null,
"movement_excluded_accounts": 0,
"movement_rule": "eligibility_flipped_false_to_true",
"note": "UNIT: every money figure on this row is an exact integer in THIS engine's own 6-decimal USD and is never summed, averaged or plotted on one axis with another engine's. `movement_rule` is eligibility_flipped_false_to_true: `flipped_to_eligible` counts eligibility flips FALSE to TRUE only, so it is not `eligible_accounts_delta`, which is a NET and also subtracts any flip back to healthy — five accounts flipping in and five flipping out is a net of 0 and is not \"nothing happened\". `hf_dropped_accounts` is null here because the Debt Manager has no health-factor wad. THE MOVEMENT COUNT'S DENOMINATOR IS `accounts` MINUS `movement_excluded_accounts` (1 minus 0 = 1), never `accounts`: The eligibility flip is testable for every account this run measured on both sides, so the only accounts excluded from it are before-side rows carrying no after-side state at all — it is not a quiet zero. THE SANCTIONED DENOMINATOR is `total_debt_usd_before`. The share a reader may legitimately compute is `eligible_debt_delta_usd` over `total_debt_usd_before` — this scenario's contribution as a fraction of the book as it stands. Aave debt is PRICED and moves under a price shock, so `total_debt_usd_after` is a DIFFERENT book and a ratio against it answers a different question. COLLATERAL AT RISK is served as TWO SIDES and never as a delta: it carries no monotonicity invariant and legitimately FALLS when already-crossed accounts are worth less, so a difference on that axis is not a ranking key and is not offered as one.",
"projection": null,
"refused_in_batch_positions": 1,
"total_collateral_usd_after": "4000000000",
"total_collateral_usd_before": "4000000000",
"total_debt_usd_after": "4200000000",
"total_debt_usd_before": "4200000000",
"unrebuildable_positions": 0,
"usd_decimals": 6
}
],
"label": "Stable depeg to 0.995 (inside the snap band - a true no-op)",
"note": "IDENTITY: this result is scenario stable_depeg_0995_in_band at version v1, under the envelope's `scenario_config_version`. Join those three against GET /v1/scenarios for this scenario's `out_of_model`, which is NOT carried here and without which a shocked number is being read wrong. NOT COVERED: aave_v3_etherfi carry rows in this batch and this scenario's committed definition does not cover them, so they are absent from `covered_engines` entirely. That absence is by DEFINITION, never withholding. SHOCK REACH: NO MARK MOVED: every mark this scenario's propagation matrix describes came back at the value it started at, and at least one declared shock was NOT the identity factor. This zero is the PRICING TRANSFORMS' doing, not the book's: all 4 held mark(s) were pinned by the Debt Manager's stable snap band, by a snapped stable BASE, or by a bound price cap, each of them a property of the oracle path rather than of any position. Of the held marks: 4 pinned by a pricing transform (a stable snap, a snapped base or a bound cap). The `before_*` figures beside this are a true measurement of a real book and nothing was suppressed to hide the zero.",
"path_assumption": "instantaneous mark at the shocked level; single-step, no path, no cascade feedback, no partial closes",
"positions_answered": 1,
"positions_withheld": 0,
"scenario_id": "stable_depeg_0995_in_band",
"scenario_version": "v1",
"shock_reach": {
"applied_shocks": [
{
"after": "1000000",
"asset": "0x08c6F91e2B681FaF5e17227F2a44C307b3C1364C",
"base_snapped": true,
"before": "1000000",
"cap_bound": false,
"chain_id": 10,
"factor_den": "1000",
"factor_num": "995",
"snapped": false,
"source": "priceproviderv2"
},
{
"after": "1000000",
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"base_snapped": false,
"before": "1000000",
"cap_bound": false,
"chain_id": 10,
"factor_den": "1000",
"factor_num": "995",
"snapped": true,
"source": "priceproviderv2"
},
{
"after": "1000000",
"asset": "0x80Eede496655FB9047dd39d9f418d5483ED600df",
"base_snapped": false,
"before": "1000000",
"cap_bound": false,
"chain_id": 10,
"factor_den": "1000",
"factor_num": "995",
"snapped": true,
"source": "priceproviderv2"
},
{
"after": "1000000",
"asset": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
"base_snapped": false,
"before": "1000000",
"cap_bound": false,
"chain_id": 10,
"factor_den": "1000",
"factor_num": "995",
"snapped": true,
"source": "priceproviderv2"
}
],
"declared_shocks": 3,
"declared_shocks_at_identity": 0,
"held_flat_assets": [
{
"asset": "0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF",
"chain_id": 10
},
{
"asset": "0x939778D83b46B456224A33Fb59630B11DEC56663",
"chain_id": 10
}
],
"held_flat_marks": 2,
"marks_base_snapped": 1,
"marks_cap_bound": 0,
"marks_held_by_arithmetic": 0,
"marks_held_by_declared_factor": 0,
"marks_held_by_transform": 4,
"marks_moved": 0,
"marks_snapped": 3,
"note": "NO MARK MOVED: every mark this scenario's propagation matrix describes came back at the value it started at, and at least one declared shock was NOT the identity factor. This zero is the PRICING TRANSFORMS' doing, not the book's: all 4 held mark(s) were pinned by the Debt Manager's stable snap band, by a snapped stable BASE, or by a bound price cap, each of them a property of the oracle path rather than of any position. Of the held marks: 4 pinned by a pricing transform (a stable snap, a snapped base or a bound cap). The `before_*` figures beside this are a true measurement of a real book and nothing was suppressed to hide the zero.",
"reach": "no_mark_moved"
},
"shocks": [
{
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"axis": "stable_usd",
"factor_den": 1000,
"factor_num": 995
},
{
"asset": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
"axis": "stable_usd",
"factor_den": 1000,
"factor_num": 995
},
{
"asset": "0x80Eede496655FB9047dd39d9f418d5483ED600df",
"axis": "stable_usd",
"factor_den": 1000,
"factor_num": 995
}
],
"unmeasurable_engines": [],
"withheld_engines": []
},
{
"covered_engines": [
"debt_manager"
],
"engines": [
{
"accounts": 1,
"after_collateral_at_risk_usd": "4000000000",
"after_eligible_accounts": 1,
"bad_debt_delta_usd": "0",
"before_bad_debt_usd": "239603961",
"before_collateral_at_risk_usd": "4000000000",
"before_eligible_accounts": 1,
"before_eligible_debt_usd": "4200000000",
"eligible_accounts_delta": 0,
"eligible_debt_delta_usd": "0",
"engine": "debt_manager",
"flipped_to_eligible": 0,
"hf_dropped_accounts": null,
"infinite_accounts": 0,
"market_realization": null,
"movement_excluded_accounts": 0,
"movement_rule": "eligibility_flipped_false_to_true",
"note": "UNIT: every money figure on this row is an exact integer in THIS engine's own 6-decimal USD and is never summed, averaged or plotted on one axis with another engine's. `movement_rule` is eligibility_flipped_false_to_true: `flipped_to_eligible` counts eligibility flips FALSE to TRUE only, so it is not `eligible_accounts_delta`, which is a NET and also subtracts any flip back to healthy — five accounts flipping in and five flipping out is a net of 0 and is not \"nothing happened\". `hf_dropped_accounts` is null here because the Debt Manager has no health-factor wad. THE MOVEMENT COUNT'S DENOMINATOR IS `accounts` MINUS `movement_excluded_accounts` (1 minus 0 = 1), never `accounts`: The eligibility flip is testable for every account this run measured on both sides, so the only accounts excluded from it are before-side rows carrying no after-side state at all — it is not a quiet zero. THE SANCTIONED DENOMINATOR is `total_debt_usd_before`. The share a reader may legitimately compute is `eligible_debt_delta_usd` over `total_debt_usd_before` — this scenario's contribution as a fraction of the book as it stands. Aave debt is PRICED and moves under a price shock, so `total_debt_usd_after` is a DIFFERENT book and a ratio against it answers a different question. COLLATERAL AT RISK is served as TWO SIDES and never as a delta: it carries no monotonicity invariant and legitimately FALLS when already-crossed accounts are worth less, so a difference on that axis is not a ranking key and is not offered as one.",
"projection": null,
"refused_in_batch_positions": 1,
"total_collateral_usd_after": "4000000000",
"total_collateral_usd_before": "4000000000",
"total_debt_usd_after": "4200000000",
"total_debt_usd_before": "4200000000",
"unrebuildable_positions": 0,
"usd_decimals": 6
}
],
"label": "DM composition census - previously unclaimed assets, explicitly held",
"note": "IDENTITY: this result is scenario dm_composition_census at version v1, under the envelope's `scenario_config_version`. Join those three against GET /v1/scenarios for this scenario's `out_of_model`, which is NOT carried here and without which a shocked number is being read wrong. NOT COVERED: aave_v3_etherfi carry rows in this batch and this scenario's committed definition does not cover them, so they are absent from `covered_engines` entirely. That absence is by DEFINITION, never withholding. SHOCK REACH: DECLARED HOLD: all 8 of this scenario's shocks are the explicit identity factor 1/1. It asks for no price move, BY DECISION rather than by accident, and the three deltas are zero BY CONSTRUCTION. The cause is the DEFINITION: nothing about the oracle, the pricing transforms or the book's sensitivity may be inferred from these zeros. `applied_shocks` is normally NON-EMPTY here (1 row(s) on this book), because a matched price is recorded whatever the factor. The definition's own disclosure is its `path_assumption`, served beside this: no move is asserted: every shock in this census is the explicit factor 1/1, so each named mark is held at its current sample BY DECISION, disclosed in the applied-shock record rather than in the held-flat list",
"path_assumption": "no move is asserted: every shock in this census is the explicit factor 1/1, so each named mark is held at its current sample BY DECISION, disclosed in the applied-shock record rather than in the held-flat list",
"positions_answered": 1,
"positions_withheld": 0,
"scenario_id": "dm_composition_census",
"scenario_version": "v1",
"shock_reach": {
"applied_shocks": [
{
"after": "1000000",
"asset": "0x939778D83b46B456224A33Fb59630B11DEC56663",
"base_snapped": false,
"before": "1000000",
"cap_bound": false,
"chain_id": 10,
"factor_den": "1",
"factor_num": "1",
"snapped": false,
"source": "priceproviderv2"
}
],
"declared_shocks": 8,
"declared_shocks_at_identity": 8,
"held_flat_assets": [
{
"asset": "0x08c6F91e2B681FaF5e17227F2a44C307b3C1364C",
"chain_id": 10
},
{
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"chain_id": 10
},
{
"asset": "0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF",
"chain_id": 10
},
{
"asset": "0x80Eede496655FB9047dd39d9f418d5483ED600df",
"chain_id": 10
},
{
"asset": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
"chain_id": 10
}
],
"held_flat_marks": 5,
"marks_base_snapped": 0,
"marks_cap_bound": 0,
"marks_held_by_arithmetic": 0,
"marks_held_by_declared_factor": 1,
"marks_held_by_transform": 0,
"marks_moved": 0,
"marks_snapped": 0,
"note": "DECLARED HOLD: all 8 of this scenario's shocks are the explicit identity factor 1/1. It asks for no price move, BY DECISION rather than by accident, and the three deltas are zero BY CONSTRUCTION. The cause is the DEFINITION: nothing about the oracle, the pricing transforms or the book's sensitivity may be inferred from these zeros. `applied_shocks` is normally NON-EMPTY here (1 row(s) on this book), because a matched price is recorded whatever the factor. The definition's own disclosure is its `path_assumption`, served beside this: no move is asserted: every shock in this census is the explicit factor 1/1, so each named mark is held at its current sample BY DECISION, disclosed in the applied-shock record rather than in the held-flat list",
"reach": "all_shocks_declared_at_identity"
},
"shocks": [
{
"asset": "0x17bC8Ffd82b8a36e737Ca1141C025089589B915e",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
},
{
"asset": "0x4200000000000000000000000000000000000042",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
},
{
"asset": "0x939778D83b46B456224A33Fb59630B11DEC56663",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
},
{
"asset": "0xA519AfBc91986c0e7501d7e34968FEE51CD901aC",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
},
{
"asset": "0xDCB612005417Dc906fF72c87DF732e5a90D49e11",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
},
{
"asset": "0xE5d3854736e0D513aAE2D8D708Ad94d14Fd56A6a",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
},
{
"asset": "0xca5921DF65E2e1b0B98Ae91c0187BA80D4124898",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
},
{
"asset": "0xd83E3d560bA6F05094d9D8B3EB8aaEA571D1864E",
"axis": "asset_usd",
"factor_den": 1,
"factor_num": 1
}
],
"unmeasurable_engines": [],
"withheld_engines": []
}
],
"scenario_config_version": "v1",
"served_at": "2026-07-29T10:00:05Z"
}The manifest that binds THIS deployment to its evidence: build commit, schema version, registry fingerprint, algorithm revision, scenario config version, the newest servable batch's substrate digest and materialization key, the feeds registry hash, the last committed reconcile receipt's summary, and the committed probe records.
Everything here is either carried by the build, persisted by a batch, or read from a COMMITTED artifact. Nothing is measured at request time, and absent evidence is stated with null and a reason — never approximated. Committed artifacts name endpoints by environment variable only; this surface serves no endpoint URL and no DSN.
THE TWO SUBJECTS (AMENDMENT 1): the manifest separates proof_subject — the pinned, exactly-reproducible acceptance evidence (the committed reconcile receipt and the identity it speaks for) — from live_subject — the currently-serving batch's watermarked identity. They are never one identity: a live batch must never read as reconciled-exact, and the proof's exactness lives only at its pin. proof_subject.status encodes the receipt's own strict conjunction (see ProofSubject).
Answers 200 even when no batch is servable: the manifest describes the DEPLOYMENT, and substrate states batch absence with its reason.
curl -s "https://solventrisk.com/v1/evidence"
200 response · api/openapi.yaml — GET /v1/evidence 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"service": {
"name": "solvent-api",
"version": "748c09d1e2f3",
"schema_version": 16,
"algorithm_revision": 4,
"scenario_config_version": "v1",
"registry_fingerprint": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"seizure_model": "pro-rata-over-counted-collateral"
},
"commit": "748c09d1e2f3",
"proof_subject": {
"status": "accepted",
"detail": "",
"pin": "5f0b3e2a4c1d99e21b7a30e12cf5a2b9a4a7c1de00b53219a6f2f41c86a77025"
},
"live_subject": {
"status": "serving",
"reason": ""
},
"substrate": {
"batch_id": 1,
"materialization_key": "9a4a7c1de00b53219a6f2f41c86a77025f0b3e2a4c1d99e21b7a30e12cf5a2b9",
"substrate_digest": "b23c2c4c182300512dcefa20f0fbf3740ac24077271059e1bd32511fec5f7ab5",
"note": "the digest is computed over the batch's INPUTS — the risk rows, consulted prices and consulted collateral flags — so two batches with identical substrate digests were computed from identical evidence."
},
"feeds_registry": {
"path": "recon/feeds.json",
"registry_fingerprint": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"file_sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"note": "`registry_fingerprint` is computed over the ASSEMBLED registry (the identity the batches are bound to); `file_sha256` is the committed file's bytes as deployed."
},
"reconcile": {
"schema": "solvent.reconcile.drift-report/v1",
"result": "pass",
"exit_code": 0,
"finished_at": "2026-07-29T02:14:07Z",
"gated_rows": 87,
"gated_exact": 87,
"gated_drift": 0,
"advisory_rows": 21,
"welds": [
{
"engine": "debt_manager",
"rows_compared": 29,
"rows_exact": 29
},
{
"engine": "aave_v3_etherfi",
"rows_compared": 14,
"rows_exact": 14
}
],
"comparison_sha256": "5f0b3e2a4c1d99e21b7a30e12cf5a2b9a4a7c1de00b53219a6f2f41c86a77025",
"artifact_path": "roadmap/evidence/artifacts/w1-reconcile/drift-report.json",
"note": "read from the COMMITTED receipt artifact; the reconcile itself ran against a pinned block, not at request time."
},
"probe_records": [
{
"path": "recon/p3-probes.md",
"note": "probe records name endpoints by environment variable only — publishable by construction."
}
],
"notes": [
"this manifest is deploy-bound: every field is carried by the build, persisted by a batch, or read from a committed artifact. Nothing here is measured at request time."
]
}The permanent identity record of ONE risk batch: its materialization key, substrate digest, counts and per-engine aggregate rollups, plus a SERVABILITY verdict relative to the batch that is serving now. This is the drawer-pin permalink: a batch id captured from any surface can be resolved to the identity it had, for as long as retention keeps it.
Servability is a statement, not a filter:
newest_servable— this batch is the one every live surface is serving right now.superseded_retained— the batch is retained and COMPLETE, but a newer servable batch exists; its rows are immutable history.unservable_incomplete— the batch's row exists but fails the completeness predicate (a torn restore, a partial write); its aggregates are WITHHELD (null) rather than served as a plausible rollup of a book nobody may read.
A pruned or never-existing id is a 404 whose message DISCLOSES retention: batches are pruned on a retention window, and "not found" here means "not retained (or never existed)", never "there was no such materialization".
The batch id, as served by any batch envelope.
curl -s "https://solventrisk.com/v1/batches/1"
200 response · api/openapi.yaml — GET /v1/batches/{id} 200 example (verbatim)
{
"served_at": "2026-07-29T10:00:05Z",
"batch_id": 1,
"servability": "newest_servable",
"servability_note": "this batch is the newest servable batch — every live surface is serving it.",
"computed_at": "2026-07-29T10:00:00Z",
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"flagged_count": 1,
"materialization_key": "9a4a7c1de00b53219a6f2f41c86a77025f0b3e2a4c1d99e21b7a30e12cf5a2b9",
"substrate_digest": "b23c2c4c182300512dcefa20f0fbf3740ac24077271059e1bd32511fec5f7ab5",
"aggregates": [
{
"engine": "aave_v3_etherfi",
"value_decimals": 8,
"positions": 2,
"computed_positions": 1,
"refused_positions": 1,
"flagged_positions": 1,
"liquidatable_positions": 0,
"sweep": null,
"total_collateral": "800000000000",
"total_debt": "600000000000",
"refusal": null
},
{
"engine": "debt_manager",
"value_decimals": 6,
"positions": 2,
"computed_positions": 1,
"refused_positions": 1,
"flagged_positions": 0,
"liquidatable_positions": 1,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "464379000",
"max_updated_at": "2026-07-29T09:58:40Z",
"age_seconds": 85,
"generation": 12,
"generation_open": false
},
"total_collateral": "4000000000",
"total_debt": "4620000000",
"refusal": null
}
],
"notes": [
"this record is the batch's IDENTITY, not its rows: per-position pages are served batch-stable by /v1/positions while the batch is newest-servable.",
"a withheld engine's aggregate serves NULL totals and names its refusal — persisted zeros under a refusal mean WITHHELD, never an empty healthy book."
]
}Event names: snapshot (sent on every connection, including a reconnect, before any tick), batch (a new servable batch at a watermark vector — never "a new block"), degradation (a transition in the refusal / flag / supersession posture), unavailable (no servable batch). Heartbeats are SSE comment frames (: heartbeat <unix>) and carry no data.
Every data: payload conforms to StreamPayload.
curl -sN "https://solventrisk.com/v1/stream"
text/event-stream · no JSON sample exists (or is invented) for a stream. Event names: snapshot · batch · degradation · unavailable; heartbeats are SSE comment frames.
The status surface. It answers 200 even when no batch is servable — a status surface that goes dark exactly when something is wrong is not a status surface — and says so in batch_unavailable_reason.
curl -s "https://solventrisk.com/v1/meta"
200 response · packages/client-ts/test/fixtures/meta.json (contract-validated 200 body, byte-identical)
{
"served_at": "2026-07-29T10:00:00Z",
"service": {
"name": "solvent-api",
"version": "0.1.0",
"schema_version": 14,
"algorithm_revision": 4,
"scenario_config_version": "v1",
"registry_fingerprint": "sha256:fixture-registry-fingerprint",
"seizure_model": "pro-rata-over-counted-collateral"
},
"batch": {
"id": 1,
"computed_at": "2026-07-29T10:00:00Z",
"age_seconds": 0,
"producer": "riskd",
"status": "complete",
"position_count": 4,
"refused_count": 2,
"refused_engines": [],
"flagged_count": 1,
"watermarks": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "aave_param",
"chain_id": 1,
"last_block": 25635600,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "prices:poll:1",
"chain_id": 1,
"last_block": 25635610,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": {
"rows": 3,
"failed": 1,
"success_sum": "309593004",
"max_updated_at": "2026-07-29T09:40:00Z",
"age_seconds": 1200,
"generation": 4,
"generation_open": false
}
},
{
"engine": "prices:poll:10",
"chain_id": 10,
"last_block": 154796540,
"acked_epoch": 0,
"max_epoch_at_compute": 0,
"sweep": null
}
],
"supersession": {
"superseded": false,
"legs": [],
"note": "a superseded batch is still served: the flag is the contract and it heals at the next materializer pass (design spec §4). The legs are evaluated against a LIVE read of the cursor and epoch tables inside the same snapshot as the database clock."
}
},
"watermark_vector": [
{
"engine": "aave_v3_etherfi",
"chain_id": 1,
"last_block": 25635618,
"acked_epoch": 0,
"covered_from_block": 20625519,
"decoder_revision": 1,
"consumed_by_risk": true
},
{
"engine": "aave_param",
"chain_id": 1,
"last_block": 25635600,
"acked_epoch": 0,
"covered_from_block": 20625519,
"decoder_revision": 1,
"consumed_by_risk": true
},
{
"engine": "prices:poll:1",
"chain_id": 1,
"last_block": 25635610,
"acked_epoch": 0,
"covered_from_block": null,
"decoder_revision": 1,
"consumed_by_risk": true
},
{
"engine": "debt_manager",
"chain_id": 10,
"last_block": 154796552,
"acked_epoch": 0,
"covered_from_block": 118000000,
"decoder_revision": 1,
"consumed_by_risk": true
},
{
"engine": "prices:poll:10",
"chain_id": 10,
"last_block": 154796540,
"acked_epoch": 0,
"covered_from_block": null,
"decoder_revision": 1,
"consumed_by_risk": true
}
],
"reorg_posture": {
"max_epochs": [],
"superseded": false,
"legs": [],
"leg_names": [
"acked_epoch_moved",
"last_block_rewound",
"unacked_epoch_recorded"
],
"note": "the three legs are evaluated per STAMPED ENGINE against a live cursor read: one chain's rewind does not invalidate the other chain's book."
},
"prices": [
{
"chain_id": 1,
"asset": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"symbol": "weETH",
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"owner_engine": "prices:poll:1",
"provenance": "adapter-output",
"value": "999900000000",
"decimals": 8,
"block_number": 25635660,
"anchor_block": 25635660,
"observed_at": "2026-07-29T09:59:55Z",
"source_as_of": "2026-07-29T09:59:55Z",
"age_seconds": 5,
"valid": true,
"invalid_reason": "",
"quarantined_rows": 1,
"highest_quarantined_block": 25635510,
"is_valuation_witness": true,
"valuation_witness_note": "this key is what values an Aave position: only engine-exact and adapter-output provenance may do so."
},
{
"chain_id": 1,
"asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"source": "aaveoracle:0x43b64f28a678944e0655404b0b98e443851cc34f",
"owner_engine": "prices:poll:1",
"provenance": "adapter-output",
"value": "100000000",
"decimals": 8,
"block_number": 25635610,
"anchor_block": 25635610,
"observed_at": "2026-07-29T09:59:48Z",
"source_as_of": "2026-07-29T09:59:48Z",
"age_seconds": 12,
"valid": true,
"invalid_reason": "",
"quarantined_rows": 0,
"highest_quarantined_block": null,
"is_valuation_witness": true,
"valuation_witness_note": "this key is what values an Aave position: only engine-exact and adapter-output provenance may do so."
},
{
"chain_id": 10,
"asset": "0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF",
"symbol": "weETH",
"source": "priceproviderv2",
"owner_engine": "prices:poll:10",
"provenance": "engine-exact",
"value": "4000000000",
"decimals": 6,
"block_number": 154796540,
"anchor_block": 154796540,
"observed_at": "2026-07-29T09:59:30Z",
"source_as_of": "2026-07-29T09:59:30Z",
"age_seconds": 30,
"valid": true,
"invalid_reason": "",
"quarantined_rows": 0,
"highest_quarantined_block": null,
"is_valuation_witness": true,
"valuation_witness_note": "this key is what values an Aave position: only engine-exact and adapter-output provenance may do so."
}
],
"neutralized_prices": [
{
"owner_engine": "prices:poll:1",
"chain_id": 1,
"rows": 1,
"oldest_observed_at": "2026-07-29T09:00:00Z",
"newest_observed_at": "2026-07-29T09:00:00Z",
"highest_block": 25635510
}
],
"sweeps": [
{
"engine": "debt_manager",
"rows": 3,
"never_swept": 1,
"failed_since_success": 1,
"success": 1,
"note": "never_swept is collateral of UNKNOWN size, never zero: those accounts are REFUSED rather than valued at zero."
}
],
"sweep_never_refusals_in_batch": 1,
"heartbeat_provenance": [
{
"chain_id": 1,
"symbol": "weETH",
"proxy": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
"aggregator": "0x00c7A37B03690fb9f41b5C5AF8131735C7275446",
"heartbeat_seconds": 3600,
"grace_seconds": 1800,
"provenance_grade": "empirical-historical-with-qualifier",
"observed_max_gap_seconds": 3732,
"tested_budget_seconds": 5400,
"budget_refuted": false,
"basis": "B3 empirical scan: measured maximum publication gap 3732s EXCEEDS the published 3600s heartbeat and survives only inside the declared 1800s operator grace (tested budget 5400s), a QUALIFIER and not a pass. Never verified: a complete ledger's max gap is exact HISTORY and cannot certify the future."
},
{
"chain_id": 1,
"symbol": "USDC",
"proxy": "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6",
"aggregator": "0x789190466E21a8b78b8027866CBBDc151542A26C",
"heartbeat_seconds": 259200,
"grace_seconds": 43200,
"provenance_grade": "empirical-historical",
"observed_max_gap_seconds": 248460,
"tested_budget_seconds": 302400,
"budget_refuted": false,
"basis": "B3 empirical scan against the ACTIVE budget: the measured maximum publication gap 248460s sits within 302400s (259200s heartbeat + 43200s grace), the correction 09d496e landed after this same ledger REFUTED the retired published budget of 90000s. The retired budget stays refuted as history. Never verified: a complete ledger's max gap is exact HISTORY and cannot certify the future."
}
],
"constants": {
"confirmation_blocks": 5,
"price_poll_seconds": 60,
"dm_sweep_interval_seconds": 3600,
"dm_sweep_pass_seconds": 1980,
"dm_sweep_worst_case_seconds": 5580,
"price_budget_seconds": 180,
"price_ceiling_seconds": 360,
"large_price_step_bps": 2000,
"rate_limit_requests_per_second": 20,
"rate_limit_burst": 40,
"max_set_run_scenarios": 24,
"set_run_token_cost_per_scenario": 1,
"max_inflight_set_runs": 2,
"sse_heartbeat_seconds": 15,
"note": "every value here is a POLICY OF THIS DEPLOYMENT or a published cadence, not a measurement. The price ceiling is 2x the budget (design spec §7, R = 2 x T_f): an input past it is REFUSED rather than served stale."
},
"disclosures": [
"Prices are 60-second samples: intra-interval wicks are invisible BY CONSTRUCTION, and no surface here implies otherwise (D-012).",
"There is exactly one price view per asset. For `priceproviderv2` inputs these numbers are exactly as manipulable as the Debt Manager itself — no more, no less; there is no second witness to disagree with.",
"Debt Manager collateral is sweep-dominated: worst case 5580 seconds behind, while its prices are 60 seconds. Never read a fresh price age as a fresh collateral age.",
"`rate_indexes` is as-of-last-ReserveDataUpdated, so a debt leg's index can trail the balances cursor badly. Every leg carries its own index as-of block.",
"Aave borrow-rate scenarios are excluded from the stress set (utilization-driven, residual dust book) and the Debt Manager rate axis is a labeled PROJECTION, never a spot health-factor shock.",
"A price sample used by a batch and neutralized afterwards is invisible to the three supersession legs; the batch recomputes within one materializer cadence (design spec §4, D-012 class).",
"Aave base values are 8-decimal and Debt Manager USD is 6-decimal. The two engines are NEVER summed into one number.",
"Refused positions are served WITH their reason and are counted in every aggregate's refusal count. This surface never omits a position it could not compute.",
"This service makes zero RPC calls. Every age is the database clock minus a durable stamp, so nothing here is measured against a chain head observed at request time."
]
}Error envelope
| Status | Response | Description |
|---|---|---|
| 400 | BadRequest | The request was malformed. |
| 404 | NotFound | No such route. |
| 409 | BatchSuperseded | The cursor is bound to a batch that is no longer the newest servable batch. The client restarts pagination from page one against the fresh batch — an honest restart. A page silently mixing rows from two materializations is exactly what this status exists to prevent. |
| 429 | RateLimited | The per-client token bucket is empty. |
| 500 | InternalError | The service failed to build the response. The message is sanitized of endpoint URLs and DSNs. |
| 503 | NoBatch | No complete risk batch is available. This is a statement about the SERVICE, not a claim that the book is empty. |
BadRequest body · packages/client-ts/test/fixtures/errors/bad-request.json (contract-validated)
{
"error": {
"code": "bad_request",
"message": "addr must be a 0x-prefixed 20-byte address"
}
}NotFound body · packages/client-ts/test/fixtures/errors/not-found.json (contract-validated)
{
"error": {
"code": "not_found",
"message": "no such route: this API serves /v1/book, /v1/address/{addr}, /v1/address/{addr}/stress, /v1/observatory, /v1/stream and /v1/meta"
}
}BatchSuperseded body · api/openapi.yaml — components.responses.BatchSuperseded example (verbatim)
{
"error": {
"code": "batch_superseded",
"message": "the cursor was minted against batch 1, which is no longer the newest servable batch; restart pagination",
"cursor_batch_id": 1,
"current_batch_id": 2
}
}RateLimited body · packages/client-ts/test/fixtures/errors/rate-limited.json (contract-validated)
{
"error": {
"code": "rate_limited",
"message": "rate limit exceeded: this surface admits 20 requests per second per client address, burst 40",
"retry_after_seconds": 3
}
}InternalError body · packages/client-ts/test/fixtures/errors/internal.json (contract-validated)
{
"error": {
"code": "internal",
"message": "the service failed to build this response"
}
}NoBatch body · packages/client-ts/test/fixtures/errors/unavailable.json (contract-validated)
{
"error": {
"code": "unavailable",
"message": "no complete risk batch is available. This is a statement about the SERVICE, NOT a claim that the book is empty.",
"retry_after_seconds": 5
}
}