feat(token-aggregation): add historical pricing context, backfill, and indexer hardening
Some checks failed
CI/CD Pipeline / Solidity Contracts (pull_request) Failing after 1m6s
CI/CD Pipeline / Security Scanning (pull_request) Successful in 12m42s
CI/CD Pipeline / Lint and Format (pull_request) Failing after 42s
CI/CD Pipeline / Terraform Validation (pull_request) Failing after 25s
CI/CD Pipeline / Kubernetes Validation (pull_request) Successful in 27s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (pull_request) Failing after 49s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (pull_request) Failing after 26s
Validation / validate-genesis (pull_request) Successful in 36s
Validation / validate-terraform (pull_request) Failing after 28s
Validation / validate-kubernetes (pull_request) Failing after 12s
Validation / validate-smart-contracts (pull_request) Failing after 13s
Validation / validate-security (pull_request) Failing after 1m39s
Validation / validate-documentation (pull_request) Failing after 18s

This commit is contained in:
defiQUG
2026-04-25 23:45:07 -07:00
parent c3b1b2cebc
commit fcd55aa9c4
18 changed files with 1963 additions and 1095 deletions

View File

@@ -67,6 +67,10 @@ interface CoinGeckoTrending {
}>;
}
interface CoinGeckoMarketChartRangeResponse {
prices?: Array<[number, number]>;
}
// Chain ID to CoinGecko platform ID mapping
const CHAIN_TO_PLATFORM: Record<number, string> = {
1: 'ethereum',
@@ -79,6 +83,17 @@ const CHAIN_TO_PLATFORM: Record<number, string> = {
// Note: 138 and 651940 are likely not supported, will return null gracefully
};
const REFERENCE_SYMBOL_TO_COIN_ID: Record<string, string> = {
ETH: 'ethereum',
BTC: 'bitcoin',
BNB: 'binancecoin',
POL: 'matic-network',
AVAX: 'avalanche-2',
CELO: 'celo',
CRO: 'crypto-com-chain',
XDAI: 'xdai',
};
export class CoinGeckoAdapter implements ExternalApiAdapter {
private api: AxiosInstance;
private apiKey?: string;
@@ -322,4 +337,73 @@ export class CoinGeckoAdapter implements ExternalApiAdapter {
return [];
}
}
async getHistoricalReferencePrice(
referenceSymbol: string,
timestamp: Date
): Promise<{ priceUsd: number; effectiveTimestamp: Date } | null> {
const symbol = referenceSymbol.trim().toUpperCase();
const coinId = REFERENCE_SYMBOL_TO_COIN_ID[symbol];
if (!coinId) {
return null;
}
const from = Math.floor((timestamp.getTime() - (6 * 60 * 60 * 1000)) / 1000);
const to = Math.floor((timestamp.getTime() + (6 * 60 * 60 * 1000)) / 1000);
const cacheKey = `history_${coinId}_${from}_${to}`;
const cached = this.cache.get(cacheKey);
if (cached && cached.expiresAt > new Date()) {
return cached.data as { priceUsd: number; effectiveTimestamp: Date } | null;
}
try {
const response = await this.api.get<CoinGeckoMarketChartRangeResponse>(
`/coins/${coinId}/market_chart/range`,
{
params: {
vs_currency: 'usd',
from,
to,
},
}
);
const prices = response.data.prices || [];
if (prices.length === 0) {
return null;
}
const targetMs = timestamp.getTime();
let bestPoint: [number, number] | null = null;
let bestDistance = Number.POSITIVE_INFINITY;
for (const point of prices) {
if (point[0] > targetMs) {
continue;
}
const distance = Math.abs(point[0] - targetMs);
if (distance < bestDistance) {
bestDistance = distance;
bestPoint = point;
}
}
if (!bestPoint || !Number.isFinite(bestPoint[1])) {
return null;
}
const resolved = {
priceUsd: bestPoint[1],
effectiveTimestamp: new Date(bestPoint[0]),
};
this.cache.set(cacheKey, {
data: resolved,
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
});
return resolved;
} catch (error) {
logger.error(`Error fetching CoinGecko historical reference price for ${referenceSymbol}:`, error);
return null;
}
}
}