Агрегація балансів криптовалютного портфеля — одне з найнетривіальніших завдань у Web3-розробці. Користувач може тримати активи в десятку мереж (Ethereum, Polygon, Arbitrum, Solana), у сотнях токенів, у DeFi-протоколах на кшталт Uniswap V3 з унікальною математикою позицій, і на централізованих біржах. Кожне джерело вимагає свого підходу до отримання даних. Якщо не продумати архітектуру з самого початку, система буде гальмувати, витрачати ліміти RPC і видавати некоректний PnL. Ми набили гулі на 30+ подібних проектах і знаємо, як уникнути граблів. Multicall3 — один із ключових інструментів, що дозволяє агрегувати дані з EVM-мереж із мінімальними витратами.
Розробка портфельного трекера: ключові архітектурні рішення
Як агрегувати баланси з різних мереж?
On-chain баланси
EVM-мережі: нативний баланс через eth_getBalance, ERC-20 баланси складніші — один RPC виклик повертає баланс одного токена на одному адресі. При 50 токенів × 5 мереж = 250 викликів. Рішення — Multicall3 (задеплоєний на всіх EVM-мережах за адресою 0xcA11bde05977b3631167028862bE2a173976CA11). Один RPC виклик замість 50 — це в 50 разів швидше і знижує витрати на інфраструктуру до 90%. Multicall3 — перевірений контракт, що використовується в тисячах проектів. Документація Multicall3 рекомендує batch-запити для зниження навантаження.
import { createPublicClient, http, parseAbi } from "viem"; import { mainnet } from "viem/chains"; const client = createPublicClient({ chain: mainnet, transport: http() }); const MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11"; const ERC20_ABI = parseAbi(["function balanceOf(address) view returns (uint256)"]); async function getTokenBalances( walletAddress: `0x${string}`, tokenAddresses: `0x${string}`[] ) { const calls = tokenAddresses.map((tokenAddress) => ({ address: tokenAddress, abi: ERC20_ABI, functionName: "balanceOf" as const, args: [walletAddress], })); return client.multicall({ contracts: calls }); } Альтернатива: Alchemy/Moralis Token API — один запит повертає всі ERC-20 баланси з метаданими та USD ціною. Платно, але економить час розробки. Для Solana використовуємо getMultipleAccounts або Anchor для читання акаунтів.
DeFi позиції
Це найскладніша частина трекера. Liquidity позиція в Uniswap V3, collateral в Aave, стейкнуті токени в Curve — кожен протокол зберігає дані по-своєму. Порівняємо підходи:
| Підхід | Швидкість | Складність | Вартість |
|---|---|---|---|
| Прямі виклики контрактів | Висока (залежить від кількості запитів) | Висока (потрібна математика тиків) | Безкоштовно (тільки газ) |
| The Graph subgraphs | Середня (GraphQL запити) | Середня (вивчення схеми) | Безкоштовно (хостинг) |
| Агрегатори (DeBank, Zapper) | Висока (кешовані дані) | Низька (один API) | Платно (підписка) |
Для більшості проектів ми рекомендуємо комбінацію: прямі виклики для основних протоколів + агрегатори для long-tail. Наприклад, для Uniswap V3 використовуємо positions і tick контракти, а для Curve — get_balances. Помилка в розрахунку PnL може виникнути через розсинхронізацію цін і блоків — ми вирішуємо це прив'язкою знімків до номера блоку.
CEX баланси
Біржові API віддають баланси миттєво, але вимагають read-only API ключ від користувача. Використовуємо CCXT — бібліотеку з єдиним інтерфейсом для 100+ бірж.
import ccxt from "ccxt"; async function getBinanceBalances(apiKey: string, secret: string) { const exchange = new ccxt.binance({ apiKey, secret, sandbox: false }); const balance = await exchange.fetchBalance(); return balance.total; // { BTC: 0.5, ETH: 2.3, USDT: 1000 } } CCXT — популярна бібліотека з відкритим вихідним кодом, що підтримує 100+ бірж.
Чому важлива стратегія оновлення даних?
Дані потрібно оновлювати, але не молотити RPC кожну секунду. Стратегія:
| Тип даних | Частота оновлення | Причина |
|---|---|---|
| On-chain баланси | Кожні 30–60 сек | Новий блок |
| CEX баланси | Кожні 30 сек | API ліміти |
| DeFi позиції | Кожні 2–5 хв | Повільно змінюються |
| Ціни токенів | Кожні 10–30 сек | Критично для P&L |
| Historical P&L | Фоновий job, 1/год | Важкі обчислення |
Background jobs через Redis + BullMQ з окремими чергами. Результати кешуємо в Redis, фронтенд читає з кешу. Для real-time використовуємо Server-Sent Events або WebSocket з push-оновленнями.
Зберігання історичних даних
Для P&L трекінгу потрібні знімки портфеля в часі. TimescaleDB (розширення PostgreSQL) ідеально підходить:
CREATE TABLE portfolio_snapshots ( user_id UUID, snapshot_at TIMESTAMPTZ NOT NULL, total_usd NUMERIC(20, 2), breakdown JSONB ); SELECT create_hypertable('portfolio_snapshots', 'snapshot_at'); Запит P&L за період:
SELECT time_bucket('1 day', snapshot_at) AS day, last(total_usd, snapshot_at) AS end_of_day_value FROM portfolio_snapshots WHERE user_id = $1 AND snapshot_at > NOW() - INTERVAL '30 days' GROUP BY day ORDER BY day; Як налаштувати Multicall3 у своєму проекті
- Встановіть viem:
npm install viem. - Імпортуйте createPublicClient і конфігурацію мережі.
- Викличте
client.multicallз масивом контрактів і функцій. - Обробіть результат — він поверне масив об'єктів з полями result і error.
Стандартний підхід, описаний у документації OpenZeppelin, рекомендує використовувати Multicall3 для batch-запитів і зниження навантаження на RPC.
Що входить у роботу
Ми передаємо повний комплект документації, вихідний код з коментарями, інструкцію з деплою, доступ до Git-репозиторію та підтримку протягом місяця після запуску. За потреби навчаємо вашу команду роботі з системою.
Обговоріть ваше завдання з нашими інженерами — оцінимо проект за 1 день і запропонуємо оптимальне рішення. Замовте розробку портфельного трекера під ключ з гарантією якості та дотриманням строків. Зв'яжіться з нами, щоб розпочати роботу.







