Skip to main content

Data Quality and Observability

API observability metadata is surfaced in the wallet UI via WalletObservabilityBadge 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

GET /api/wallet?address=<0x-address-or-ENS>
All responses are normalized by api/wallet-contract.js via normalizeWalletGraphResponse before the route sends JSON.

WalletGraph v1 response contract

Every successful response includes contractVersion: "walletGraph.v1". This field is the authoritative version tag — never remove or rename it. Key top-level fields:
FieldTypeNotes
contractVersion"walletGraph.v1"Stable version tag
addressstringResolved 0x address
chain"ethereum"Currently Ethereum only
providerstringNormalized to: mock, etherscan, alchemy, mixed, or unknown
sourcestringNormalized to: mock, fallback, or live
sourcesobjectMap of provider identities, e.g. { walletActivity, prices }
dataQualityobjectQuality flags
isFallbackboolAlias for dataQuality.isFallback
transactionSampleobjectSample window metadata
apiErrorsarrayUser-safe provider warnings (sanitized)
nodesarrayGraph nodes
edgesarrayGraph edges
observabilityobjectStructured observability metadata

Provider selection

ConditionProviderobservability.sourceobservability.provider
No ETHERSCAN_API_KEY and no ALCHEMY_API_KEYmockWalletProvidermockmock
ETHERSCAN_API_KEY setliveWalletProviderliveetherscan
ALCHEMY_API_KEY onlyliveWalletProviderlivealchemy
Production + no keys + ENABLE_MOCK_MODE unset503 error
Mock data is deterministic and address-seeded via ethers.id. It never calls external APIs.

Observability fields

observability is always present on a normalized response:
{
  "provider": "etherscan",
  "source": "live",
  "partial": false,
  "fallback": false,
  "providerErrors": false,
  "freshness": "2026-05-14",
  "durationMs": 312,
  "timing": { "totalMs": 312, "providerMs": 180, "coingeckoMs": 80 }
}
  • provider: normalized provider name.
  • source: data class — live (real provider data), mock (demo/no keys), or fallback (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 when apiErrors contains entries with severity === 'partial' or 'error'.
  • freshness: wallet’s lastActive ISO date string, or null.
  • durationMs: total route wall-clock time in milliseconds.
  • timing: sub-phase breakdowns. Allowed keys: totalMs, providerMs, coingeckoMs, graphMs, duneMs.

dataQuality flags

{
  "isFallback": false,
  "isDemo": false,
  "isPartial": true,
  "warnings": ["Loaded latest 200 normal transactions; totals may be partial."],
  "flags": {
    "partial": true,
    "sampled": false,
    "fallback": false,
    "demo": false,
    "providerErrors": true
  },
  "providerErrors": []
}
  • partial: set when data is sampled or any provider sub-call degraded.
  • sampled: set when transactionSample.isSampled is true.
  • fallback: set when fallback pricing is active.
  • demo: set only for explicit mock/demo data.
  • providerErrors: set when apiErrors contains partial or error entries.

apiErrors sanitization

apiErrors entries are user-safe. Before serialization, sanitizeApiErrorMessage in api/wallet-contract.js:
  1. Strips URL query strings (which may contain provider API keys or tokens) — replaced with [params redacted].
  2. Caps message length at 200 characters.
Never add raw provider error messages, API keys, full URLs, or stack traces to apiErrors.

UI status badge behavior

WalletObservabilityBadge — compact badge rendered in the GalaxyHeader:
observability.sourceBadge textBadge tone
mockDemomuted
fallbackFallbackwarn
liveLive · <ProviderName>safe
WalletDataQualityStrip — fixed bottom bar:
  • Reads from observability when present; falls back to legacy dataQuality fields.
  • 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

GET /api/market-radar?q=<search-query>
Stable Seer performs DEX token and pool market lookup using DEX Screener. It does not provide holder analytics. The holderAnalyticsSupported: false flag is present on every response.

Cache behavior

LayerWhen activeTTL
Redis (Upstash)UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN set60 s
In-memory MapRedis not configured (fallback)60 s
Cache key is the lowercased query string. Provider error and 4xx/5xx responses are never cached.

confidence values

StateconfidenceHTTP 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: false on all Stable Seer responses is a hard invariant.
  • Do not expose secrets. sanitizeApiErrorMessage strips 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: true and contributes an apiErrors entry. 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/wallet response. The observability field must be present on every normalized response.
FileRole
api/wallet-contract.jsResponse normalization, observability, sanitization
api/wallet.jsRoute handler — provider selection, rate limiting, cache headers
api/_wallet-providers.jsmakeProviderResult envelope, mockWalletProvider
api/_wallet-live-provider.jsliveWalletProvider — Etherscan, Alchemy, CoinGecko, Dune, Graph
api/market-radar.jsStable Seer route (legacy filename) — DEX Screener fetch, cache, metadata
api/_cache.jsRadar cache — Redis + in-memory fallback, 60s TTL
src/components/WalletObservabilityBadge.jsxCompact observability badge in GalaxyHeader
src/components/WalletDataQualityStrip.jsxFixed-bottom data provenance strip