Интеграция TradingView Lightweight Charts в dApp
TradingView Lightweight Charts — open-source библиотека (~45 KB gzipped), которую используют большинство DEX'ов для отображения ценовых графиков. Dydx, GMX, Uniswap Info — все они либо используют её напрямую, либо fork'ают. Интеграция в dApp несложная, но есть несколько мест, где производительность ломается при работе с on-chain данными.
Базовая инициализация
import { createChart, IChartApi, CandlestickData } from 'lightweight-charts';
const chartContainer = useRef<HTMLDivElement>(null);
const chartRef = useRef<IChartApi>();
useEffect(() => {
if (!chartContainer.current) return;
const chart = createChart(chartContainer.current, {
width: chartContainer.current.clientWidth,
height: 400,
layout: {
background: { color: '#0d0d0d' },
textColor: '#9ca3af',
},
grid: {
vertLines: { color: '#1f2937' },
horzLines: { color: '#1f2937' },
},
timeScale: {
timeVisible: true,
secondsVisible: false,
},
});
chartRef.current = chart;
return () => chart.remove();
}, []);
Обязательно вызывайте chart.remove() в cleanup функции useEffect, иначе при hot reload или размонтировании компонента накапливаются утечки памяти.
Подача данных из on-chain источников
Это главная проблема при интеграции с dApp. On-chain данные для OHLCV-свечей берутся из нескольких источников:
Subgraph (The Graph) — самый распространённый вариант для DEX. Uniswap v3 subgraph предоставляет poolHourDatas и poolDayDatas с OHLC по каждому пулу. Запрос:
query GetCandles($pool: String!, $startTime: Int!) {
poolHourDatas(
where: { pool: $pool, periodStartUnix_gte: $startTime }
orderBy: periodStartUnix
first: 1000
) {
periodStartUnix
open
high
low
close
volumeUSD
}
}
Преобразование в формат LWC:
const candles: CandlestickData[] = data.poolHourDatas.map((d) => ({
time: d.periodStartUnix as UTCTimestamp,
open: parseFloat(d.open),
high: parseFloat(d.high),
low: parseFloat(d.low),
close: parseFloat(d.close),
}));
candleSeries.setData(candles);
Real-time обновления — polling subgraph каждые 30–60 секунд или WebSocket подписка на Swap-события через RPC. При получении нового события пересчитываем текущую незакрытую свечу и обновляем через candleSeries.update(newCandle) вместо setData (полный пересет данных при каждом тике убивает производительность).
Синхронизация нескольких графиков
Если нужны два синхронных графика (цена + объём), используем chart.timeScale().subscribeVisibleTimeRangeChange() для синхронизации viewport между инстансами. Стандартная практика для торговых интерфейсов:
chart1.timeScale().subscribeVisibleTimeRangeChange((range) => {
if (range) chart2.timeScale().setVisibleRange(range);
});
Адаптивный ресайзинг
LWC не адаптируется автоматически при изменении размера контейнера. Используем ResizeObserver:
const resizeObserver = new ResizeObserver(entries => {
const { width, height } = entries[0].contentRect;
chart.applyOptions({ width, height });
});
resizeObserver.observe(chartContainer.current);
Кастомные маркеры и overlay
Для отображения on-chain событий поверх графика (liquidations, large trades) используем series.setMarkers():
series.setMarkers([
{
time: timestamp as UTCTimestamp,
position: 'belowBar',
color: '#ef4444',
shape: 'arrowUp',
text: 'Liquidation $2.4M',
},
]);
Маркеры отображаются прямо на свечах и не требуют кастомного рендеринга — это существенно проще, чем реализовывать overlay через canvas напрямую.







