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
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user