Разработка системы резолвинга ENS-доменов
ENS резолвинг — это не просто вызов provider.resolveName(). Для production систем нужно учесть кеширование, поддержку wildcard, CCIP-Read (off-chain resolvers), и корректную обработку multi-chain адресов. Особенно если резолвинг происходит не в браузере, а в backend сервисах или смарт-контрактах.
Полная цепочка резолвинга
Стандартный ENS lookup выглядит так:
- Нормализация имени по UTS-46 (lowercase + IDNA2008)
- Вычисление namehash
- Запрос к ENS Registry:
registry.resolver(namehash) - Если resolver поддерживает wildcard (
supportsInterface(ENSIP-10)) — используем extended resolution - Запрос к Resolver:
resolver.addr(namehash, coinType) - Если resolver возвращает
OffchainLookup(EIP-3668) — выполняем CCIP-Read - Финальный адрес
Большинство библиотек (ethers.js, viem) обрабатывают шаги 1-5. CCIP-Read поддерживается в ethers v6 и viem нативно.
Кастомный резолвер сервис
Для высоконагруженных приложений стоит реализовать собственный резолвинг сервис с кешем:
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;
}
// Batch resolution для списка адресов
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;
}
}
Multi-chain адреса (ENSIP-9)
ENS хранит адреса для разных сетей через coinType (SLIP-44):
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;
return {
eth: await resolver.getAddress(COIN_TYPES.ETH),
btc: await resolver.getAddress(COIN_TYPES.BTC),
sol: await resolver.getAddress(COIN_TYPES.SOL),
};
}
Срок разработки кастомного резолвинг сервиса с кешем и multi-chain поддержкой — 3-5 рабочих дней. Интеграция в существующий бэкенд — 1-2 дня дополнительно.







