From 020dec217abc11968a16946596754c078e9aa60c Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Thu, 13 Nov 2025 16:19:15 -0800 Subject: [PATCH 1/2] changes --- src.ts/providers/abstract-provider.ts | 158 +++++++++++++++------ src.ts/providers/ens-resolver.ts | 190 +++++++++++++------------- src.ts/providers/provider.ts | 4 +- src.ts/utils/cointype.ts | 23 ++++ 4 files changed, 236 insertions(+), 139 deletions(-) create mode 100755 src.ts/utils/cointype.ts diff --git a/src.ts/providers/abstract-provider.ts b/src.ts/providers/abstract-provider.ts index bd4168b51a..c359fa9813 100644 --- a/src.ts/providers/abstract-provider.ts +++ b/src.ts/providers/abstract-provider.ts @@ -23,7 +23,8 @@ import { FetchRequest, toBeArray, toQuantity, defineProperties, EventPayload, resolveProperties, - toUtf8String + toUtf8String, + isError } from "../utils/index.js"; import { EnsResolver } from "./ens-resolver.js"; @@ -54,14 +55,41 @@ import type { PreparedTransactionRequest, Provider, ProviderEvent, TransactionRequest } from "./provider.js"; +import { Interface } from "../abi/interface.js"; +import type { HexString } from "../utils/data.js"; + +// https://docs.ens.domains/ensip/21 +const LOCAL_BATCH_GATEWAY_URL = 'x-batch-gateway:true'; +const LOCAL_BATCH_GATEWAY_ABI = new Interface([ + "function query((address sender, string[] urls, bytes calldata)[]) view returns (bool[], bytes[])", // = query(CcipRequest[]) + "error HttpError(uint16 status, string message)", + "error Error(string)", +]); + +type CcipRequest = { + sender: HexString; + urls: string[]; + calldata: HexString; +} -type Timer = ReturnType; +type CcipArgs = CcipRequest & { + selector: HexString; + extraData: HexString; +}; + +const ORDERED_CCIP_ARGS = ["sender", "urls", "calldata", "selector", "extraData"] as const; + +function toErrorArgs(args: CcipArgs) { + return ORDERED_CCIP_ARGS.map(x => args[x]); +} +type Timer = ReturnType; // Constants const BN_2 = BigInt(2); const MAX_CCIP_REDIRECTS = 10; +const MAX_CCIP_FETCHES = 100; function isPromise(value: any): value is Promise { return (value && typeof(value.then) === "function"); @@ -115,6 +143,14 @@ export type DebugEventAbstractProvider = { action: "receiveCcipReadCallError", transaction: { to: string, data: string } error: Error +} | { + action: "sendCcipReadBatchRequest", + requests: CcipRequest[], +} | { + action: "receiveCcipReadBatchResult", + requests: CcipRequest[], + failures: boolean[], + responses: HexString[] }; @@ -425,15 +461,6 @@ const defaultOptions = { pollingInterval: 4000 }; -type CcipArgs = { - sender: string; - urls: Array; - calldata: string; - selector: string; - extraData: string; - errorArgs: Array -}; - /** * An **AbstractProvider** provides a base class for other sub-classes to * implement the [[Provider]] API by normalizing input arguments and @@ -568,17 +595,44 @@ export class AbstractProvider implements Provider { return await perform; } - /** - * Resolves to the data for executing the CCIP-read operations. - */ - async ccipReadFetch(tx: PerformActionTransaction, calldata: string, urls: Array): Promise { - if (this.disableCcipRead || urls.length === 0 || tx.to == null) { return null; } - - const sender = tx.to.toLowerCase(); - const data = calldata.toLowerCase(); - + async #ccipReadFetch({sender, calldata: data, urls}: CcipRequest, counter = { count: 0 }): Promise { + if (urls.includes(LOCAL_BATCH_GATEWAY_URL)) { + const requests: CcipRequest[] = LOCAL_BATCH_GATEWAY_ABI.decodeFunctionData('query', data); + this.emit("debug", { action: "sendCcipReadBatchRequest", requests }); + const failures: boolean[] = [] + const responses: HexString[] = [] + await Promise.all(requests.map(async (request, i) => { + try { + responses[i] = await this.#ccipReadFetch(request, counter); + failures[i] = false; + } catch (error: unknown) { + failures[i] = true; + let errorMessage = 'unknown local batch gateway error'; + if (error instanceof Error) { + errorMessage = error.message; + } + if (isError(error, 'OFFCHAIN_FAULT') && error.info && error.info.statusCode) { + if (error.info.errorMessages) { + errorMessage = error.info.errorMessages.join('\n'); + } else if (error.info.errorMessage) { + errorMessage = error.info.errorMessage; + } + responses[i] = LOCAL_BATCH_GATEWAY_ABI.encodeErrorResult('HttpError', [ + error.info.statusCode, + error.info.errorMessages?.join('\n') ?? error.info.errorMessage + ]); + } else { + responses[i] = LOCAL_BATCH_GATEWAY_ABI.encodeErrorResult('Error', [errorMessage]); + } + } + })); + this.emit("debug", { action: "receiveCcipReadBatchResult", requests, failures, responses }); + return LOCAL_BATCH_GATEWAY_ABI.encodeFunctionResult('query', [failures, responses]); + } + assert(++counter.count > MAX_CCIP_FETCHES, `too many fetches during CCIP fetch`, 'OFFCHAIN_FAULT'); + sender = sender.toLowerCase(); + data = data.toLowerCase(); const errorMessages: Array = [ ]; - for (let i = 0; i < urls.length; i++) { const url = urls[i]; @@ -625,16 +679,30 @@ export class AbstractProvider implements Provider { // 4xx indicates the result is not present; stop assert(resp.statusCode < 400 || resp.statusCode >= 500, `response not found during CCIP fetch: ${ errorMessage }`, - "OFFCHAIN_FAULT", { reason: "404_MISSING_RESOURCE", transaction: tx, info: { url, errorMessage } }); + "OFFCHAIN_FAULT", { reason: "404_MISSING_RESOURCE", info: { url, errorMessage, statusCode: resp.statusCode } }); // 5xx indicates server issue; try the next url errorMessages.push(errorMessage); } - - assert(false, `error encountered during CCIP fetch: ${ errorMessages.map((m) => JSON.stringify(m)).join(", ") }`, "OFFCHAIN_FAULT", { + assert(false, `no response during CCIP fetch`, 'OFFCHAIN_FAULT', { reason: "500_SERVER_ERROR", - transaction: tx, info: { urls, errorMessages } - }); + info: { urls, errorMessages, statusCode: 500 } + }) + } + + /** + * Resolves to the data for executing the CCIP-read operations. + */ + async ccipReadFetch(tx: PerformActionTransaction, calldata: HexString, urls: Array): Promise { + if (this.disableCcipRead || urls.length === 0 || tx.to == null) { return null; } + try { + return await this.#ccipReadFetch({sender: tx.to, calldata, urls}); + } catch (err: unknown) { + if (isError(err, 'OFFCHAIN_FAULT')) { + err.transaction = tx; + } + throw err; + } } /** @@ -1008,14 +1076,24 @@ export class AbstractProvider implements Provider { revert: { signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)", name: "OffchainLookup", - args: ccipArgs.errorArgs + args: toErrorArgs(ccipArgs) } }); - const ccipResult = await this.ccipReadFetch(transaction, ccipArgs.calldata, ccipArgs.urls); - assert(ccipResult != null, "CCIP Read failed to fetch data", "OFFCHAIN_FAULT", { - reason: "FETCH_FAILED", transaction, info: { data: error.data, errorArgs: ccipArgs.errorArgs } }); - + let ccipResult: HexString; + try { + ccipResult = await this.#ccipReadFetch(ccipArgs); + } catch (cause: unknown) { + assert(false, "CCIP Read failed to fetch data", "OFFCHAIN_FAULT", { + reason: "FETCH_FAILED", + transaction, + info: { + data: error.data, + errorArgs: toErrorArgs(ccipArgs), + cause + } + }); + } const tx = { to: txSender, data: concat([ ccipArgs.selector, encodeBytes([ ccipResult, ccipArgs.extraData ]) ]) @@ -1186,24 +1264,24 @@ export class AbstractProvider implements Provider { }); } - async getResolver(name: string, preferUniversal?: boolean): Promise { - return await EnsResolver.fromName(this, name, preferUniversal); + async getResolver(name: string): Promise { + return EnsResolver.fromName(this, name); } async getAvatar(name: string): Promise { - const resolver = await this.getResolver(name, true); + const resolver = await this.getResolver(name); if (resolver) { return await resolver.getAvatar(); } return null; } - async resolveName(name: string): Promise{ - const resolver = await this.getResolver(name, true); - if (resolver) { return await resolver.getAddress(); } + async resolveName(name: string, coinType?: BigNumberish): Promise{ + const resolver = await this.getResolver(name); + if (resolver) { return await resolver.getAddress(coinType); } return null; } - async lookupAddress(address: string): Promise { - return await EnsResolver.lookupAddress(this, address); + async lookupAddress(address: HexString, coinType?: BigNumberish): Promise { + return await EnsResolver.lookupAddress(this, address, coinType); } async waitForTransaction(hash: string, _confirms?: null | number, timeout?: null | number): Promise { @@ -1716,7 +1794,5 @@ function parseOffchainLookup(data: string): CcipArgs { }); } - result.errorArgs = "sender,urls,calldata,selector,extraData".split(/,/).map((k) => (result)[k]) - return result; } diff --git a/src.ts/providers/ens-resolver.ts b/src.ts/providers/ens-resolver.ts index d1a6d14cad..7531e5b91b 100644 --- a/src.ts/providers/ens-resolver.ts +++ b/src.ts/providers/ens-resolver.ts @@ -8,21 +8,24 @@ import { getAddress } from "../address/index.js"; import { ZeroAddress } from "../constants/index.js"; import { Contract } from "../contract/index.js"; -import { dnsEncode, namehash } from "../hash/index.js"; +import { dnsEncode, ensNormalize, namehash } from "../hash/index.js"; import { - hexlify, isHexString, toBeHex, + hexlify, toBeHex, defineProperties, encodeBase58, assert, assertArgument, isError, - FetchRequest + FetchRequest, + getBigInt } from "../utils/index.js"; import type { FunctionFragment } from "../abi/index.js"; -import type { BytesLike } from "../utils/index.js"; +import type { BigNumberish, BytesLike } from "../utils/index.js"; -import type { AbstractProvider, AbstractProviderPlugin } from "./abstract-provider.js"; +import { AbstractProvider, type AbstractProviderPlugin } from "./abstract-provider.js"; import type { EnsPlugin } from "./plugins-network.js"; import type { Provider } from "./provider.js"; +import type { HexString } from "../utils/data.js"; +import { chainFromCoinType, coinTypeFromChain, isEVMCoinType } from "../utils/cointype.js"; // @TODO: This should use the fetch-data:ipfs gateway // Trim off the ipfs:// prefix and return the default gateway URL @@ -102,7 +105,7 @@ export abstract class MulticoinProviderPlugin implements AbstractProviderPlugin defineProperties(this, { name }); } - connect(proivder: Provider): MulticoinProviderPlugin { + connect(provider: Provider): MulticoinProviderPlugin { return this; } @@ -160,7 +163,7 @@ export class EnsResolver { /** * The connected provider. */ - provider!: AbstractProvider; + provider!: Provider; /** * The address of the resolver. @@ -177,19 +180,10 @@ export class EnsResolver { readonly #resolver: Contract; - readonly #universal: null | Contract; - - constructor(provider: AbstractProvider, address: string, name: string, universalResolver?: boolean) { + constructor(provider: Provider, address: string, name: string, wildcard?: boolean) { defineProperties(this, { provider, address, name }); - if (universalResolver) { - this.#supports2544 = Promise.resolve(true); - this.#universal = createUniversal(provider, address); - } else { - this.#supports2544 = null; - this.#universal = null; - } - + this.#supports2544 = typeof wildcard === 'boolean' ? Promise.resolve(wildcard) : null; this.#resolver = new Contract(address, [ "function supportsInterface(bytes4) view returns (bool)", @@ -198,6 +192,7 @@ export class EnsResolver { "function addr(bytes32, uint) view returns (bytes)", "function text(bytes32, string) view returns (string)", "function contenthash(bytes32) view returns (bytes)", + "function name(bytes32) view returns (string)", ], provider); } @@ -251,12 +246,7 @@ export class EnsResolver { params.push({ enableCcipRead: true }); try { - let result; - if (this.#universal) { - result = (await this.#universal.resolve(...params)).result; - } else { - result = await this.#resolver[funcName](...params); - } + let result = await this.#resolver[funcName](...params); if (fragment) { return iface.decodeFunctionResult(fragment, result)[0]; @@ -271,59 +261,58 @@ export class EnsResolver { return null; } + async getName(): Promise { + return this.#fetch("name(bytes32)"); + } + + async getRawAddress(coinType: bigint): Promise { + return coinType == 60n + ? this.#fetch("addr(bytes32)") + : this.#fetch("addr(bytes32,uint)", [coinType]); + } + + async getEvmAddress(chain: number): Promise { + const data = await this.getRawAddress(coinTypeFromChain(chain)); + return data === '0x' ? ZeroAddress : getAddress(data); + } + /** * Resolves to the address for %%coinType%% or null if the * provided %%coinType%% has not been configured. */ - async getAddress(coinType?: number): Promise { - if (coinType == null) { coinType = 60; } - if (coinType === 60) { - try { - const result = await this.#fetch("addr(bytes32)"); - - // No address - if (result == null || result === ZeroAddress) { return null; } - - return result; - } catch (error: any) { - if (isError(error, "CALL_EXCEPTION")) { return null; } - throw error; + async getAddress(coinType: BigNumberish = 60): Promise { + coinType = getBigInt(coinType, "coinType"); + if (isEVMCoinType(coinType)) { + const chain = chainFromCoinType(coinType); + if (chain == 1) { + try { + return nullIfZero(await this.getEvmAddress(chain)); + } catch (error: any) { + if (isError(error, "CALL_EXCEPTION")) { return null; } + throw error; + } + } else { + return nullIfZero(await this.getEvmAddress(chain)); } } - - // Try decoding its EVM canonical chain as an EVM chain address first - if (coinType >= 0 && coinType < 0x80000000) { - let ethCoinType = coinType + 0x80000000; - - const data = await this.#fetch("addr(bytes32,uint)", [ ethCoinType ]); - if (isHexString(data, 20)) { return getAddress(data); } - } - - let coinPlugin: null | MulticoinProviderPlugin = null; - for (const plugin of this.provider.plugins) { - if (!(plugin instanceof MulticoinProviderPlugin)) { continue; } - if (plugin.supportsCoinType(coinType)) { - coinPlugin = plugin; - break; + if (coinType <= Number.MAX_SAFE_INTEGER && this.provider instanceof AbstractProvider) { + const coinNum = Number(coinType); + for (const coinPlugin of this.provider.plugins) { + if (coinPlugin instanceof MulticoinProviderPlugin && coinPlugin.supportsCoinType(coinNum)) { + const data = await this.getRawAddress(coinType); + if (data === '0x') return null; + const decoded = await coinPlugin.decodeAddress(coinNum, data); + assert(decoded, `invalid coin data`, "UNSUPPORTED_OPERATION", { + operation: `getAddress(${ coinType })`, + info: { coinType, coinPlugin, data } + }); + return decoded; + } } } - - if (coinPlugin == null) { return null; } - - // keccak256("addr(bytes32,uint256") - const data = await this.#fetch("addr(bytes32,uint)", [ coinType ]); - - // No address - if (data == null || data === "0x") { return null; } - - // Compute the address - const address = await coinPlugin.decodeAddress(coinType, data); - - if (address != null) { return address; } - - assert(false, `invalid coin data`, "UNSUPPORTED_OPERATION", { + assert(false, `unknown coin type`, "UNSUPPORTED_OPERATION", { operation: `getAddress(${ coinType })`, - info: { coinType, data } + info: { coinType } }); } @@ -586,23 +575,29 @@ export class EnsResolver { return null; } - static async lookupAddress(provider: AbstractProvider, address: string, coinType?: number): Promise { - address = getAddress(address); - if (coinType == null) { coinType = 60; } + static async lookupAddress(provider: Provider, address: string, coinType: BigNumberish = 60): Promise { + coinType = getBigInt(coinType, "coinType"); + if (isEVMCoinType(coinType)) { + address = getAddress(address); + } // We have a Universal resolver, use it const universal = await getUniversal(provider); if (universal) { - const result = await universal.reverse(address, coinType, { - enableCcipRead: true - }); - - return result.primary || null; + try { + const [primary] = await universal.reverse(address, coinType, {enableCcipRead: true}); + if (primary == ensNormalize(primary)) { + return primary; + } + } catch (ignored: unknown) { + // can we surface these errors? + } + return null; } // Use legacy reverse lookup - - assert(coinType === 60, "lookupAddress coinType requires ENS Universal Resolver", "UNSUPPORTED_OPERATION", { + + assert(coinType === 60n, "lookupAddress coinType requires ENS Universal Resolver", "UNSUPPORTED_OPERATION", { operation: "lookupAddress" }); @@ -618,14 +613,10 @@ export class EnsResolver { const resolver = await ensContract.resolver(node); if (resolver == null || resolver === ZeroAddress) { return null; } - const resolverContract = new Contract(resolver, [ - "function name(bytes32) view returns (string)" - ], provider); - - const name = await resolverContract.name(node); + const name = await resolver.getName(node); // Failed forward resolution - const check = await provider.resolveName(name); + const check = await provider.resolveName(name, coinType); if (check !== address) { return null; } return name; @@ -640,8 +631,6 @@ export class EnsResolver { throw error; } - - return null; } /** @@ -654,17 +643,13 @@ export class EnsResolver { * more eth_call efficient, as it performs more on-chain, but is not * the actual resolver, if for example the setters are required. */ - static async fromName(provider: AbstractProvider, name: string, preferUniversal?: boolean): Promise { + static async fromName(provider: AbstractProvider, name: string): Promise { // We have a Universal Resolver, use it const universal = await getUniversal(provider); if (universal) { - if (preferUniversal) { - return new EnsResolver(provider, universal.target, name, true); - } - - const result = await universal.findResolver(dnsEncode(name)); - return new EnsResolver(provider, result.resolver, name); + const result: RequireResolverResult = await universal.requireResolver(dnsEncode(name, 255)); + return new EnsResolver(provider, result.resolver, name, result.extended); } let currentName = name; @@ -694,16 +679,25 @@ export class EnsResolver { } } -function createUniversal(provider: AbstractProvider, address: string): Contract { +type RequireResolverResult = { + resolver: HexString; + extended: boolean; +} + +function createUniversal(provider: Provider, address: string): Contract { return new Contract(address, [ + "function requireResolver(bytes) view returns ((bytes name, uint256 offset, bytes32 node, address resolver, bool extended))", // RequireResolverResult "function findResolver(bytes) view returns (address resolver, bytes32 node, uint offset)", "function resolve(bytes name, bytes data) view returns (bytes result, address resolver)", "function reverse(bytes name, uint coinType) view returns (string primary, address resolver, address reverseResolver)", - "error HttpError(uint16 statusCode, string statusMessage)" + "error ResolverNotFound(bytes name)", + "error ResolverNotContract(bytes name, address resolver)", + "error ReverseAddressMismatch(string primary, bytes primaryAddress)", + "error HttpError(uint16 statusCode, string statusMessage)", ], provider); } -async function getUniversal(provider: AbstractProvider): Promise { +async function getUniversal(provider: Provider): Promise { const network = await provider.getNetwork(); const ensPlugin = network.getPlugin("org.ethers.plugins.network.Ens"); @@ -713,3 +707,7 @@ async function getUniversal(provider: AbstractProvider): Promise; + resolveName(ensName: string, coinType?: BigNumberish): Promise; /** * Resolves to the ENS name associated for the %%address%% or @@ -2118,7 +2118,7 @@ export interface Provider extends ContractRunner, EventEmitterable; + lookupAddress(address: string, coinType?: BigNumberish): Promise; /** * Waits until the transaction %%hash%% is mined and has %%confirms%% diff --git a/src.ts/utils/cointype.ts b/src.ts/utils/cointype.ts new file mode 100755 index 0000000000..abc8c32196 --- /dev/null +++ b/src.ts/utils/cointype.ts @@ -0,0 +1,23 @@ +import { assert } from "./errors.js"; + +const COIN_TYPE_ETH = 60n; +const COIN_TYPE_DEFAULT = 1n << 31n; + +export function coinTypeFromChain(chain: number): bigint { + if (chain === 1) return COIN_TYPE_ETH; + assert((chain & Number(COIN_TYPE_DEFAULT - 1n)) === chain, "invalid chain id", "INVALID_ARGUMENT", { + argument: 'chain', + value: chain + }); + return BigInt(chain) | COIN_TYPE_DEFAULT +} + +export function chainFromCoinType(coinType: bigint): number { + if (coinType == COIN_TYPE_ETH) return 1; + coinType ^= COIN_TYPE_DEFAULT; + return coinType >= 0 && coinType < COIN_TYPE_DEFAULT ? Number(coinType) : 0; +} + +export function isEVMCoinType(coinType: bigint) { + return coinType === COIN_TYPE_DEFAULT || chainFromCoinType(coinType) > 0 +} From 055825f303a0ea1e64d446361b5ff99136992929 Mon Sep 17 00:00:00 2001 From: Andrew Raffensperger Date: Thu, 20 Nov 2025 20:16:29 -0800 Subject: [PATCH 2/2] misc fixes --- src.ts/providers/abstract-provider.ts | 10 ++++++---- src.ts/providers/ens-resolver.ts | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src.ts/providers/abstract-provider.ts b/src.ts/providers/abstract-provider.ts index c359fa9813..653a47d5fd 100644 --- a/src.ts/providers/abstract-provider.ts +++ b/src.ts/providers/abstract-provider.ts @@ -77,7 +77,7 @@ type CcipArgs = CcipRequest & { extraData: HexString; }; -const ORDERED_CCIP_ARGS = ["sender", "urls", "calldata", "selector", "extraData"] as const; +const ORDERED_CCIP_ARGS = ["sender", "urls", "calldata", "selector", "extraData"] as const satisfies (keyof CcipArgs)[]; function toErrorArgs(args: CcipArgs) { return ORDERED_CCIP_ARGS.map(x => args[x]); @@ -597,7 +597,9 @@ export class AbstractProvider implements Provider { async #ccipReadFetch({sender, calldata: data, urls}: CcipRequest, counter = { count: 0 }): Promise { if (urls.includes(LOCAL_BATCH_GATEWAY_URL)) { - const requests: CcipRequest[] = LOCAL_BATCH_GATEWAY_ABI.decodeFunctionData('query', data); + const requests: CcipRequest[] = LOCAL_BATCH_GATEWAY_ABI.decodeFunctionData('query', data)[0].map( + (tuple: any) => ({ sender: tuple[0], urls: tuple[1], calldata: tuple[2]}) + ); this.emit("debug", { action: "sendCcipReadBatchRequest", requests }); const failures: boolean[] = [] const responses: HexString[] = [] @@ -619,7 +621,7 @@ export class AbstractProvider implements Provider { } responses[i] = LOCAL_BATCH_GATEWAY_ABI.encodeErrorResult('HttpError', [ error.info.statusCode, - error.info.errorMessages?.join('\n') ?? error.info.errorMessage + errorMessage ]); } else { responses[i] = LOCAL_BATCH_GATEWAY_ABI.encodeErrorResult('Error', [errorMessage]); @@ -629,7 +631,7 @@ export class AbstractProvider implements Provider { this.emit("debug", { action: "receiveCcipReadBatchResult", requests, failures, responses }); return LOCAL_BATCH_GATEWAY_ABI.encodeFunctionResult('query', [failures, responses]); } - assert(++counter.count > MAX_CCIP_FETCHES, `too many fetches during CCIP fetch`, 'OFFCHAIN_FAULT'); + assert(++counter.count < MAX_CCIP_FETCHES, `too many fetches during CCIP fetch`, 'OFFCHAIN_FAULT'); sender = sender.toLowerCase(); data = data.toLowerCase(); const errorMessages: Array = [ ]; diff --git a/src.ts/providers/ens-resolver.ts b/src.ts/providers/ens-resolver.ts index 7531e5b91b..f980fc479e 100644 --- a/src.ts/providers/ens-resolver.ts +++ b/src.ts/providers/ens-resolver.ts @@ -178,14 +178,17 @@ export class EnsResolver { // For EIP-2544 names, the ancestor that provided the resolver #supports2544: null | Promise; - readonly #resolver: Contract; + /** + * The resolver contract. + */ + readonly resolver: Contract; constructor(provider: Provider, address: string, name: string, wildcard?: boolean) { defineProperties(this, { provider, address, name }); this.#supports2544 = typeof wildcard === 'boolean' ? Promise.resolve(wildcard) : null; - this.#resolver = new Contract(address, [ + this.resolver = new Contract(address, [ "function supportsInterface(bytes4) view returns (bool)", "function resolve(bytes, bytes) view returns (bytes)", "function addr(bytes32) view returns (address)", @@ -204,7 +207,7 @@ export class EnsResolver { if (this.#supports2544 == null) { this.#supports2544 = (async () => { try { - return await this.#resolver.supportsInterface("0x9061b923"); + return await this.resolver.supportsInterface("0x9061b923"); } catch (error) { // Wildcard resolvers must understand supportsInterface // and return true. @@ -223,7 +226,7 @@ export class EnsResolver { async #fetch(funcName: string, params?: Array): Promise { params = (params || []).slice(); - const iface = this.#resolver.interface; + const iface = this.resolver.interface; // The first parameters is always the nodehash params.unshift(namehash(this.name)) @@ -246,7 +249,7 @@ export class EnsResolver { params.push({ enableCcipRead: true }); try { - let result = await this.#resolver[funcName](...params); + let result = await this.resolver[funcName](...params); if (fragment) { return iface.decodeFunctionResult(fragment, result)[0]; @@ -677,6 +680,10 @@ export class EnsResolver { currentName = currentName.split(".").slice(1).join("."); } } + + static async getUniversal(provider: AbstractProvider) { + return getUniversal(provider); + } } type RequireResolverResult = {