
本文介绍使用 ethers.js v6 高效批量读取 ENS 域名全部文本记录的正确实践,避免全链日志扫描性能瓶颈,通过 MulticallProvider 与 getResolver + getText 组合实现精准、可靠、低开销的数据拉取。
本文介绍使用 ethers.js v6 高效批量读取 ens 域名全部文本记录的正确实践,避免全链日志扫描性能瓶颈,通过 `multicallprovider` 与 `getresolver` + `gettext` 组合实现精准、可靠、低开销的数据拉取。
在以太坊生态中,ENS(Ethereum Name Service)支持为域名绑定丰富的文本记录(Text Records),如 email、url、avatar、description 等。但直接通过 provider.getLogs() 全范围扫描 PublicResolver 合约日志(如你示例中硬编码地址与事件 topic 的方式)存在严重缺陷:日志量巨大、匹配不精确、无法区分归属域名、且易受历史重放或无关更新干扰。
推荐方案:基于解析器(Resolver)的主动查询法
核心思路是——不依赖日志回溯,而是先定位目标域名的 Resolver 地址,再枚举已设置的 key 并逐个调用 resolver.getText(key) 获取值。该方法语义清晰、结果确定、无需区块范围参数,且天然适配 ENS 的设计范式。
以下是完整、可运行的实现(需配合 @ethers-ext/provider-multicall 提升并发效率):
npm install ethers @ethers-ext/provider-multicall
import { ethers } from "ethers";
import { MulticallProvider } from "@ethers-ext/provider-multicall";
async function getTextRecords(provider: ethers.Provider, name: string): Promise<map string>> {
// 使用 MulticallProvider 批量优化后续 getText 调用(减少 RPC 请求次数)
const multicallProvider = new MulticallProvider(provider);
// 1. 获取该域名当前绑定的 Resolver 地址(自动处理 .eth 及其他 TLD)
const resolver = await multicallProvider.getResolver(name);
if (!resolver || !resolver.address) {
throw new Error(`No resolver found for ${name}`);
}
// 2. 构建 TextChanged 事件过滤器(仅关注本域名的变更日志,用于推断已设置的 keys)
const contract = new ethers.Contract(
resolver.address,
["event TextChanged(bytes32 indexed node, string indexed _key, string key)"],
provider
);
const filter = contract.filters.TextChanged(ethers.namehash(name));
// 3. 查询该域名相关的 TextChanged 日志(范围可控,默认为最新区块,也可加 fromBlock/toBlock)
const logs = await contract.queryFilter(filter);
// 4. 提取所有唯一 key(去重)
const keys = [...new Set(logs.map(log => log.args.key))];
// 5. 并行批量获取各 key 对应的值(自动跳过未设置或失败的 key)
const values = await Promise.all(
keys.map(key =>
resolver.getText(key).catch(() => null)
)
);
// 6. 构建并返回键值映射
return keys.reduce((map, key, i) => {
const value = values[i];
if (value !== null && value !== undefined) {
map.set(key, value);
}
return map;
}, new Map<string string>());
}
// ✅ 使用示例
(async () => {
const provider = new ethers.InfuraProvider("mainnet", "YOUR_INFURA_KEY");
// 或使用 Alchemy、Base Provider 等
// const provider = new ethers.AlchemyProvider("mainnet", "YOUR_ALCHEMY_KEY");
try {
const records = await getTextRecords(provider, "vitalik.eth");
console.log("Text Records:", Object.fromEntries(records));
// 输出类似:{ 'avatar': 'https://...', 'email': 'vitalik@ethereum.org', ... }
} catch (err) {
console.error("Failed to fetch text records:", err);
}
})();</string></map>
✅ 关键优势说明:
- 精准性:getResolver(name) 确保只操作当前生效的解析器;TextChanged(node) 过滤器将日志限定在目标域名下,避免全局污染。
- 健壮性:getText(key) 是 ENS 标准接口,无论记录是何时写入、是否被覆盖,均返回最新值;失败 key 自动忽略,不中断流程。
- 性能友好:MulticallProvider 将多个 getText 调用合并为单次 RPC 请求(底层使用 eth_call batch),显著降低延迟与请求次数。
- 无需手动管理区块范围:queryFilter 默认从最新区块开始检索,兼顾实时性与轻量性;如需历史快照,可显式传入 fromBlock/toBlock。
⚠️ 注意事项:
- @ethers-ext/provider-multicall 是社区维护的扩展包(非官方),生产环境建议锁定版本(如 ^6.10.0)并验证其兼容性;若倾向零依赖,可改用 Promise.allSettled() + 原生 provider,但性能略低。
- 某些旧版 Resolver(如早期 PublicResolver v1)可能不支持 TextChanged 事件,此时日志为空,但 getText 仍有效——因此该方案具备优雅降级能力。
- 若需获取 所有可能的 文本 key(而非仅已设置的),目前无链上通用方式,需依赖中心化索引(如 ENS Subgraph)或预定义 key 列表。
综上,放弃“暴力扫日志”的反模式,转向“查解析器 + 查事件 + 查值”的三层精准查询,才是 ethers.js 生态中获取 ENS 文本记录的现代、可靠、可持续的最佳实践。










