Интеграция с CoinGecko API
CoinGecko — один из двух крупных агрегаторов рыночных данных (второй — CoinMarketCap). Если нужны цены токенов, исторические данные, метаданные монет или рыночная капитализация — CoinGecko API покрывает большинство задач. Free tier (Demo) достаточен для большинства приложений; Pro даёт выше лимиты и дополнительные эндпоинты.
Лимиты и аутентификация
| Тариф | Rate limit | Лимит/месяц |
|---|---|---|
| Demo (бесплатно) | 30 req/min | ~10 000 |
| Analyst | 500 req/min | 500 000 |
| Lite | 500 req/min | 500 000 |
| Pro | 1 000 req/min | Unlimited |
Demo-ключ получается на сайте, передаётся как x-cg-demo-api-key header или ?x_cg_demo_api_key= параметр. Без ключа — очень жёсткий rate limit (~10 req/min), в production так работать нельзя.
const COINGECKO_BASE = 'https://api.coingecko.com/api/v3';
class CoinGeckoClient {
private apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
private async get<T>(endpoint: string, params: Record<string, string> = {}): Promise<T> {
const url = new URL(`${COINGECKO_BASE}${endpoint}`);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
const response = await fetch(url.toString(), {
headers: {
'x-cg-demo-api-key': this.apiKey,
'Accept': 'application/json',
},
});
if (response.status === 429) {
throw new RateLimitError('CoinGecko rate limit exceeded');
}
if (!response.ok) {
throw new Error(`CoinGecko API error: ${response.status}`);
}
return response.json();
}
}
Ключевые эндпоинты
Цена токена — самый частый запрос:
// Цена одного или нескольких токенов
async function getTokenPrices(
coinIds: string[],
vsCurrencies: string[] = ['usd', 'eur']
): Promise<Record<string, Record<string, number>>> {
return this.get('/simple/price', {
ids: coinIds.join(','), // 'bitcoin,ethereum,tether'
vs_currencies: vsCurrencies.join(','),
include_24hr_change: 'true',
include_last_updated_at: 'true',
});
}
// По адресу контракта (без знания CoinGecko ID)
async function getTokenPriceByContract(
contractAddress: string,
platform: string = 'ethereum' // 'binance-smart-chain', 'polygon-pos', etc.
): Promise<TokenPrice> {
return this.get(`/simple/token_price/${platform}`, {
contract_addresses: contractAddress,
vs_currencies: 'usd',
include_24hr_change: 'true',
});
}
Исторические данные для графиков:
// OHLC данные (свечи)
async function getOhlcData(coinId: string, days: number): Promise<[number, number, number, number, number][]> {
// Возвращает массив [timestamp, open, high, low, close]
return this.get(`/coins/${coinId}/ohlc`, {
vs_currency: 'usd',
days: days.toString(), // 1, 7, 14, 30, 90, 180, 365, 'max'
});
}
// Market chart (цена + volume + marketcap по времени)
async function getMarketChart(coinId: string, days: number) {
return this.get(`/coins/${coinId}/market_chart`, {
vs_currency: 'usd',
days: days.toString(),
interval: days <= 1 ? 'minutely' : days <= 90 ? 'hourly' : 'daily',
});
}
Кеширование — обязательно
Дёргать CoinGecko на каждый запрос пользователя — быстрый путь к исчерпанию лимитов. Цены обновляются каждые 60 секунд; кеш на 30–60 секунд не ухудшает точность:
import { Redis } from 'ioredis';
class CachedCoinGeckoClient extends CoinGeckoClient {
constructor(private redis: Redis, apiKey: string) {
super(apiKey);
}
async getCachedPrice(coinId: string): Promise<number> {
const cacheKey = `coingecko:price:${coinId}`;
const cached = await this.redis.get(cacheKey);
if (cached) return parseFloat(cached);
const data = await this.getTokenPrices([coinId]);
const price = data[coinId]?.usd;
if (price) {
await this.redis.setex(cacheKey, 60, price.toString()); // TTL 60 секунд
}
return price;
}
}
Для high-traffic сервисов: фоновый job обновляет цены каждые 30 секунд, все пользовательские запросы читают из кеша.
Поиск CoinGecko ID по контракту
Проблема: у вас есть адрес токена, но не его CoinGecko ID. Решение:
// Получить список всех монет с их контрактами
// Этот эндпоинт вызывается редко, кешируется на часы
async function buildContractToIdMap(platform: string): Promise<Map<string, string>> {
const coins = await this.get<Array<{ id: string; platforms: Record<string, string> }>>(
'/coins/list',
{ include_platform: 'true' }
);
const map = new Map<string, string>();
for (const coin of coins) {
const contractAddress = coin.platforms[platform];
if (contractAddress) {
map.set(contractAddress.toLowerCase(), coin.id);
}
}
return map;
}
Список монет (~15 000 позиций) кешируйте на несколько часов — он меняется редко.
Обработка ошибок и retry
async function fetchWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (err instanceof RateLimitError) {
// Экспоненциальный backoff при rate limit
const delay = baseDelay * Math.pow(2, attempt);
await new Promise(r => setTimeout(r, delay));
continue;
}
throw err; // Другие ошибки не ретраим
}
}
throw new Error('Max retries exceeded');
}
Для критичных сервисов: добавьте fallback на CoinMarketCap или Binance Public API при недоступности CoinGecko.







