ync refreshes.
Technical Implementation
The following TypeScript implementation demonstrates a production-grade ValuationEngine with aggregation, staleness checks, and provider management.
// Core Types
export interface PricePoint {
assetId: string;
value: bigint; // Use BigInt for precision; scale factor handled by engine
timestamp: number;
source: string;
confidence: number; // 0.0 to 1.0
}
export interface PriceProvider {
id: string;
fetchPrice(assetId: string): Promise<PricePoint>;
healthCheck(): Promise<boolean>;
}
export interface ValuationConfig {
maxStalenessMs: number;
aggregationMethod: 'median' | 'trimmedMean';
trimPercentage: number;
minProviders: number;
circuitBreaker: {
maxDeviation: number; // Max % deviation from last known price
cooldownMs: number;
};
}
export class ValuationEngine {
private providers: Map<string, PriceProvider> = new Map();
private cache: Map<string, PricePoint> = new Map();
private lastValidated: Map<string, number> = new Map();
constructor(private config: ValuationConfig) {}
registerProvider(provider: PriceProvider): void {
this.providers.set(provider.id, provider);
}
async getValuation(assetId: string): Promise<PricePoint> {
const now = Date.now();
// 1. Check Cache for immediate response
const cached = this.cache.get(assetId);
if (cached && (now - cached.timestamp) < this.config.maxStalenessMs) {
// Trigger async refresh
this.refreshAsset(assetId).catch(console.error);
return cached;
}
// 2. Fetch from active providers
const activeProviders = Array.from(this.providers.values())
.filter(p => p.id !== 'cache');
const results = await Promise.allSettled(
activeProviders.map(p => p.fetchPrice(assetId))
);
const validPrices: PricePoint[] = results
.filter((r): r is PromiseFulfilledResult<PricePoint> => r.status === 'fulfilled')
.map(r => r.value)
.filter(p => this.isStale(p, now));
// 3. Circuit Breaker & Validation
if (validPrices.length < this.config.minProviders) {
throw new Error(`Insufficient providers for ${assetId}. Found: ${validPrices.length}`);
}
const aggregated = this.aggregate(validPrices);
// 4. Circuit Breaker Check against last valid
const last = this.lastValidated.get(assetId);
if (last && this.deviationExceeded(aggregated, last)) {
throw new Error(`Circuit breaker triggered for ${assetId}. Deviation too high.`);
}
// 5. Update State
this.cache.set(assetId, aggregated);
this.lastValidated.set(assetId, aggregated);
return aggregated;
}
private aggregate(prices: PricePoint[]): PricePoint {
const sortedValues = prices.map(p => p.value).sort((a, b) => Number(a - b));
if (this.config.aggregationMethod === 'median') {
const mid = Math.floor(sortedValues.length / 2);
return { ...prices[0], value: sortedValues[mid], source: 'aggregated' };
}
// Trimmed Mean
const trimCount = Math.floor(sortedValues.length * (this.config.trimPercentage / 100));
const trimmed = sortedValues.slice(trimCount, sortedValues.length - trimCount);
const sum = trimmed.reduce((acc, val) => acc + val, 0n);
const avg = sum / BigInt(trimmed.length);
return { ...prices[0], value: avg, source: 'aggregated' };
}
private isStale(price: PricePoint, now: number): boolean {
return (now - price.timestamp) <= this.config.maxStalenessMs;
}
private deviationExceeded(current: PricePoint, previous: PricePoint): boolean {
const diff = current.value > previous.value
? current.value - previous.value
: previous.value - current.value;
const deviation = (diff * 10000n) / previous.value; // Basis points
return deviation > BigInt(this.config.circuitBreaker.maxDeviation * 100);
}
private async refreshAsset(assetId: string): Promise<void> {
// Background refresh logic
}
}
Rationale
- BigInt Usage: Floating-point math introduces precision errors that compound in financial calculations.
BigInt ensures exact arithmetic, with scale factors managed at the presentation layer.
- Promise.allSettled: Prevents a single provider failure from blocking the entire valuation. The engine degrades gracefully based on
minProviders.
- Circuit Breaker: Protects against flash crash scenarios or oracle manipulation by rejecting prices that deviate significantly from recent history.
- Aggregation Flexibility: Supporting both median and trimmed mean allows tuning based on asset volatility. Trimmed mean is preferred when outlier noise is expected.
Pitfall Guide
1. The Liquidity Illusion in AMM Pricing
Mistake: Using the spot price from an Automated Market Maker (AMM) for valuation without considering trade size.
Impact: Valuation appears correct for small amounts but fails for large positions due to price impact. This leads to under-collateralization in lending protocols.
Fix: Implement getQuote functions that simulate trade depth. Use the price at a specific trade size (e.g., $10k equivalent) rather than the marginal spot price.
2. Decimal Precision Mismatches
Mistake: Assuming all assets use 18 decimals. ERC-20 tokens can have arbitrary decimals (e.g., USDC uses 6).
Impact: Valuation calculations return values off by orders of magnitude, causing massive financial errors.
Fix: Always query and cache asset metadata, including decimals. Normalize all values to a base unit (e.g., 18 decimals) internally before calculation.
3. Stale Price Serving Without Warning
Mistake: Returning cached prices indefinitely after the provider goes offline.
Impact: The system operates on outdated valuations, allowing arbitrage or insolvency during market crashes.
Fix: Implement strict staleness limits. If data exceeds maxStalenessMs, the engine must either throw an error or return a PricePoint with a confidence: 0 flag that downstream systems must handle.
4. Single-Source Dependency
Mistake: Relying on a single oracle or DEX for critical assets.
Impact: If the source is manipulated or experiences downtime, the valuation engine fails completely.
Fix: Enforce multi-source aggregation. Configure the engine to require a minimum of three independent providers for high-value assets.
5. Ignoring Cross-Chain Bridging Risks
Mistake: Valuing a bridged asset (e.g., wBTC on L2) identically to the native asset without assessing bridge risk.
Impact: During bridge exploits or congestion, the bridged asset may depeg. Valuation engines that ignore this risk overvalue collateral.
Fix: Maintain a riskMultiplier or depegThreshold for bridged assets. Adjust valuation dynamically based on bridge health metrics or observed spread between native and bridged versions.
6. Race Conditions in Updates
Mistake: Updating the valuation cache without atomic operations in a distributed environment.
Impact: Concurrent requests may read partial updates or inconsistent states, leading to divergent valuations across nodes.
Fix: Use atomic cache updates. In distributed systems, employ a consensus-based cache update mechanism or leader election for cache refreshes.
7. Failure to Handle "Dust" Assets
Mistake: Attempting to fetch prices for assets with zero liquidity or no trading pairs.
Impact: Engine timeouts, resource exhaustion, or crashes.
Fix: Implement a pre-check for liquidity depth. If liquidity is below a threshold, mark the asset as untradeable and return a null valuation or use a fallback intrinsic model.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| High-Frequency Trading | TWAP + AMM Depth | Prevents manipulation; accounts for slippage. | Medium (Compute heavy) |
| NFT Marketplace | Intrinsic + Comparable Sales | No liquid market; requires heuristic pricing. | Low (Model maintenance) |
| Enterprise Ledger | Centralized Oracle + Audit | Requires deterministic, auditable pricing. | High (Oracle fees) |
| Long-Tail Assets | Multi-Source Aggregator | Maximizes coverage; filters noise from thin liquidity. | Medium (Infrastructure) |
| Stablecoin Pairs | Peg Deviation Monitor | Focus on depeg detection rather than price discovery. | Low |
Configuration Template
# valuation-engine-config.yaml
engine:
aggregationMethod: trimmedMean
trimPercentage: 20
minProviders: 3
maxStalenessMs: 60000
circuitBreaker:
maxDeviationBps: 500 # 5%
cooldownMs: 300000
assets:
- id: ETH
baseAsset: USD
providers:
- id: chainlink-eth-usd
weight: 0.4
- id: uniswap-v3-eth-usdc
weight: 0.3
- id: binance-spot-eth-usdt
weight: 0.3
circuitBreaker:
maxDeviationBps: 200 # Tighter for major assets
- id: USDC
baseAsset: USD
providers:
- id: chainlink-usdc-usd
weight: 1.0
pegCheck: true
depegThresholdBps: 100
providers:
- id: chainlink-eth-usd
type: oracle
endpoint: https://api.chain.link/v1/...
timeoutMs: 500
- id: uniswap-v3-eth-usdc
type: amm
poolAddress: 0x88e6A0c2...
tickSpacing: 100
timeoutMs: 200
Quick Start Guide
-
Initialize Engine:
npm install @codcompass/valuation-engine
Create a config object matching the YAML structure above.
-
Register Providers:
const engine = new ValuationEngine(config);
engine.registerProvider(new ChainlinkProvider(config.providers[0]));
engine.registerProvider(new UniswapV3Provider(config.providers[1]));
-
Configure Caching:
Enable the built-in cache or inject a Redis adapter for distributed caching:
engine.setCache(new RedisCacheAdapter(redisClient));
-
Fetch Valuation:
try {
const price = await engine.getValuation('ETH');
console.log(`ETH Price: ${price.value} (Source: ${price.source})`);
} catch (error) {
// Handle circuit breaker or insufficient providers
console.error('Valuation failed:', error.message);
}
-
Monitor Health:
Expose engine metrics via Prometheus:
engine.on('priceUpdate', (metric) => metricsClient.gauge('valuation_latency', metric.latency));
engine.on('circuitBreaker', (metric) => metricsClient.increment('circuit_breaker_triggers'));
Deploy to staging and run the manipulation test suite before promoting to production. Ensure all downstream consumers handle confidence flags and stale data warnings appropriately.