Проблема: стандартный резолвинг не тянет нагрузку
Представьте 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 в год.
Процесс резолвинга ENS
Полная цепочка ENS lookup включает шаги: нормализация имени по UTS-46, вычисление namehash (keccak256), запрос к ENS Registry для получения адреса резолвера, проверка поддержки wildcard (ENSIP-10), запрос к резолверу с coinType, и если резолвер возвращает OffchainLookup (EIP-3668), выполнение 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-вызовов для уже известных адресов. При пиковых нагрузках каждый миллисекунда на счету, поэтому мы оптимизируем сервис до максимальной производительности.
Преимущества кастомного сервиса перед библиотеками
Готовые библиотеки (ethers, viem) работают в браузере, но на бэкенде без кеша быстро исчерпывают лимиты RPC. Кастомный сервис даёт полный контроль над кешированием, поддержку wildcard, параллельный batch-резолвинг и единую точку интеграции для нескольких сетей. Ниже сравнение:
| Критерий | ethers/viem | Кастомный сервис |
|---|---|---|
| Кеширование | Нет (или ручное) | Встроенное с TTL |
| Batch-резолвинг | Последовательный | Параллельный |
| Wildcard (ENSIP-10) | Частичная | Полная поддержка |
| Multi-chain | Только ETH | Любые coinType |
| Таймауты | По умолчанию | Гибкая настройка |
Кастомный сервис обрабатывает batch-запросы в 5–10 раз быстрее.
Этапы разработки кастомного резолвинга
| Этап | Длительность | Результат |
|---|---|---|
| Анализ архитектуры | 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 дня. Стоимость рассчитывается индивидуально на основе объёма работ. Получите консультацию по вашему проекту — мы оценим архитектуру и предложим оптимизацию. Свяжитесь с нами для анализа текущей системы ENS-интеграции: выявим узкие места и ускорим резолвинг.







