Skip to main content

Wallet Risk Report Model

WalletRiskReport.v1 adds no new scoring, no new provider, no Dune execution, and no wallet connection or signing. It is a read-only composition layer over EXISTING canonical scorers — Quantum Vault Readiness, Stablecoin Vault Readiness, Migration Readiness, and Whale Watcher behavioral signals. This PR ships the model, the read-only API, and tests only — the report page/UI is intentionally deferred.
WalletRiskReport.v1 is the canonical per-wallet risk assessment for a single public Ethereum address. It composes four existing dimensions — quantum exposure readiness, stablecoin/concentration exposure, dormancy/activity, and deterministic behavioral signals — into one object with an overall canonical readiness tier, without inventing any new score, tier, or driver vocabulary. Source: src/lib/wallet-risk-report/ (schema.js, build-wallet-risk-report.js) and api/wallet-risk-report.js.

Three report-shaped objects — do not confuse them

This is the third distinct report/packet object in the codebase. Each has a different scope and a different purpose:
ObjectScopePurposeSigned?
Vault Readiness Packet (buildVaultReadinessPacket.js)Per-wallet, vault-specificA bounded, shareable “proof packet” for the Vault Candidate surfaceYes, via api/readiness-packet.js (ECDSA P-256)
Exposure Report Edition (exposure-report/buildExposureReportEdition.js)Cohort (many wallets aggregated)A citable, dated snapshot of Holder Wall cohort exposure statsNo
Wallet Risk Report (this page)Per-wallet, general-purposeThe canonical cross-dimension risk assessment for one walletNo (v1)
The Wallet Risk Report is the only one of the three that spans quantum + concentration + dormancy + behavior in a single object with an overall readiness tier. It never aggregates across wallets (that’s the Exposure Report’s job) and it is not a vault-specific artifact (that’s the Readiness Packet’s job).

Schema shape

WalletRiskReport {
  reportType: 'walletwall.wallet-risk-report'
  reportVersion: 'v1'
  subject: { address, chain }
  generatedAt: string | null          // caller-supplied; the model never reads a clock
  modelVersions: { ... }              // which composed pipeline produced each dimension
  status: 'assessed' | 'insufficient_evidence'
  overall: {
    riskTier: TIER_KEYS.* | null      // risk-tiers.js — no new tier taxonomy
    riskTierLabel: string | null
    recommendation: RECOMMENDATION_KEYS.*
    compositeScoreNote: string        // explicit "no single composite score" statement
  }
  dimensions: {
    quantum: Dimension
    concentration: Dimension
    dormancyActivity: Dimension
    behavior: Dimension
  }
  evidenceConfidence: { overall, scorable, reasons, perDimension }
  dataSources: [{ id, type, freshness, queryRunAt, cachedAt }]
  missingEvidence: string[]
  riskDrivers: string[]               // RISK_DRIVER_KEYS values only
  recommendedNextActions: string[]    // deterministic, read-only-verb copy
  boundaries: { custodyClaim: false, yieldClaim: false, insuranceClaim: false, quantumProofClaim: false, productionVaultClaim }
  disclaimers: string[]               // non-strippable; always non-empty
  narrative: null                     // ALWAYS null in v1 — see "AI narrative" below
}

Dimension {
  status: 'observed' | 'partial' | 'unknown'
  confidence: 'high' | 'medium' | 'low'
  scorable: boolean
  reasonCodes: string[]               // src/lib/wallet-evidence/evidence-quality.js SCORING_REASON_CODES
  source: string                      // which existing module/function produced this
  value: object | null
}

What each dimension actually is

Quantum

Reuses api/quantum-readiness.js’s already-composed Quantum Vault Readiness pipeline (score/band/exposureLevel, higher = better-prepared) plus its freshness-aware evidence diagnostics. The separate, higher-is-worse “Quantum Exposure Score” pipeline (src/lib/quantum-exposure.js) is intentionally not composed in v1 — see “Deferred work” below.

Concentration

Reuses getStablecoinVaultReadiness() (stablecoin balance / portfolio share / dormancy / signature exposure), which implements the OR-of-four-facts null-gate: score/tier stay null only when none of the four primary facts were observed.

Dormancy / activity

Built directly from the Dune-sourced dormancy facts (daysDormant, dormancyBucket, lastActiveAt) already fetched for the Quantum dimension — no separate query.

Behavior

Reuses deriveWhaleWatcherSignals() — the same deterministic WalletSignal[] engine Whale Watcher itself uses. An empty signal array is a valid, meaningful “no notable behavior” result, distinct from “no evidence” (status: 'unknown'), which is set only when neither a Dune 12-week baseline nor live transaction events existed to evaluate.

The overall readiness tier

The overall.riskTier is derived by calling buildStablecoinVaultReadiness() — the same composition point existing Vault Readiness surfaces already use — not a new tier-derivation rule. That function resolves the strongest categorical signal across whichever dimension inputs are available and bridges the result to risk-tiers.js’s canonical 5-tier vocabulary (monitorreviewpreparemigratevaultPrototype / “Vault Candidate”) via getCanonicalRiskTier(). There is no separate composite risk score. overall.compositeScoreNote says so explicitly on every report — each dimension carries its own score/label on its own scale; they are never averaged or blended into one number (see “What’s deterministic, inferred, or unavailable” below for why: the dimensions genuinely use different scales and null-gate rules, and collapsing them would erase real distinctions the underlying models were built to preserve).

Deterministic vs. inferred vs. unavailable

CategoryMeaningExample
Deterministic (observed)Read directly from a source, or a pure deterministic function of observed factsdimensions.quantum.value.vaultReadinessScore when Dune facts exist
Deterministic (inferred)A pure, deterministic derivation with no invented dataoverall.riskTier, derived from dimension outputs via the existing composition rules
Unavailable (status: 'unknown')No evidence source existeddimensions.behavior when neither a Dune 12-week baseline nor live wallet transactions were available
AI narrative generation is optional and strictly downstream — never computed by this module. narrative is always null in v1. A future PR may add an opt-in narrative step that reads a finished WalletRiskReport and produces plain-English copy; it must never be required to interpret the report, and it must never write back into the deterministic fields above.

Honesty rules (enforced by tests)

  • Unknown is never zero. A dimension with no evidence reports status: 'unknown', not a fabricated 'monitor'/0/false.
  • Stale/conflicting evidence lowers confidence, not necessarily severity. A stale Dune dormancy fact downgrades dimensions.dormancyActivity.confidence, but does not by itself change overall.riskTier.
  • If nothing can be honestly assessed, the report says so structurallystatus: 'insufficient_evidence', overall.riskTier: null, overall.recommendation: 'unsupported' — never a fabricated tier.
  • The client cannot inject any computed field. The read-only API (GET /api/wallet-risk-report?address=0x…) never reads a request body, and the only client-controlled input is the wallet address itself.
  • Dune data is never described as live. dataSources[].type distinguishes dune_scheduled from live_provider; the app-wide Dune spend-path rules (docs/operations/dune-spend-path-controls.md) apply unchanged — this endpoint only reads Dune’s cache, never executes or reads live results.
  • Mock/demo data is never served as a real report. Unlike /api/wallet, this endpoint does not fall back to mockWalletProvider when no live provider key is configured — the concentration and behavior dimensions honestly degrade to unknown instead.

Deferred work (intentional v1 scope)

Not built in this PR — listed here so a follow-up doesn’t need to rediscover the reasoning:
  • Report page / UI. This PR ships the model, API, tests, and docs only.
  • Quantum Exposure Score pipeline (deriveWalletSignatureExposurederiveQuantumExposureScore). That pipeline has never been server-composed anywhere in this codebase; wiring it here would mean writing new composition logic rather than reusing an existing one. v1 composes the already fully server-composed Quantum Vault Readiness pipeline instead.
  • Dune 12-week Whale Watcher cohort baseline. The behavior dimension currently relies only on live wallet transactions (when a provider key is configured) for its baseline; most report subjects are outside the curated 12-week active-wallet cohort anyway.
  • ENS resolution. The endpoint requires a raw 0x address; resolve via /api/resolve-wallet first.
  • Report persistence, sharing, signing, or a database. Every call recomputes the report fresh from current cache/live state.
  • Portfolio / multi-wallet batching.
  • Reconciling Vault’s two disagreeing threshold sets (legacy 30/60 vs. canonical 25/50/75) — a pre-existing, separate gap this PR does not touch.

Wallet Evidence Model

The provenance-aware /api/wallet layer this report’s concentration dimension reads balances from.

Model Layer

The shared SourceMetadata / DataQuality / WalletSignal conventions this report’s behavior dimension builds on.