Data Quality and Observability
API observability metadata is surfaced in the wallet UI viaWalletObservabilityBadge and WalletDataQualityStrip components to provide users with clear insights into data provenance, staleness, partial data, and fallback states.
This is the trust model reference for the Wallet Graph (Holder Wall) and Stable Seer features.
1. Wallet Graph / Holder Wall
Endpoint
api/wallet-contract.js via normalizeWalletGraphResponse before the route sends JSON.
WalletGraph v1 response contract
Every successful response includescontractVersion: "walletGraph.v1". This field is the authoritative version tag — never remove or rename it.
Key top-level fields:
| Field | Type | Notes |
|---|---|---|
contractVersion | "walletGraph.v1" | Stable version tag |
address | string | Resolved 0x address |
chain | "ethereum" | Currently Ethereum only |
provider | string | Normalized to: mock, etherscan, alchemy, mixed, or unknown |
source | string | Normalized to: mock, fallback, or live |
sources | object | Map of provider identities, e.g. { walletActivity, prices } |
dataQuality | object | Quality flags |
isFallback | bool | Alias for dataQuality.isFallback |
transactionSample | object | Sample window metadata |
apiErrors | array | User-safe provider warnings (sanitized) |
nodes | array | Graph nodes |
edges | array | Graph edges |
observability | object | Structured observability metadata |
Provider selection
| Condition | Provider | observability.source | observability.provider |
|---|---|---|---|
No ETHERSCAN_API_KEY and no ALCHEMY_API_KEY | mockWalletProvider | mock | mock |
ETHERSCAN_API_KEY set | liveWalletProvider | live | etherscan |
ALCHEMY_API_KEY only | liveWalletProvider | live | alchemy |
Production + no keys + ENABLE_MOCK_MODE unset | 503 error | — | — |
ethers.id. It never calls external APIs.
Observability fields
observability is always present on a normalized response:
provider: normalized provider name.source: data class —live(real provider data),mock(demo/no keys), orfallback(price fallback active).partial: true when any provider sub-request failed or transaction data is sampled.fallback: true when fallback pricing or demo data is being served.providerErrors: true whenapiErrorscontains entries withseverity === 'partial'or'error'.freshness: wallet’slastActiveISO date string, ornull.durationMs: total route wall-clock time in milliseconds.timing: sub-phase breakdowns. Allowed keys:totalMs,providerMs,coingeckoMs,graphMs,duneMs.
dataQuality flags
partial: set when data is sampled or any provider sub-call degraded.sampled: set whentransactionSample.isSampledis true.fallback: set when fallback pricing is active.demo: set only for explicit mock/demo data.providerErrors: set whenapiErrorscontainspartialorerrorentries.
apiErrors sanitization
apiErrors entries are user-safe. Before serialization, sanitizeApiErrorMessage in api/wallet-contract.js:
- Strips URL query strings (which may contain provider API keys or tokens) — replaced with
[params redacted]. - Caps message length at 200 characters.
apiErrors.
UI status badge behavior
WalletObservabilityBadge — compact badge rendered in the GalaxyHeader:
observability.source | Badge text | Badge tone |
|---|---|---|
mock | Demo | muted |
fallback | Fallback | warn |
live | Live · <ProviderName> | safe |
WalletDataQualityStrip — fixed bottom bar:
- Reads from
observabilitywhen present; falls back to legacydataQualityfields. - Invariant copy: “Read-only public wallet data · No wallet connection required”
- Conditional: partial note, “Some provider data unavailable”, “Fallback data source”, “Demo mode”, provider name, freshness, durationMs.
- Never exposes raw URLs, API keys, stack traces, or provider error details.
2. Stable Seer
Endpoint
holderAnalyticsSupported: false flag is present on every response.
Cache behavior
| Layer | When active | TTL |
|---|---|---|
| Redis (Upstash) | UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN set | 60 s |
In-memory Map | Redis not configured (fallback) | 60 s |
confidence values
| State | confidence | HTTP status |
|---|---|---|
| Success (live or cache hit) | "low" | 200 |
| Provider unreachable | "unavailable" | 502 |
confidence: "low" on success is intentional — market data is DEX pool data, not audited fundamentals.
confidence: "unavailable" is a provider failure state. UI should suppress user-blame wording when this value is present.
3. Agent guardrails
Rules enforced by the test suite:- Do not conflate Holder Wall and Stable Seer. These are separate features with separate data sources and API routes.
- Do not invent holder data.
holderAnalyticsSupported: falseon all Stable Seer responses is a hard invariant. - Do not expose secrets.
sanitizeApiErrorMessagestrips query strings from error messages. Tests verify no provider key strings appear in normalized output. - Keep partial provider degradation non-fatal and non-scary. A failed sub-provider call sets
partial: trueand contributes anapiErrorsentry. It does not fail the route. UI copy should say “Provider data may be partial” — not “Error” or “Failed”. - Preserve existing response contracts.
contractVersion: "walletGraph.v1"must be present on every/api/walletresponse. Theobservabilityfield must be present on every normalized response.
Related files
| File | Role |
|---|---|
api/wallet-contract.js | Response normalization, observability, sanitization |
api/wallet.js | Route handler — provider selection, rate limiting, cache headers |
api/_wallet-providers.js | makeProviderResult envelope, mockWalletProvider |
api/_wallet-live-provider.js | liveWalletProvider — Etherscan, Alchemy, CoinGecko, Dune, Graph |
api/market-radar.js | Stable Seer route (legacy filename) — DEX Screener fetch, cache, metadata |
api/_cache.js | Radar cache — Redis + in-memory fallback, 60s TTL |
src/components/WalletObservabilityBadge.jsx | Compact observability badge in GalaxyHeader |
src/components/WalletDataQualityStrip.jsx | Fixed-bottom data provenance strip |