This document formally states the data quality guarantees provided by Lens: freshness, completeness, precision, venue inclusion, and the explicit system behaviors when any guarantee is violated. Every numerical bound documented below links directly to the code or configuration that enforces it.
Lens continuously aggregates order book trades and AMM liquidity pool snapshots across Stellar networks. The following freshness bounds are enforced:
A price response is considered fresh when its newest underlying trade observation occurred within 300 seconds (5 minutes). When the newest observation age exceeds 300 seconds, the response explicitly sets stale: true.
- Enforcing Code:
src/aggregator/vwap.ts(STALENESS_THRESHOLD_SECONDS) - Violation Behavior: The engine returns the last observed price alongside
stale: trueandlastTradeAgeSeconds: <age>(exceeding300seconds). Confidence rating degrades to'low'.
Every pricing response carries a categorical confidence rating derived from observation age and venue diversity:
- High Confidence: Requires an observation age under
30seconds across at least2distinct sources with positive24h volume. - Medium Confidence: Requires an observation age under
300seconds. - Low Confidence: Assigned when observation age equals or exceeds
300seconds. - Unknown Confidence: Assigned when no trades have ever been recorded (
lastTradeAgeSeconds === null). - Enforcing Code:
src/aggregator/vwap.ts
Venue adapters report network liveness on /venues/registry. A venue is marked 'live' if a price point has been ingested within the last 300000 ms (5 minutes).
- Enforcing Code:
src/routes/venues.ts(LIVE_WINDOW_MS) - Violation Behavior: If no price points are observed within
300000ms, venue liveness transitions to'stale'. Disabled venues report'disabled'.
- SDEX Order Book & AMM Trades: Horizon ingestion polls every
5000ms with a batch page size of200records for SDEX and200records for AMM pools. - Soroswap AMM Reserves: Contract reserves poll via Soroban RPC every
60000ms. - Enforcing Code:
src/config.ts(pollIntervalMs,soroswapPollMs)
- Redis Price Cache TTL: Aggregate prices are cached in Redis with a TTL of
10seconds. - Outbound HTTP Timeout: External HTTP requests enforce a deadline timeout of
10000ms. - Enforcing Code:
src/config.ts(priceTtl,timeoutMs) - Violation Behavior: Outbound calls abort upon reaching
10000ms; ingester logs errors and retries on subsequent poll ticks.
- Pool Snapshot Pruning: Historical liquidity pool snapshots older than
30days are pruned hourly in batches of1000rows. - Price Point Partition Dropping: Daily
price_pointspartitions older than30days are dropped. - Enforcing Code:
src/jobs/snapshotRetention.ts,src/partitioning/pricePoints.ts
High confidence requires price observations from at least 2 distinct liquidity sources (e.g. SDEX and AMM pools). When only 1 source is active, confidence is limited to 'medium'.
TWAP and VWAP computations apply statistical filtering to reject manipulation spikes:
- Minimum Sample Count: Outlier filtering requires at least
4sample points. - Interquartile Range (IQR): Rejects samples beyond Q1 -
1.5* IQR or Q3 +1.5* IQR. - Modified Z-Score: Rejects samples with absolute score exceeding
3.5, using a consistency factor of0.6745. - Enforcing Code:
src/pricing/twap.ts - Violation Behavior: If fewer than
4samples exist, filtering is bypassed (outlierRejectedis zero). If0samples exist in the window, endpoints return zero TWAP and sample count.
- TWAP Windows: Window ranges from
1to1440minutes (default60minutes). Sample intervals range from1to3600seconds (default60seconds). - VWAP Windows: Window ranges from
1to1440minutes (default60minutes). - Price History Limits: Bucket count is capped at
1000buckets (default100buckets). - Screener Pair Limits: Pair count defaults to
20and is clamped between1and100. - Spread Observations: Venues must quote at least
2observations in the window to report a spread. - Violation Behavior: Inputs outside allowed ranges trigger HTTP
400Bad Request with validation details.
- Prices and VWAPs: Stored as PostgreSQL
DECIMAL(36,18)with18decimal digits of fractional scale. - Stellar Native Reserves & Volumes: Stored as PostgreSQL
DECIMAL(36,7)matching Stellar's native7decimal place stroop scale (1stroop =0.0000001XLM). - Enforcing Code:
prisma/schema.prisma
- Standard Default Mode: Returns numbers as IEEE-
754double precision floating-point numbers (~15-17significant digits). - Exact String Representation Mode: When negotiated via
X-Decimal-Format: string,Accept: application/json; decimals=strings, or?decimals=strings, all decimal quantities serialise as exact strings without floating-point truncation. - Enforcing Code:
prisma/schema.prisma,src/types.ts - Violation Behavior: Consumers that require exact
18-decimal precision must negotiate string format; unnegotiated requests receive standard JSON numbers for backward compatibility.
Lens integrates 4 registered venue adapters:
| Venue | Identifier | Kind | Testnet | Mainnet | Included In | Enforcing Code |
|---|---|---|---|---|---|---|
| Stellar Decentralized Exchange (SDEX) | sdex |
orderbook |
Enabled | Enabled | VWAP, Best Route, Depth, Book, Volumes, Spreads | src/ingest/venues/sdex.ts |
| Stellar Native Liquidity Pools (AMM) | stellar-amm |
amm |
Enabled | Enabled | Spot, Pools, Best Route, Depth, Book, Volumes | src/ingest/venues/amm.ts |
| Soroswap AMM | soroswap |
amm |
Enabled | Enabled | Price aggregation, multi-venue pools | src/ingest/venues/soroswap.ts |
| Aquarius AMM | aquarius |
amm |
Disabled | Enabled | Mainnet liquidity pool aggregation | src/ingest/venues/aquarius.ts |
- Reflector Oracle: Used optionally for oracle benchmark comparison (
/compare/:asset) and GraphQL oracle resolution. Disabled by default unless configured. - Enforcing Code:
src/ingest/venues/index.ts,src/config.ts
When a data quality guarantee is breached, Lens behaves deterministically:
- Stale Observation (age >=
300s): Returns the last known price marked withstale: trueand downgraded confidence (low), preserving availability while warning callers. - Missing Ingestion / Empty Window (trades =
0): Returnsconfidence: "unknown"withlastTradeAgeSeconds: null, falling back through VWAP1h -> VWAP24h -> AMM Spot ->0. - Unwatched Pair: Returns HTTP
404Not Found with{ "error": "Pair <A>/<B> not watched on <network>" }. - Invalid Query Parameters: Returns HTTP
400Bad Request with structured Zod validation errors. - Outbound Failure / Ingestion Silence (>=
300000ms): Venue liveness degrades from'live'to'stale'. - Rate Limit Exceeded: Returns HTTP
429Too Many Requests.
| Parameter | Bound | Units | Enforcing Code | Description |
|---|---|---|---|---|
staleness_threshold |
300 |
seconds | src/aggregator/vwap.ts:4 |
Ingestion age after which a price is flagged stale |
high_confidence_max_age |
30 |
seconds | src/aggregator/vwap.ts:5 |
Max trade age for high confidence rating |
medium_confidence_max_age |
300 |
seconds | src/aggregator/vwap.ts:6 |
Max trade age for medium confidence rating |
venue_liveness_window |
300000 |
ms | src/routes/venues.ts:7 |
Silence interval after which a venue is marked stale |
indexer_poll_interval |
5000 |
ms | src/config.ts:266 |
Polling interval for SDEX / Horizon trades |
sdex_page_size |
200 |
rows | src/config.ts:267 |
Maximum trade records fetched per Horizon request |
amm_page_size |
200 |
rows | src/config.ts:268 |
Maximum pool snapshot records fetched per page |
soroswap_poll_interval |
60000 |
ms | src/config.ts:178 |
Polling interval for Soroswap smart contract reserves |
price_cache_ttl |
10 |
seconds | src/config.ts:271 |
Redis cache TTL for pricing responses |
http_timeout_ms |
10000 |
ms | src/config.ts:274 |
Outbound deadline timeout for external network requests |
snapshot_retention_days |
30 |
days | src/jobs/snapshotRetention.ts:12 |
Retention window for historical pool snapshots |
snapshot_retention_batch_size |
1000 |
rows | src/jobs/snapshotRetention.ts:14 |
Row limit for batch snapshot pruning statements |
price_points_retention_days |
30 |
days | src/partitioning/pricePoints.ts:5 |
Retention window before daily partition drops |
min_outlier_samples |
4 |
samples | src/pricing/twap.ts:288 |
Minimum samples required for statistical outlier rejection |
iqr_multiplier |
1.5 |
factor | src/pricing/twap.ts:289 |
Interquartile range multiplier for outlier fence |
modified_z_score_threshold |
3.5 |
factor | src/pricing/twap.ts:290 |
Modified Z-Score deviation threshold |
modified_z_score_factor |
0.6745 |
factor | src/pricing/twap.ts:291 |
Consistency adjustment factor for MAD normal distribution |
twap_window_min |
1 |
minutes | src/routes/price.ts:8 |
Minimum allowed TWAP calculation window |
twap_window_max |
1440 |
minutes | src/routes/price.ts:9 |
Maximum allowed TWAP calculation window (24h) |
twap_window_default |
60 |
minutes | src/routes/price.ts:10 |
Default TWAP window |
twap_sample_interval_min |
1 |
seconds | src/routes/price.ts:11 |
Minimum sample interval for TWAP calculation |
twap_sample_interval_max |
3600 |
seconds | src/routes/price.ts:12 |
Maximum sample interval for TWAP calculation |
twap_sample_interval_default |
60 |
seconds | src/routes/price.ts:13 |
Default sample interval for TWAP calculation |
vwap_window_min |
1 |
minutes | src/routes/price.ts:15 |
Minimum allowed VWAP calculation window |
vwap_window_max |
1440 |
minutes | src/routes/price.ts:16 |
Maximum allowed VWAP calculation window (24h) |
vwap_window_default |
60 |
minutes | src/routes/price.ts:17 |
Default VWAP window |
history_default_limit |
100 |
buckets | src/api/rest.ts:24 |
Default bucket limit for historical aggregates |
history_max_limit |
1000 |
buckets | src/api/rest.ts:25 |
Upper ceiling for historical aggregates |
screener_default_limit |
20 |
pairs | src/services/screener.ts:16 |
Default limit of pairs returned by screener |
screener_max_limit |
100 |
pairs | src/services/screener.ts:17 |
Maximum clamped limit of pairs returned by screener |
spread_min_observations |
2 |
observations | src/routes/spreads.ts:20 |
Minimum observations required before a venue spread is reported |
registered_venues_count |
6 |
adapters | src/ingest/venues/index.ts:23 |
Total number of registered venue adapters |
database_price_precision |
36 |
digits | prisma/schema.prisma:20 |
Total numeric precision in PostgreSQL decimal columns |
database_price_scale |
18 |
decimals | prisma/schema.prisma:20 |
Decimal places stored for pricing in PostgreSQL |
stellar_native_scale |
7 |
decimals | prisma/schema.prisma:21 |
Native Stellar stroop scale (one stroop equals ten to the negative seven XLM) |