diff --git a/src/Ens.ts b/src/Ens.ts index f5a52ed6..e16ef51a 100644 --- a/src/Ens.ts +++ b/src/Ens.ts @@ -161,7 +161,9 @@ export default class Ens extends NamingService { if (!returnee) { throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { domain, - key, + recordName: key, + method: this.name, + methodName: 'record', }); } @@ -290,8 +292,9 @@ export default class Ens extends NamingService { } async locations(domains: string[]): Promise { - const result: Locations = domains.reduce(async (locations, domain) => { - locations[domain] = { + const result: Locations = {}; + for (const domain of domains) { + result[domain] = { resolverAddress: (await this.getResolverContract(domain)).address, registryAddress: this.registryContract.address, networkId: this.network, @@ -299,8 +302,7 @@ export default class Ens extends NamingService { ownerAddress: (await this.addr(domain, BlockchainType.ETH)) || '', blockchainProviderUrl: this.url, }; - return locations; - }, {}); + } return result; } @@ -335,11 +337,18 @@ export default class Ens extends NamingService { return await this.callMethod(reverseRegistrarContract, 'node', [address]); } + // @see: https://docs.ens.domains/ens-improvement-proposals/ensip-5-text-records#service-keys async twitter(domain: string): Promise { - throw new ResolutionError(ResolutionErrorCode.UnsupportedMethod, { - domain, - methodName: 'twitter', - }); + try { + return await this.record(domain, 'com.twitter'); + } catch (err) { + throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { + domain, + method: this.name, + methodName: 'twitter', + recordName: err.recordName, + }); + } } async allRecords(domain: string): Promise> { diff --git a/src/Resolution.ts b/src/Resolution.ts index 29dff674..5fd7b1c5 100644 --- a/src/Resolution.ts +++ b/src/Resolution.ts @@ -562,7 +562,16 @@ export default class Resolution { * @returns A promise that resolves in chatId */ async chatId(domain: string): Promise { - return this.record(domain, 'gundb.username.value'); + try { + return await this.record(domain, 'gundb.username.value'); + } catch (err) { + throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { + domain, + method: err.method, + methodName: 'chatId', + recordName: err.recordName, + }); + } } /** @@ -572,7 +581,16 @@ export default class Resolution { * @returns a promise that resolves in gundb public key */ async chatPk(domain: string): Promise { - return this.record(domain, 'gundb.public_key.value'); + try { + return await this.record(domain, 'gundb.public_key.value'); + } catch (err) { + throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { + domain, + method: err.method, + methodName: 'chatPk', + recordName: err.recordName, + }); + } } /** @@ -919,14 +937,22 @@ export default class Resolution { */ async locations(domains: string[]): Promise { const zilDomains = domains.filter((domain) => domain.endsWith('.zil')); - + const ensDomains = domains.filter((domain) => + domain.match(/^([^\s\\.]+\.)+(eth|luxe|xyz|kred)+$/), + ); + const unsDomains = domains.filter( + (domain) => + !domain.endsWith('.zil') && + !domain.match(/^([^\s\\.]+\.)+(eth|luxe|xyz|kred)+$/), + ); // Here, we call both UNS and ZNS methods and merge the results. // If any of the calls fails, this method will fail as well as we aren't interested in partial results. // For example, if one of the providers is configured as `UdApi`, it'll fail as the method is unsupported. // But if there are no .zil domains with absent UNS locations (i.e. all the requested .zil domains have been // migrated to UNS), the ZNS call result will be ignored and an error, if there's one, won't be thrown. - const unsPromise = this.serviceMap.UNS.usedServices[0].locations(domains); + const unsPromise = + this.serviceMap.UNS.usedServices[0].locations(unsDomains); // Fetch UNS locations first. If we see that there are no .zil domains with absent locations, we can return early. const unsLocations = await unsPromise; if (zilDomains.length) { @@ -946,7 +972,6 @@ export default class Resolution { } } - const ensDomains = domains.filter((domain) => domain.endsWith('.eth')); if (ensDomains.length) { const ensLocations = await this.serviceMap.ENS.usedServices[0].locations( ensDomains, diff --git a/src/errors/resolutionError.ts b/src/errors/resolutionError.ts index 173ae443..8b1998a7 100644 --- a/src/errors/resolutionError.ts +++ b/src/errors/resolutionError.ts @@ -22,7 +22,6 @@ type ResolutionErrorOptions = { location?: UnsLocation; tokenUri?: string; config?: ResolutionConfig; - key?: string; }; export enum ResolutionErrorCode { @@ -125,11 +124,13 @@ export class ResolutionError extends Error { readonly code: ResolutionErrorCode; readonly domain?: string; readonly method?: string; + readonly methodName?: string; + readonly recordName?: string; readonly currencyTicker?: string; constructor(code: ResolutionErrorCode, options: ResolutionErrorOptions = {}) { const resolutionErrorHandler: ResolutionErrorHandler = HandlersByCode[code]; - const {domain, method, currencyTicker} = options; + const {domain, method, currencyTicker, methodName, recordName} = options; const message = resolutionErrorHandler(options); super(message); @@ -138,6 +139,8 @@ export class ResolutionError extends Error { this.method = method; this.currencyTicker = currencyTicker; this.name = 'ResolutionError'; + this.methodName = methodName; + this.recordName = recordName; Object.setPrototypeOf(this, ResolutionError.prototype); } }