Розробка системи резолвінгу ENS-доменів під навантаженням
Проблема: стандартний резолвінг не витримує навантаження
Уявіть DApp з 100 000 щоденних користувачів. Кожен виклик provider.resolveName() йде в RPC-ноду. Без кеша ви швидко впретеся в ліміти запитів — типовий Infura/Alchemy tier допускає 100 000 запитів на добу. При піку в 10 000 одночасних користувачів резолвінг може займати до 500 мс на ім'я, а при batch-обробці списку з 1000 адрес послідовний виклик бібліотеки ethers займає 2–5 хвилин. Це неприйнятно для real-time додатків. Крім того, потрібно підтримувати multi-chain адреси (ETH, BSC, Polygon), обробляти wildcard-домени (ENSIP-10) та off-chain дані через CCIP-Read. Стандартні бібліотеки не оптимізовані під такі сценарії.
Ми розробляємо кастомні ENS-резолвери, які вирішують ці проблеми. На нашому рахунку 30+ проектів, де ми впроваджували надійну інтеграцію ENS під високими навантаженнями. Кешування скорочує час резолвінгу до 1–5 мс, а batch-обробка 1000 адрес укладається у 2 секунди. Зниження витрат на RPC-провайдера досягає 40%, що при масштабуванні дає економію до $500 на місяць. При навантаженні 200 000 запитів на добу економія сягає $6000 на рік. Завдяки 30+ успішним впровадженням, ми гарантуємо надійність та продуктивність вашого резолвінгу.
Процес резолвінгу ENS
Повний ланцюжок ENS lookup включає кроки: нормалізація імені за UTS-46, обчислення namehash (keccak256), запит до ENS Registry для отримання адреси резолвера, перевірка підтримки wildcard (ENSIP-10), запит до резолвера з coinType, і якщо резолвер повертає OffchainLookup (EIP-3668), виконання CCIP-Read. Кожен крок може бути вузьким місцем. Наш кастомний сервіс бере на себе весь ланцюжок, кешуючи проміжні результати.
Деталі про обробку помилок
В нашому сервісі ми обробляємо помилки за допомогою try-catch, повертаючи null для неіснуючих імен. При помилках CCIP-Read ми повторюємо запит з більшим таймаутом.Кастомний резолвер сервіс з кешуванням
Для production-систем ми реалізуємо власний резолвінг-сервіс з розподіленим кешем (Redis + in-memory cache). Приклад на TypeScript з viem (версія 2.x) та node-cache:
import { createPublicClient, http, normalize } from "viem"; import { mainnet } from "viem/chains"; import NodeCache from "node-cache"; class ENSResolutionService { private client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) }); private cache = new NodeCache({ stdTTL: 300 }); // 5 хвилин TTL async resolveName(name: string): Promise<string | null> { const normalized = normalize(name); const cacheKey = `addr:${normalized}`; const cached = this.cache.get<string>(cacheKey); if (cached !== undefined) return cached; try { const address = await this.client.getEnsAddress({ name: normalized }); this.cache.set(cacheKey, address ?? null); return address; } catch (e) { return null; } } async lookupAddress(address: `0x${string}`): Promise<string | null> { const cacheKey = `name:${address.toLowerCase()}`; const cached = this.cache.get<string>(cacheKey); if (cached !== undefined) return cached; const name = await this.client.getEnsName({ address }); this.cache.set(cacheKey, name ?? null); return name; } async batchLookup(addresses: `0x${string}`[]): Promise<Map<string, string | null>> { const results = new Map<string, string | null>(); const uncached: `0x${string}`[] = []; for (const addr of addresses) { const cached = this.cache.get<string>(`name:${addr.toLowerCase()}`); if (cached !== undefined) { results.set(addr, cached); } else { uncached.push(addr); } } await Promise.allSettled( uncached.map(async (addr) => { const name = await this.lookupAddress(addr); results.set(addr, name); }) ); return results; } } Цей код легко інтегрується в бекенд. Кеш з TTL 5 хвилин знижує навантаження на RPC в десятки разів. Для розподіленого середовища використовуємо Redis з таким же TTL. Для складних сценаріїв ми також розробляємо Solidity-резолвери, які можуть бути розгорнуті як власні контракти.
Як реалізувати multi-chain адреси?
ENS підтримує зберігання адрес для різних блокчейнів через coinType (SLIP-44). Наприклад, ETH = 60, BTC = 0, SOL = 501, MATIC = 966. Наш сервіс дозволяє отримувати адреси для будь-якого coinType однією функцією:
const COIN_TYPES = { ETH: 60, BTC: 0, SOL: 501, MATIC: 966, ARB: 9001 }; async function getMultiChainAddresses(name: string) { const resolver = await provider.getResolver(name); if (!resolver) return null; const eth = await resolver.getAddress(COIN_TYPES.ETH); const btc = await resolver.getAddress(COIN_TYPES.BTC); const sol = await resolver.getAddress(COIN_TYPES.SOL); return { eth, btc, sol }; } Ви можете додати будь-які coinType, включаючи MATIC, ARB, OP. Ми надаємо єдиний інтерфейс для роботи з кількома мережами.
Як обробляти CCIP-Read в кастомному резолвері?
CCIP-Read (EIP-3668) дозволяє резолверу повертати посилання на off-chain дані замість адреси. Клієнт повинен виконати HTTP-запит за цим посиланням та верифікувати підпис. У нашому сервісі ми автоматично обробляємо OffchainLookup: після отримання помилки ми витягуємо URL та дані, завантажуємо їх, перевіряємо підпис (якщо потрібно) та кешуємо результат. Детальніше в EIP-3668. Для wildcard-резолверів (ENSIP-10) ми реалізували повну підтримку — дивіться ENSIP-10.
Прискорення резолвінгу з кастомним сервісом
Візьмемо реальний кейс: клієнтський додаток резолвить імена 10 000 користувачів при завантаженні. З ethers послідовно — ~50 секунд. З кастомним сервісом з кешем та паралельними запитами — 2 секунди. Різниця в 25 разів. Досягається це за рахунок кешування часто запитуваних імен, паралельної обробки batch-запитів через Promise.allSettled та відсутності повторних RPC-викликів для вже відомих адрес. При пікових навантаженнях кожна мілісекунда на рахунку, тому ми оптимізуємо сервіс до максимальної продуктивності. Кешування знижує час резолвінгу до 1–5 мс, що в 100 разів швидше за стандартний підхід.
Переваги кастомного сервісу перед бібліотеками
Готові бібліотеки (ethers, viem) працюють в браузері, але на бекенді без кеша швидко вичерпують ліміти RPC. Кастомний сервіс дає повний контроль над кешуванням, підтримку wildcard, паралельний batch-резолвінг та єдину точку інтеграції для кількох мереж. Нижче порівняння:
| Критерій | ethers/viem | Кастомний сервіс |
|---|---|---|
| Кешування | Немає (або ручне) | Вбудоване з TTL |
| Batch-резолвінг | Послідовний | Паралельний |
| Wildcard (ENSIP-10) | Часткова | Повна підтримка |
| Multi-chain | Тільки ETH | Будь-які coinType |
| Таймаути | За замовчуванням | Гнучке налаштування |
Кастомний сервіс обробляє batch-запити в 5–10 разів швидше, ніж ethers/viem.
Етапи розробки кастомного резолвінгу
Розробка кастомного резолвінгу проходить такі кроки:
- Аналіз архітектури (0.5 дня)
- Проектування сервісу (1 день)
- Реалізація ядра (2-3 дні)
- Інтеграція в бекенд (1-2 дні)
- Моніторинг та оптимізація (1 день)
| Етап | Тривалість | Результат |
|---|---|---|
| Аналіз архітектури | 0.5 дня | Технічне завдання з метриками |
| Проектування сервісу | 1 день | Документація API, схема кешування |
| Реалізація ядра | 2–3 дні | Працюючий резолвер з тестами |
| Інтеграція в бекенд | 1–2 дні | Готовий endpoint |
| Моніторинг та оптимізація | 1 день | Dashboard, алерти, навантажувальне тестування |
Що входить в роботу
- Документація API резолвера — REST або JSON-RPC специфікація з прикладами запитів
- Розгортання сервісу на вашій інфраструктурі (Docker, Kubernetes, Lambda) з моніторингом та алертингом
- Інтеграція з існуючим бекендом — адаптація під fastify, express, serverless
- Навчання команди — воркшоп з використання резолвера та troubleshooting
- Технічна підтримка — місяць після впровадження
Строки та вартість
Розробка кастомного резолвінг-сервісу з кешем та multi-chain підтримкою займає від 3 до 5 робочих днів. Інтеграція в існуючий бекенд — ще 1–2 дні. Вартість такого сервісу зазвичай становить від $1500 до $5000 залежно від складності. Отримайте консультацію щодо вашого проекту — ми оцінимо архітектуру та запропонуємо оптимізацію. Зв'яжіться з нами для аналізу поточної системи ENS-інтеграції: виявимо вузькі місця та прискоримо резолвінг.







