From f19421a18c8876258f4a3ae5e3e27b127763206c Mon Sep 17 00:00:00 2001 From: DChan0319 Date: Tue, 3 Oct 2023 08:54:49 -0700 Subject: [PATCH] Dchan/dt 902 add ens resolution js (#254) * [DT-912] Revert: "Removes ENS support" This reverts commit 0af264b1c3927c266444f185759f6ce8161a22d4. * [DT-902] test: fixing tests to use alchemy's goerli as provider for ENS * [DT-902] docs: updating changelog to include minor version for ENS support * [DT-902] refactor: ENS constructor and fix ENS.test.ts * [DT-902] test: Fixed Resolution.test.ts unit tests * [DT-902] refactor: Added UdApi for ensConfig * [DT-902] refactor: added ens.getAddress() * [DT-902] fix: CI node version * [DT-902] fix: test setup and timeout * [DT-902] feature: Adding support for ENS tokenURI() * [DT-902] feature: adding ENS support for unhash() * [DT-902] feature: adding ENS support for locations() * [DT-902] chore: removing unnecessary README.md info * [DT-902] fix: fixing unit tests * [DT-902] fix: Ens.fetchAddress() to support ud Api as the provider with apiKey * [DT-902] Updating @unstoppabledomains/sizecheck dev dependency to latest version * [DT-902] fix: Fixed @unstoppabledomains/sizecheck version to 4.0.4 * [DT-902] Fix: owner() & reverse() * [DT-902] Fix: Resolution.addr() * [DT-902] feature: adding ENS support for Resolution.email(), Resolution.httpUrl(), Resolution.ipfsHash() * [DT-902] fix: Added final validation for Ens.reverseOf() * [DT-902] fix: locations() & added better error handling * [DT-902] refactor: reverseTokenId() to support ENS * [DT-902] chore: added Ens.dns() todo * [DT-902] fix: Unit testing and small updates * [DT-902] chore: resolve PR comments & revise unit tests * [DT-902] chore: Making sure to throw unsupported method for Ens multiChainAddr() * [DT-902] chore: updating README.md to include ENS examples * v9.2.0 --- .env.example | 2 +- .github/workflows/main.yaml | 4 +- CHANGELOG.md | 4 + README.md | 23 +- jest.config.js | 2 +- package.json | 15 +- src/Ens.ts | 647 +++++++ src/NamingService.ts | 1 + src/Resolution.ts | 340 +++- src/UdApi.ts | 1 + src/config/ens-config.json | 167 ++ src/contracts/ens/baseRegistrar.ts | 753 ++++++++ src/contracts/ens/ens.ts | 108 ++ src/contracts/ens/nameWrapper.ts | 1456 ++++++++++++++ src/contracts/ens/newResolver.ts | 368 ++++ src/contracts/ens/oldResolver.ts | 389 ++++ src/contracts/ens/resolver.ts | 33 + src/contracts/ens/reverseRegistrar.ts | 336 ++++ src/errors/configurationError.ts | 6 + src/errors/resolutionError.ts | 6 +- src/tests/Ens.test.ts | 415 ++++ src/tests/Resolution.test.ts | 2535 +++++++++++++------------ src/tests/UdApi.test.ts | 4 +- src/tests/Uns.test.ts | 30 +- src/tests/UnsInternal.test.ts | 42 +- src/tests/Zns.test.ts | 74 +- src/tests/helpers.ts | 29 +- src/tests/namehash.test.ts | 7 + src/tests/requireOrFail.test.ts | 20 + src/tests/testData/mockData.json | 38 +- src/types/index.ts | 7 + src/types/publicTypes.ts | 9 + src/utils/Eip1993Factories.ts | 2 +- src/utils/index.ts | 9 + src/utils/namehash.ts | 29 + src/utils/requireOrFail.ts | 26 + 36 files changed, 6594 insertions(+), 1343 deletions(-) create mode 100644 src/Ens.ts create mode 100644 src/config/ens-config.json create mode 100644 src/contracts/ens/baseRegistrar.ts create mode 100644 src/contracts/ens/ens.ts create mode 100644 src/contracts/ens/nameWrapper.ts create mode 100644 src/contracts/ens/newResolver.ts create mode 100644 src/contracts/ens/oldResolver.ts create mode 100644 src/contracts/ens/resolver.ts create mode 100644 src/contracts/ens/reverseRegistrar.ts create mode 100644 src/tests/Ens.test.ts create mode 100644 src/tests/requireOrFail.test.ts create mode 100644 src/utils/requireOrFail.ts diff --git a/.env.example b/.env.example index d99e40f6..52800290 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ L1_TEST_NET_RPC_URL=https://goerli.infura.io/v3/ L1_TEST_NET_RPC_WSS_URL=wss://goerli.infura.io/ws/v3/ L2_TEST_NET_RPC_URL=https://polygon-mumbai.infura.io/v3/ -L2_TEST_NET_RPC_WSS_URL=wss://polygon-mumbai.g.alchemy.com/v2/ \ No newline at end of file +L2_TEST_NET_RPC_WSS_URL=wss://polygon-mumbai.g.alchemy.com/v2/ diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index acb286d5..44ca722f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [12, 14, 16] + node: [16] name: ${{ matrix.os }} - Node ${{ matrix.node }} @@ -55,7 +55,7 @@ jobs: - name: Setup node uses: actions/setup-node@v1 with: - node-version: 12 + node-version: ${{ matrix.node }} - name: Install Dependencies run: yarn install diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d02218c..b964467c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 9.2.0 + +- Add ENS support + ## 9.1.0 - Add new getAddress API diff --git a/README.md b/README.md index 55b41485..47843bb9 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,10 @@ const resolution = new Resolution({ url: 'https://api.zilliqa.com', network: 'mainnet', }, + ens: { + url: 'https://mainnet.infura.io/v3/', + network: 'mainnet', + }, }, }); ``` @@ -163,6 +167,7 @@ function resolve(domain, currency) { resolve('brad.crypto', 'ETH'); resolve('brad.zil', 'ZIL'); +resolve('vitalik.eth', 'ETH'); ``` ### Find the IPFS hash for a decentralized website @@ -182,6 +187,7 @@ function resolveIpfsHash(domain) { } resolveIpfsHash('homecakes.crypto'); +resolveIpfsHash('vitalik.eth'); ``` ### Find a custom record @@ -189,6 +195,8 @@ resolveIpfsHash('homecakes.crypto'); Create a new file in your project, `custom-resolution.js`. ```javascript +// Does not support ENS + function resolveCustomRecord(domain, record) { resolution .records(domain, [record]) @@ -218,6 +226,8 @@ function getWalletAddr(domain, ticker) { } getWalletAddr('homecakes.crypto', 'ETH'); // Domain homecakes.crypto has address for ETH: 0xe7474D07fD2FA286e7e0aa23cd107F8379085037 +getWalletAddr('vitalik.eth', 'ETH'); +// Domain homecakes.crypto has address for ETH: 0xe7474D07fD2FA286e7e0aa23cd107F8379085037 ``` ### Resolve multi-chain address format using `multiChainAddr` @@ -229,6 +239,8 @@ This API is used to retrieve wallet address for multi-chain address records. With `aaron.x` has `crypto.AAVE.version.ERC20.address` on-chain: ```javascript +// Does not support ENS + function getMultiChainWalletAddr(domain, ticker, network) { resolution .multiChainAddr(domain, ticker, network) @@ -335,6 +347,7 @@ token.EVM.ETH.address ``` `getAddress(domain, 'ETH', 'USDC')` will lookup records in the following order: +// Not supported with ENS ``` 1. token.EVM.ETH.USDC.address @@ -344,15 +357,6 @@ token.EVM.ETH.address 5. token.EVM.address ``` -> **Warning** please use the API with caution as it's still in beta. Please -> submit an issue if you find a bug. - -### Command Line Interface - -CLI support was removed from the Resolution library starting from version 6.0. -Please use the -[standalone CLI tool](https://github.com/unstoppabledomains/resolution-cli). - ## Error Handling When resolution encounters an error it returns the error code instead of @@ -389,7 +393,6 @@ or **Linux shell**). ### Internal config #### To update: - - Network config: `$ yarn network-config:pull` - Resolver keys: `$ yarn resolver-keys:pull` - Both configs: `$ yarn config:pull` diff --git a/jest.config.js b/jest.config.js index 634376e2..5f885c0a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,7 +8,7 @@ module.exports = { diagnostics: false, }, }, - testTimeout: 40000, + testTimeout: 50000, coveragePathIgnorePatterns: ['/node_modules/', '/src/tests/'], setupFilesAfterEnv: ['/src/tests/jestSetup.ts'], }; diff --git a/package.json b/package.json index f7cdad90..f6f5fdb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unstoppabledomains/resolution", - "version": "9.1.0", + "version": "9.2.0", "description": "Domain Resolution for blockchain domains", "main": "./build/index.js", "directories": { @@ -45,6 +45,7 @@ "keywords": [ ".crypto", "zns", + "ens", "ethereum", "zilliqa", "blockchain", @@ -59,6 +60,7 @@ }, "homepage": "https://github.com/unstoppabledomains/resolution.git#readme", "devDependencies": { + "@ensdomains/address-encoder": "0.2.18", "@ethersproject/providers": "^5.4.5", "@types/bn.js": "^4.11.6", "@types/crypto-js": "^4.1.1", @@ -68,9 +70,11 @@ "@types/node": "11.15.3", "@typescript-eslint/eslint-plugin": "^3.9.0", "@typescript-eslint/parser": "^3.9.0", - "@unstoppabledomains/sizecheck": "^4.0.0", + "@unstoppabledomains/sizecheck": "^4.0.4", "@zilliqa-js/core": "^3.3.4", "audit-ci": "^3.1.1", + "bip44-constants": "^8.0.103", + "content-hash": "^2.5.2", "dotenv": "^8.2.0", "eslint": "^7.7.0", "eslint-config-prettier": "^8.3.0", @@ -97,9 +101,12 @@ "dependencies": { "@ethersproject/abi": "^5.0.1", "bn.js": "^4.4.0", - "cross-fetch": "^3.1.4", + "cross-fetch": "4.0.0", "crypto-js": "^4.1.1", - "elliptic": "^6.5.4" + "elliptic": "^6.5.4", + "ethereum-ens-network-map": "^1.0.2", + "js-sha256": "^0.9.0", + "js-sha3": "^0.8.0" }, "lint-staged": { "src/**/*.ts": "eslint --fix", diff --git a/src/Ens.ts b/src/Ens.ts new file mode 100644 index 00000000..4262e589 --- /dev/null +++ b/src/Ens.ts @@ -0,0 +1,647 @@ +import {default as ensInterface} from './contracts/ens/ens'; +import {default as resolverInterface} from './contracts/ens/resolver'; +import {default as nameWrapperInterface} from './contracts/ens/nameWrapper'; +import {default as baseRegistrarInterface} from './contracts/ens/baseRegistrar'; +import {default as reverseRegistrarInterface} from './contracts/ens/reverseRegistrar'; +import {EnsSupportedNetwork, EthCoinIndex, hasProvider} from './types'; +import {ResolutionError, ResolutionErrorCode} from './errors/resolutionError'; +import EthereumContract from './contracts/EthereumContract'; +import EnsNetworkMap from 'ethereum-ens-network-map'; +import { + EnsSource, + Locations, + NamingServiceName, + Provider, + TokenUriMetadata, + BlockchainType, + DnsRecordType, + DnsRecord, +} from './types/publicTypes'; +import {EthereumNetworksInverted, isNullAddress} from './utils'; +import FetchProvider from './FetchProvider'; +import {eip137Childhash, eip137Namehash, labelNameHash} from './utils/namehash'; +import {NamingService} from './NamingService'; +import ConfigurationError, { + ConfigurationErrorCode, +} from './errors/configurationError'; +import {EthereumNetworks} from './utils'; +import {requireOrFail} from './utils/requireOrFail'; +import ensConfig from '../src/config/ens-config.json'; +import Networking from './utils/Networking'; + +/** + * @internal + */ +export default class Ens extends NamingService { + readonly name = NamingServiceName.ENS; + readonly network: number; + readonly networkName: string; + readonly url: string; + readonly provider: Provider; + readonly registryContract: EthereumContract; + readonly nameWrapperContract: EthereumContract; + readonly baseRegistrarContract: EthereumContract; + readonly proxyServiceApiKey: string | undefined; + + constructor(source?: EnsSource) { + super(); + let finalSource: EnsSource = {url: '', network: 'mainnet'}; + if (source) { + finalSource = this.checkNetworkConfig(source); + } + this.network = EthereumNetworks[finalSource.network]; + this.networkName = finalSource.network; + this.url = finalSource['url']; + this.provider = + finalSource['provider'] || + FetchProvider.factory(NamingServiceName.ENS, this.url); + this.proxyServiceApiKey = finalSource['proxyServiceApiKey']; + + const registryAddress = + finalSource['registryAddress'] || EnsNetworkMap[this.network]; + this.registryContract = new EthereumContract( + ensInterface, + registryAddress, + this.provider, + this.proxyServiceApiKey, + ); + + const nameWrapperAddress = this.determineNameWrapperAddress(this.network); + this.nameWrapperContract = new EthereumContract( + nameWrapperInterface, + nameWrapperAddress, + this.provider, + this.proxyServiceApiKey, + ); + + const baseRegistrarAddress = this.determineBaseRegistrarAddress( + this.network, + ); + this.baseRegistrarContract = new EthereumContract( + baseRegistrarInterface, + baseRegistrarAddress, + this.provider, + this.proxyServiceApiKey, + ); + } + + static async autoNetwork( + config: {url: string} | {provider: Provider}, + ): Promise { + let provider: Provider; + + if (hasProvider(config)) { + provider = config.provider; + } else { + if (!config.url) { + throw new ConfigurationError(ConfigurationErrorCode.UnspecifiedUrl, { + method: NamingServiceName.ENS, + }); + } + provider = FetchProvider.factory(NamingServiceName.ENS, config.url); + } + + const networkId = (await provider.request({ + method: 'net_version', + })) as number; + const networkName = EthereumNetworksInverted[networkId]; + if (!networkName || !EnsSupportedNetwork.guard(networkName)) { + throw new ConfigurationError(ConfigurationErrorCode.UnsupportedNetwork, { + method: NamingServiceName.ENS, + }); + } + return new this({network: networkName, provider: provider}); + } + + serviceName(): NamingServiceName { + return this.name; + } + + namehash(domain: string): string { + if (!this.checkSupportedDomain(domain)) { + throw new ResolutionError(ResolutionErrorCode.UnsupportedDomain, { + domain, + }); + } + return eip137Namehash(domain); + } + + childhash(parentHash: string, label: string): string { + return eip137Childhash(parentHash, label); + } + + async isSupportedDomain(domain: string): Promise { + return this.checkSupportedDomain(domain); + } + + async owner(domain: string): Promise { + const namehash = this.namehash(domain); + const isWrapped = await this.determineIsWrappedDomain(namehash); + if (isWrapped) { + return await this.callMethod(this.nameWrapperContract, 'ownerOf', [ + namehash, + ]); + } + return await this.callMethod(this.registryContract, 'owner', [namehash]); + } + + async resolver(domain: string): Promise { + const nodeHash = this.namehash(domain); + const resolverAddr = await this.callMethod( + this.registryContract, + 'resolver', + [nodeHash], + ); + if (isNullAddress(resolverAddr)) { + throw new ResolutionError(ResolutionErrorCode.UnspecifiedResolver); + } + return resolverAddr; + } + + async record(domain: string, key: string): Promise { + const returnee = await this.getTextRecord(domain, key); + if (!returnee) { + throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { + domain, + recordName: key, + method: this.name, + methodName: 'record', + }); + } + + return returnee; + } + + async records( + domain: string, + keys: string[], + ): Promise> { + throw new ResolutionError(ResolutionErrorCode.UnsupportedMethod, { + methodName: 'records', + domain, + method: this.serviceName(), + }); + } + + // TODO: Need to understand how ENS supports DNS @see https://docs.ens.domains/contract-api-reference/dns-registrar#retrieving-the-dns-text-record + async dns(domain: string, types: DnsRecordType[]): Promise { + throw new ResolutionError(ResolutionErrorCode.UnsupportedMethod, { + methodName: 'dns', + method: this.serviceName(), + domain, + }); + } + + async reverse( + address: string, + currencyTicker: string, + ): Promise { + throw new ResolutionError(ResolutionErrorCode.UnsupportedMethod, { + methodName: 'reverse', + }); + } + + // TODO: Figure out why nameHash() does not work for reverse address. + // Current implementation uses reverseRegistrarContract to fetch the correct node hash. + // @see: https://eips.ethereum.org/EIPS/eip-181 + async reverseOf(address: string): Promise { + const originalAddress = address; + if (address.startsWith('0x')) { + address = address.substr(2); + } + + const reverseRegistrarAddress = this.determineReverseRegistrarAddress( + this.network, + ); + const reverseRegistrarContract = new EthereumContract( + reverseRegistrarInterface, + reverseRegistrarAddress, + this.provider, + this.proxyServiceApiKey, + ); + + const nodeHash = await this.reverseRegistrarCallToNode( + reverseRegistrarContract, + address, + ); + const resolverAddress = await this.callMethod( + this.registryContract, + 'resolver', + [nodeHash], + ); + + if (isNullAddress(resolverAddress)) { + return null; + } + + const resolverContract = new EthereumContract( + resolverInterface(resolverAddress, EthCoinIndex), + resolverAddress, + this.provider, + this.proxyServiceApiKey, + ); + + const domainName = await this.resolverCallToName( + resolverContract, + nodeHash, + ); + + const fetchedAddress = await this.resolverCallToAddr( + resolverContract, + domainName, + ); + if (fetchedAddress?.toLowerCase() !== originalAddress.toLowerCase()) { + return null; + } + + return domainName; + } + + async getTokenUri(domain: string): Promise { + const tokenId = this.namehash(domain); + const isWrappedDomain = await this.determineIsWrappedDomain(tokenId); + if (isWrappedDomain) { + return `https://metadata.ens.domains/${this.networkName}/${this.nameWrapperContract.address}/${tokenId}`; + } + + const hashedLabel = labelNameHash(domain); + return `https://metadata.ens.domains/${this.networkName}/${this.baseRegistrarContract.address}/${hashedLabel}`; + } + + async isAvailable(domain: string): Promise { + return !(await this.isRegistered(domain)); + } + + async registryAddress(domain: string): Promise { + return this.registryContract.address; + } + + async isRegistered(domain: string): Promise { + const address = await this.owner(domain); + return !isNullAddress(address); + } + + // Tries to fetch domain metadata from both NameWrapper & BaseRegistrar contract + // due to ENS having wrapped domains and not knowing which hash the user will input. + async getDomainFromTokenId(hash: string): Promise { + let domainName = ''; + const nameWrapperMetadataResponse = await Networking.fetch( + `https://metadata.ens.domains/${this.networkName}/${this.nameWrapperContract.address}/${hash}`, + {}, + ); + if (nameWrapperMetadataResponse.status === 200) { + const jsonResponse = await nameWrapperMetadataResponse.json(); + domainName = jsonResponse.name; + return domainName; + } + + const baseRegistrarMetadataResponse = await Networking.fetch( + `https://metadata.ens.domains/${this.networkName}/${this.baseRegistrarContract.address}/${hash}`, + {}, + ); + + if (baseRegistrarMetadataResponse.status === 200) { + const jsonResponse = await baseRegistrarMetadataResponse.json(); + domainName = jsonResponse.name; + } + + return domainName; + } + + async locations(domains: string[]): Promise { + const result: Locations = {}; + for (const domain of domains) { + result[domain] = { + resolverAddress: (await this.getResolverContract(domain)).address, + registryAddress: this.registryContract.address, + networkId: this.network, + blockchain: BlockchainType.ETH, + ownerAddress: (await this.addr(domain, BlockchainType.ETH)) || '', + blockchainProviderUrl: this.url, + }; + } + + return result; + } + + async getAddress( + domain: string, + network: string, + token: string, + ): Promise { + throw new ResolutionError(ResolutionErrorCode.UnsupportedMethod, { + methodName: 'getAddress', + domain, + method: this.serviceName(), + }); + } + + /** + * This was done to make automated tests more configurable + */ + private resolverCallToName( + resolverContract: EthereumContract, + nodeHash: string, + ) { + return this.callMethod(resolverContract, 'name', [nodeHash]); + } + + /** + * This was done to make automated tests more configurable + */ + private async reverseRegistrarCallToNode( + reverseRegistrarContract: EthereumContract, + address: string, + ): Promise { + return await this.callMethod(reverseRegistrarContract, 'node', [address]); + } + + /** + * This was done to make automated tests more configurable + */ + private async resolverCallToAddr( + resolverContract: EthereumContract, + domainName: string, + ): Promise { + return await this.callMethod(resolverContract, 'addr', [ + this.namehash(domainName), + ]); + } + + // @see: https://docs.ens.domains/ens-improvement-proposals/ensip-5-text-records#service-keys + async twitter(domain: string): Promise { + 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> { + throw new ResolutionError(ResolutionErrorCode.UnsupportedMethod, { + domain, + methodName: 'allRecords', + }); + } + + protected getCoinType(currencyTicker: string): string { + const bip44constants = requireOrFail( + 'bip44-constants', + 'bip44-constants', + '^8.0.5', + ); + const formatsByCoinType = requireOrFail( + '@ensdomains/address-encoder', + '@ensdomains/address-encoder', + '>= 0.1.x <= 0.2.x', + ).formatsByCoinType; + const coin = bip44constants.findIndex( + (item) => + item[1] === currencyTicker.toUpperCase() || + item[2] === currencyTicker.toUpperCase(), + ); + if (coin < 0 || !formatsByCoinType[coin]) { + throw new ResolutionError(ResolutionErrorCode.UnsupportedCurrency, { + currencyTicker, + }); + } + + return coin.toString(); + } + + async addr( + domain: string, + currencyTicker: string, + ): Promise { + const resolver = await this.resolver(domain).catch( + (err: ResolutionError) => { + if (err.code !== ResolutionErrorCode.UnspecifiedResolver) { + throw err; + } + }, + ); + + if (!resolver) { + const owner = await this.owner(domain); + if (isNullAddress(owner)) { + throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain, { + domain, + }); + } + throw new ResolutionError(ResolutionErrorCode.UnspecifiedResolver, { + domain, + }); + } + + const coinType = this.getCoinType(currencyTicker.toUpperCase()); + return await this.fetchAddress(resolver, domain, coinType); + } + + private async fetchAddress( + resolver: string, + domain: string, + coinType: string, + ): Promise { + const formatsByCoinType = requireOrFail( + '@ensdomains/address-encoder', + '@ensdomains/address-encoder', + '>= 0.1.x <= 0.2.x', + ).formatsByCoinType; + + const resolverContract = new EthereumContract( + resolverInterface(resolver, coinType), + resolver, + this.provider, + this.proxyServiceApiKey, + ); + + const nodeHash = this.namehash(domain); + const addr: string = + coinType !== EthCoinIndex + ? await this.callMethod(resolverContract, 'addr', [nodeHash, coinType]) + : await this.callMethod(resolverContract, 'addr', [nodeHash]); + if (isNullAddress(addr)) { + return undefined; + } + + // eslint-disable-next-line no-undef + const data = Buffer.from(addr.replace('0x', ''), 'hex'); + return formatsByCoinType[coinType].encoder(data); + } + + // @see https://docs.ens.domains/ens-improvement-proposals/ensip-5-text-records + private async getTextRecord( + domain: string, + key: string, + ): Promise { + if (key === 'contenthash') { + return await this.getContentHash(domain); + } + const nodeHash = this.namehash(domain); + const resolver = await this.getResolverContract(domain); + const textRecord = await this.callMethod(resolver, 'text', [nodeHash, key]); + return textRecord; + } + + // @see https://docs.ens.domains/ens-improvement-proposals/ensip-7-contenthash-field + private async getContentHash(domain: string): Promise { + const contentHash = requireOrFail('content-hash', 'content-hash', '^2.5.2'); + const nodeHash = this.namehash(domain); + const resolverContract = await this.getResolverContract(domain); + const contentHashEncoded = await this.callMethod( + resolverContract, + 'contenthash', + [nodeHash], + ); + const codec = contentHash.getCodec(contentHashEncoded); + if (codec !== 'ipfs-ns') { + return undefined; + } + return contentHash.decode(contentHashEncoded); + } + + private async getResolverContract( + domain: string, + coinType?: string, + ): Promise { + const resolverAddress = await this.resolver(domain); + return new EthereumContract( + resolverInterface(resolverAddress, coinType), + resolverAddress, + this.provider, + this.proxyServiceApiKey, + ); + } + + private async callMethod( + contract: EthereumContract, + method: string, + params: (string | string[])[], + ): Promise { + const result = await contract.call(method, params); + return result[0]; + } + + // Checks if domain is an ENS domain with tlds, or a reverse address. + private checkSupportedDomain(domain: string): boolean { + return ( + domain === 'eth' || + /^([^\s\\.]+\.)+(eth|luxe|xyz|kred)+$/.test(domain) || + /^([^\s\\.]+\.)(addr\.)(reverse)$/.test(domain) + ); + } + + private checkNetworkConfig(source: EnsSource | undefined): EnsSource { + if (!source?.network) { + throw new ConfigurationError(ConfigurationErrorCode.UnsupportedNetwork, { + method: this.name, + }); + } + if (!EnsSupportedNetwork.guard(source.network)) { + this.checkCustomNetworkConfig(source); + } + + return source; + } + + private checkCustomNetworkConfig(source: EnsSource): void { + if (!this.isValidRegistryAddress(source.registryAddress)) { + throw new ConfigurationError( + ConfigurationErrorCode.InvalidConfigurationField, + { + method: this.name, + field: 'registryAddress', + }, + ); + } + if (!source['url'] && !source['provider']) { + throw new ConfigurationError( + ConfigurationErrorCode.CustomNetworkConfigMissing, + { + method: this.name, + config: 'url or provider', + }, + ); + } + } + + private isValidRegistryAddress(address?: string): boolean { + if (!address) { + throw new ConfigurationError( + ConfigurationErrorCode.CustomNetworkConfigMissing, + { + method: this.name, + config: 'registryAddress', + }, + ); + } + const ethLikePattern = new RegExp('^0x[a-fA-F0-9]{40}$'); + return ethLikePattern.test(address); + } + + private async getAddressForWrappedDomain( + domain: string, + ): Promise { + const addr1 = await this.getOwnerOfFromNameHashContract(domain); + const addr2 = await this.owner(domain); + + if (addr1) { + return addr1; + } + + if (addr2) { + return addr2; + } + + return; + } + + private determineNameWrapperAddress(network: number): string { + return ensConfig.networks[network].contracts.NameWrapper.address; + } + + private determineBaseRegistrarAddress(network: number): string { + return ensConfig.networks[network].contracts.BaseRegistrarImplementation + .address; + } + + private determineReverseRegistrarAddress(network: number): string { + return ensConfig.networks[network].contracts.ReverseRegistrar.address; + } + + private async determineIsWrappedDomain( + hashedDomain: string, + ): Promise { + return await this.callMethod(this.nameWrapperContract, 'isWrapped', [ + hashedDomain, + ]); + } + + private async getOwnerOfFromNameHashContract( + domain: string, + ): Promise { + return await this.callMethod(this.nameWrapperContract, 'ownerOf', [ + this.namehash(domain), + ]); + } + + private async getMetadataFromTokenURI( + tokenUri: string, + ): Promise { + const resp = await Networking.fetch(tokenUri, {}); + if (resp.ok) { + return resp.json(); + } + + throw new ResolutionError(ResolutionErrorCode.ServiceProviderError, { + providerMessage: await resp.text(), + method: 'UDAPI', + methodName: 'tokenURIMetadata', + }); + } +} diff --git a/src/NamingService.ts b/src/NamingService.ts index 84c74a09..3f25bada 100644 --- a/src/NamingService.ts +++ b/src/NamingService.ts @@ -1,6 +1,7 @@ import {Locations, UnsLocation} from './types/publicTypes'; export abstract class NamingService { + abstract name?: string; abstract owner(domain: string): Promise; abstract resolver(domain: string): Promise; abstract namehash(domain: string): string; diff --git a/src/Resolution.ts b/src/Resolution.ts index 51013ef3..76c7ad25 100644 --- a/src/Resolution.ts +++ b/src/Resolution.ts @@ -1,4 +1,5 @@ import BN from 'bn.js'; +import Ens from './Ens'; import Zns from './Zns'; import Uns from './Uns'; import UdApi from './UdApi'; @@ -26,6 +27,7 @@ import ResolutionError, {ResolutionErrorCode} from './errors/resolutionError'; import DnsUtils from './utils/DnsUtils'; import { findNamingServiceName, + signedInfuraLink, signedLink, UnwrapPromise, wrapResult, @@ -33,6 +35,8 @@ import { } from './utils'; import {Eip1993Factories as Eip1193Factories} from './utils/Eip1993Factories'; import {NamingService} from './NamingService'; +import ConfigurationError from './errors/configurationError'; +import {ConfigurationErrorCode} from './errors/configurationError'; import Networking from './utils/Networking'; import {prepareAndValidateDomain} from './utils/prepareAndValidate'; import {fromDecStringToHex} from './utils/namehash'; @@ -49,7 +53,7 @@ const DEFAULT_UNS_PROXY_SERVICE_URL = * * let resolution = new Resolution({ blockchain: { * uns: { - * url: "https://mainnet.infura.io/v3/c4bb906ed6904c42b19c95825fe55f39", + * url: "https://mainnet.infura.io/v3/", * network: "mainnet" * } * } @@ -68,6 +72,7 @@ export default class Resolution { constructor(config: {sourceConfig?: SourceConfig; apiKey?: string} = {}) { const uns = this.getUnsConfig(config); const zns = this.getZnsConfig(config); + const ens = this.getEnsConfig(config); // If both UNS and ZNS use the same UdApi providers, we don't want to call the API twice as it would return same // responses. It should be enough to compare just the URLs, as the network param isn't actually used in the calls. @@ -85,19 +90,26 @@ export default class Resolution { usedServices: equalUdApiProviders ? [uns] : [uns, zns], native: zns instanceof Zns ? zns : new Zns(), }, + [NamingServiceName.ENS]: { + usedServices: [ens], + native: ens instanceof Ens ? ens : new Ens(), + }, }; } /** - * AutoConfigure the blockchain network for UNS + * AutoConfigure the blockchain network between different testnets for ENS and UNS * We make a "net_version" JSON RPC call to the blockchain either via url or with the help of given provider. - * @param sourceConfig - configuration object for uns + * @param sourceConfig - configuration object for ens and uns * @returns configured Resolution object */ static async autoNetwork( sourceConfig: AutoNetworkConfigs, ): Promise { const resolution = new this(); + if (!sourceConfig.uns && !sourceConfig.ens) { + throw new ConfigurationError(ConfigurationErrorCode.UnsupportedNetwork); + } if (sourceConfig.uns) { const uns = await Uns.autoNetwork(sourceConfig.uns); @@ -107,17 +119,28 @@ export default class Resolution { }; } + if (sourceConfig.ens) { + const ens = await Ens.autoNetwork(sourceConfig.ens); + resolution.serviceMap[NamingServiceName.ENS] = { + usedServices: [ens], + native: ens, + }; + } + return resolution; } /** - * Creates a resolution with configured infura id for uns + * Creates a resolution with configured infura id for ens and uns * @param infura - infura project id - * @param networks - an optional object that describes what network to use when connecting UNS default is mainnet + * @param networks - an optional object that describes what network to use when connecting ENS or UNS default is mainnet */ static infura( infura: string, networks?: { + ens?: { + network: string; + }; uns?: { locations: { Layer1: { @@ -132,6 +155,10 @@ export default class Resolution { ): Resolution { return new this({ sourceConfig: { + ens: { + url: signedInfuraLink(infura, networks?.ens?.network), + network: networks?.ens?.network || 'mainnet', + }, uns: { locations: { Layer1: { @@ -204,10 +231,14 @@ export default class Resolution { /** * Creates a resolution instance with configured provider - * @param networks - an object that describes what network to use when connecting UNS or ZNS default is mainnet + * @param networks - an object that describes what network to use when connecting UNS, ENS, or ZNS default is mainnet * @see https://eips.ethereum.org/EIPS/eip-1193 */ static fromResolutionProvider(networks: { + ens?: { + provider: Provider; + network: string; + }; uns?: { locations: { Layer1: {provider: Provider; network: string}; @@ -219,8 +250,9 @@ export default class Resolution { network: string; }; }): Resolution { - if (networks.uns) { + if (networks.ens || networks.uns) { return this.fromEthereumEip1193Provider({ + ens: networks.ens, uns: networks.uns, }); } @@ -228,16 +260,20 @@ export default class Resolution { return this.fromZilliqaProvider(networks.zns.provider, networks); } throw new ResolutionError(ResolutionErrorCode.ServiceProviderError, { - providerMessage: 'Must specify network for uns or zns', + providerMessage: 'Must specify network for uns, ens, or zns', }); } /** * Creates a resolution instance with configured provider - * @param networks - an object that describes what network to use when connecting UNS default is mainnet + * @param networks - an object that describes what network to use when connecting UNS and ENS default is mainnet * @see https://eips.ethereum.org/EIPS/eip-1193 */ static fromEthereumEip1193Provider(networks: { + ens?: { + network?: string; + provider: Provider; + }; uns?: { locations: { Layer1: { @@ -252,6 +288,12 @@ export default class Resolution { }; }): Resolution { const sourceConfig: SourceConfig = {}; + if (networks.ens) { + sourceConfig.ens = { + provider: networks.ens.provider, + network: networks?.ens?.network || 'mainnet', + }; + } if (networks.uns) { sourceConfig.uns = { locations: { @@ -298,6 +340,10 @@ export default class Resolution { * @see https://github.com/ethereum/web3.js/blob/0.20.7/lib/web3/httpprovider.js#L116 */ static fromWeb3Version0Provider(networks: { + ens?: { + provider: Web3Version0Provider; + network: string; + }; uns?: { locations: { Layer1: { @@ -312,6 +358,14 @@ export default class Resolution { }; }): Resolution { return this.fromEthereumEip1193Provider({ + ens: networks.ens + ? { + network: networks.ens.network, + provider: Eip1193Factories.fromWeb3Version0Provider( + networks.ens.provider, + ), + } + : undefined, uns: networks.uns ? { locations: { @@ -335,11 +389,15 @@ export default class Resolution { /** * Create a resolution instance from web3 1.x version provider - * @param networks - an optional object with 1.x version provider from web3 ( must implement send(payload, callback) ) that describes what network to use when connecting UNS default is mainnet + * @param networks - an optional object with 1.x version provider from web3 ( must implement send(payload, callback) ) that describes what network to use when connecting ENS or UNS default is mainnet * @see https://github.com/ethereum/web3.js/blob/1.x/packages/web3-core-helpers/types/index.d.ts#L165 * @see https://github.com/ethereum/web3.js/blob/1.x/packages/web3-providers-http/src/index.js#L95 */ static fromWeb3Version1Provider(networks: { + ens?: { + provider: Web3Version1Provider; + network: string; + }; uns?: { locations: { Layer1: { @@ -354,6 +412,14 @@ export default class Resolution { }; }): Resolution { return this.fromEthereumEip1193Provider({ + ens: networks.ens + ? { + network: networks.ens.network, + provider: Eip1193Factories.fromWeb3Version1Provider( + networks.ens.provider, + ), + } + : undefined, uns: networks.uns ? { locations: { @@ -378,13 +444,17 @@ export default class Resolution { /** * Creates instance of resolution from provider that implements Ethers Provider#call interface. * This wrapper support only `eth_call` method for now, which is enough for all the current Resolution functionality - * @param networks - an object that describes what network to use when connecting UNS default is mainnet + * @param networks - an object that describes what network to use when connecting ENS or UNS default is mainnet * @see https://github.com/ethers-io/ethers.js/blob/v4-legacy/providers/abstract-provider.d.ts#L91 * @see https://github.com/ethers-io/ethers.js/blob/v5.0.4/packages/abstract-provider/src.ts/index.ts#L224 * @see https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#jsonrpcprovider-inherits-from-provider * @see https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/json-rpc-provider.ts */ static fromEthersProvider(networks: { + ens?: { + network: string; + provider: EthersProvider; + }; uns?: { locations: { Layer1: { @@ -399,6 +469,14 @@ export default class Resolution { }; }): Resolution { return this.fromEthereumEip1193Provider({ + ens: networks.ens + ? { + network: networks.ens.network, + provider: Eip1193Factories.fromEthersProvider( + networks.ens.provider, + ), + } + : undefined, uns: networks.uns ? { locations: { @@ -428,8 +506,18 @@ export default class Resolution { * @throws [[ResolutionError]] if address is not found * @returns A promise that resolves in an address */ - async addr(domain: string, ticker: string): Promise { - return this.record(domain, `crypto.${ticker.toUpperCase()}.address`); + async addr(domain: string, ticker: string): Promise { + domain = prepareAndValidateDomain(domain); + return await this.callServiceForDomain(domain, async (service) => { + if (service instanceof Ens) { + return await service.addr(domain, ticker); + } + + return await this.record( + domain, + `crypto.${ticker.toUpperCase()}.address`, + ); + }); } /** @@ -445,12 +533,20 @@ export default class Resolution { domain: string, ticker: string, chain: string, - ): Promise { + ): Promise { domain = prepareAndValidateDomain(domain); const recordKey = `crypto.${ticker.toUpperCase()}.version.${chain.toUpperCase()}.address`; - return this.callServiceForDomain(domain, (service) => - service.record(domain, recordKey), - ); + return this.callServiceForDomain(domain, async (service) => { + if (service instanceof Ens) { + throw new ResolutionError(ResolutionErrorCode.UnsupportedMethod, { + methodName: 'multiChainAddr', + domain, + method: service.name, + }); + } + + return await service.record(domain, recordKey); + }); } /** @@ -474,7 +570,19 @@ 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) { + if (err.code === ResolutionErrorCode.RecordNotFound) { + throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { + domain, + method: err.method, + methodName: 'chatId', + recordName: err.recordName, + }); + } + throw err; + } } /** @@ -484,7 +592,19 @@ 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) { + if (err.code === ResolutionErrorCode.RecordNotFound) { + throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { + domain, + method: err.method, + methodName: 'chatId', + recordName: err.recordName, + }); + } + throw err; + } } /** @@ -494,11 +614,19 @@ export default class Resolution { */ async ipfsHash(domain: string): Promise { domain = prepareAndValidateDomain(domain); - return this.getPreferableNewRecord( - domain, - 'dweb.ipfs.hash', - 'ipfs.html.value', - ); + return await this.callServiceForDomain(domain, async (service) => { + if (service instanceof Ens) { + // @see https://docs.ens.domains/ens-improvement-proposals/ensip-7-contenthash-field + const contentHash = await service.record(domain, 'contenthash'); + return `ipfs://${contentHash}`; + } + + return await this.getPreferableNewRecord( + domain, + 'dweb.ipfs.hash', + 'ipfs.html.value', + ); + }); } /** @@ -507,11 +635,17 @@ export default class Resolution { */ async httpUrl(domain: string): Promise { domain = prepareAndValidateDomain(domain); - return this.getPreferableNewRecord( - domain, - 'browser.redirect_url', - 'ipfs.redirect_domain.value', - ); + return await this.callServiceForDomain(domain, async (service) => { + if (service instanceof Ens) { + return await service.record(domain, 'url'); + } + + return await this.getPreferableNewRecord( + domain, + 'browser.redirect_url', + 'ipfs.redirect_domain.value', + ); + }); } /** @@ -521,7 +655,23 @@ export default class Resolution { * @returns A Promise that resolves in an email address configured for this domain whois */ async email(domain: string): Promise { - return this.record(domain, 'whois.email.value'); + domain = prepareAndValidateDomain(domain); + let key = 'whois.email.value'; + const serviceName = findNamingServiceName(domain); + if (serviceName === 'ENS') { + key = 'email'; + } + + try { + return await this.record(domain, key); + } catch (err) { + throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { + domain, + method: err.method, + methodName: 'email', + recordName: err.recordName, + }); + } } /** @@ -564,9 +714,9 @@ export default class Resolution { token: string, ): Promise { domain = prepareAndValidateDomain(domain); - return this.callServiceForDomain(domain, (service) => - service.getAddress(domain, network, token), - ); + return this.callServiceForDomain(domain, (service) => { + return service.getAddress(domain, network, token); + }); } /** @@ -627,7 +777,7 @@ export default class Resolution { /** * @returns Produces a namehash from supported naming service in hex format with 0x prefix. - * Corresponds to ERC721 token id in case of Ethereum based naming service like UNS. + * Corresponds to ERC721 token id in case of Ethereum based naming service like ENS or UNS. * @param domain domain name to be converted * @param namingService "UNS" or "ZNS" (uses keccak256 or sha256 algorithm respectively) * @param options formatting options @@ -653,7 +803,7 @@ export default class Resolution { * @returns a namehash of a subdomain with name label * @param parent namehash of a parent domain * @param label subdomain name - * @param namingService "UNS" or "ZNS" (uses keccak256 or sha256 algorithm respectively) + * @param namingService "ENS", "UNS" or "ZNS" (uses keccak256 or sha256 algorithm respectively) * @param options formatting options */ childhash( @@ -708,6 +858,7 @@ export default class Resolution { /** * Checks if the domain name is valid according to naming service rules * for valid domain names. + * Example: ENS doesn't allow domains that start from '-' symbol. * @param domain - domain name to be checked */ async isSupportedDomain(domain: string): Promise { @@ -725,6 +876,7 @@ export default class Resolution { /** * Returns all record keys of the domain. * This method is strongly unrecommended for production use due to lack of support for many ethereum service providers and low performance + * Method is not supported by ENS * @param domain - domain name * @deprecated */ @@ -751,12 +903,18 @@ export default class Resolution { * @param domain - domain name */ async tokenURI(domain: string): Promise { - // The `getTokenUri` method isn't supported in ZNS (it'll throw in the next call), so we just assume that we need - // to calculate a UNS namehash. - const namehash = this.namehash(domain, NamingServiceName.UNS); - return this.callServiceForDomain(domain, (service) => - service.getTokenUri(namehash), - ); + // The `getTokenUri` method isn't supported in ZNS (it'll throw in the next call) + return this.callServiceForDomain(domain, (service) => { + if (service.name === NamingServiceName.UNS) { + const namehash = this.namehash(domain, NamingServiceName.UNS); + return service.getTokenUri(namehash); + } else if (service.name === NamingServiceName.ENS) { + return service.getTokenUri(domain); + } + + const namehash = this.namehash(domain, NamingServiceName.ZNS); + return service.getTokenUri(namehash); + }); } /** @@ -782,10 +940,10 @@ export default class Resolution { /** * Retrieves the domain name from tokenId by parsing registry smart contract event logs. - * @throws {ResolutionError} if returned domain name doesn't match the original namhash. - * @returns the domain name retrieved from token metadata - * @param hash - domain hash - * @param service - nameservice which is used for lookup + * @throws {ResolutionError} if returned domain name doesn't match the original namehash. + * @returns the domain name retrieved from token metadata. + * @param hash - domain name hash or label hash. + * @param service - name service which is used for lookup. */ async unhash(hash: string, service: NamingServiceName): Promise { hash = fromDecStringToHex(hash); @@ -803,38 +961,46 @@ 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 nonEnsDomains = domains.filter( + (domain) => !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); - if (!zilDomains.length) { - return unsPromise; - } - - const znsServices = this.serviceMap.ZNS.usedServices; - // The actual ZNS service is the last one in the array. - const znsService = znsServices[znsServices.length - 1]; - // Start fetching ZNS locations before awaiting UNS ones for the concurrency sake, wrap errors to avoid unhandled - // exceptions in case we decide that we aren't interested in the result. - const znsPromise = wrapResult(() => znsService.locations(zilDomains)); - + const unsPromise = + this.serviceMap.UNS.usedServices[0].locations(nonEnsDomains); // Fetch UNS locations first. If we see that there are no .zil domains with absent locations, we can return early. const unsLocations = await unsPromise; - const emptyZilEntries = Object.entries(unsLocations).filter( - ([domain, location]) => domain.endsWith('.zil') && !location, - ); - if (!emptyZilEntries.length) { - return unsLocations; + if (zilDomains.length) { + const znsServices = this.serviceMap.ZNS.usedServices; + // The actual ZNS service is the last one in the array. + const znsService = znsServices[znsServices.length - 1]; + const znsPromise = wrapResult(() => znsService.locations(zilDomains)); + const emptyZilEntries = Object.entries(unsLocations).filter( + ([domain, location]) => domain.endsWith('.zil') && !location, + ); + + // If we don't have locations for some .zil domains in UNS, we want to check whether they are present in ZNS and + // merge them if that's the case. + const znsLocations = await znsPromise.then(unwrapResult); + for (const [domain] of emptyZilEntries) { + unsLocations[domain] = znsLocations[domain]; + } } - // If we don't have locations for some .zil domains in UNS, we want to check whether they are present in ZNS and - // merge them if that's the case. - const znsLocations = await znsPromise.then(unwrapResult); - for (const [domain] of emptyZilEntries) { - unsLocations[domain] = znsLocations[domain]; + + if (ensDomains.length) { + const ensLocations = await this.serviceMap.ENS.usedServices[0].locations( + ensDomains, + ); + for (const ensDomain in ensLocations) { + unsLocations[ensDomain] = ensLocations[ensDomain]; + } } return unsLocations; @@ -848,15 +1014,26 @@ export default class Resolution { async reverseTokenId( address: string, options?: ReverseResolutionOptions, - ): Promise { - const tokenId = this.reverseGetTokenId(address, options?.location); - return tokenId; + ): Promise { + const tokenId = await this.reverseGetTokenId(address, options?.location); + if (tokenId) { + return tokenId; + } + + const ensService = this.serviceMap['ENS'].native; + const ensDomainName = await ensService.reverseOf(address); + if (ensDomainName) { + const ensNameHash = ensService.namehash(ensDomainName); + return `${BigInt(ensNameHash)}`; + } + + return null; } /** * Returns the domain that is the primary resolution of the provided address * @param address - owner's address - * @returns Promise - domain URL that is the primary resolution of the provided addresss + * @returns Promise - domain URL that is the primary resolution of the provided address */ async reverse( address: string, @@ -868,6 +1045,12 @@ export default class Resolution { return this.unhash(tokenId as string, NamingServiceName.UNS); } + const ensService = this.serviceMap['ENS'].native; + const ensDomainName = await ensService.reverseOf(address); + if (ensDomainName) { + return ensDomainName; + } + return null; } @@ -1024,6 +1207,19 @@ export default class Resolution { ? new UdApi(config.sourceConfig?.zns) : new Zns(config.sourceConfig?.zns); } + + getEnsConfig(config: ResolutionConfig): Ens | UdApi { + if (config.apiKey) { + return new Ens({ + url: `${DEFAULT_UNS_PROXY_SERVICE_URL}/chains/eth/rpc`, + network: 'mainnet', + proxyServiceApiKey: config.apiKey, + }); + } + return isApi(config.sourceConfig?.ens) + ? new UdApi(config.sourceConfig?.ens) + : new Ens(config.sourceConfig?.ens); + } } export {Resolution}; diff --git a/src/UdApi.ts b/src/UdApi.ts index cbbef260..142e55da 100644 --- a/src/UdApi.ts +++ b/src/UdApi.ts @@ -23,6 +23,7 @@ import {NamingService} from './NamingService'; * @internal */ export default class UdApi extends NamingService { + readonly name?: string | undefined; public readonly url: string; private readonly headers: { [key: string]: string; diff --git a/src/config/ens-config.json b/src/config/ens-config.json new file mode 100644 index 00000000..ae045a1e --- /dev/null +++ b/src/config/ens-config.json @@ -0,0 +1,167 @@ +{ + "version": "0.1.8", + "networks": { + "1": { + "contracts": { + "ENSRegistry": { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "deploymentBlock": "0x8f221c" + }, + "BaseRegistrarImplementation": { + "address": "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + "deploymentBlock": "0x8f223a" + }, + "ReverseRegistrar": { + "address": "0xa58E81fe9b61B5c3fE2AFD33CF304c454AbFc7Cb", + "deploymentBlock": "0x10243a6" + }, + "DummyOracle": { + "address": "0x0000000000000000000000000000000000000000", + "deploymentBlock": "0x0" + }, + "StablePriceOracle": { + "address": "0x0000000000000000000000000000000000000000", + "deploymentBlock": "0x0" + }, + "NameWrapper": { + "address": "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401", + "deploymentBlock": "0x10243a8" + }, + "ETHRegistrarController": { + "address": "0x253553366Da8546fC250F225fe3d25d0C782303b", + "deploymentBlock": "0x10243b2" + }, + "PublicResolver": { + "address": "0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63", + "deploymentBlock": "0x10243b3" + }, + "ProxyAdmin": { + "address": "0xAA16DA78110D9A9742c760a1a064F28654Ab93de", + "deploymentBlock": "0xc2fedc" + }, + "ENSCustody": { + "address": "0x27c9B34eB43523447d3e1bcf26f009D814522687", + "deploymentBlock": "0x010ae8a9", + "implementation": "0x1FF7fC4473E3CDA0428830a1bc96028A0C12244C", + "forwarder": "0x27c9B34eB43523447d3e1bcf26f009D814522687" + }, + "LegacyENSRegistry": { + "address": "0x314159265dD8dbb310642f98f50C066173C1259b", + "deploymentBlock": "0x32c5b9" + }, + "LegacyETHRegistrarController": { + "address": "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", + "deploymentBlock": "0x8f2277" + } + } + }, + "5": { + "contracts": { + "ENSRegistry": { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "deploymentBlock": "0x1fd6d3" + }, + "BaseRegistrarImplementation": { + "address": "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + "deploymentBlock": "0x1fd6dd" + }, + "ReverseRegistrar": { + "address": "0x4f7A657451358a22dc397d5eE7981FfC526cd856", + "deploymentBlock": "0x830309" + }, + "NameWrapper": { + "address": "0x114D4603199df73e7D157787f8778E21fCd13066", + "deploymentBlock": "0x83f9a2" + }, + "DummyOracle": { + "address": "0x0000000000000000000000000000000000000000", + "deploymentBlock": "0x0" + }, + "StablePriceOracle": { + "address": "0x0000000000000000000000000000000000000000", + "deploymentBlock": "0x0" + }, + "ETHRegistrarController": { + "address": "0xCc5e7dB10E65EED1BBD105359e7268aa660f6734", + "deploymentBlock": "0x83fa46" + }, + "PublicResolver": { + "address": "0xd7a4F6473f32aC2Af804B3686AE8F1932bC35750", + "deploymentBlock": "0x83fa9e" + }, + "ProxyAdmin": { + "address": "0xf4906E210523F9dA79E33811A44EE000441F4E04", + "deploymentBlock": "0x5b57e8" + }, + "ENSCustody": { + "address": "0x74d1fA29295028e58573b894A4bF2cE8541036d4", + "deploymentBlock": "0x8b85b3", + "implementation": "0x223350B4892300EA6E8adEF48C955dcf1C544D02", + "forwarder": "0x74d1fA29295028e58573b894A4bF2cE8541036d4" + }, + "LegacyENSRegistry": { + "address": "0x112234455C3a32FD11230C42E7Bccd4A84e02010", + "deploymentBlock": "0xa890" + }, + "LegacyETHRegistrarController": { + "address": "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", + "deploymentBlock": "0x1fd6f0" + } + } + }, + "1337": { + "contracts": { + "ENSRegistry": { + "address": "0x24F45688D421f46cca60734BdC648ad192dB0f2E", + "deploymentBlock": "0x28" + }, + "BaseRegistrarImplementation": { + "address": "0x7e76aE49D6F4c6158757693b050BF88f039DeA21", + "deploymentBlock": "0x29" + }, + "ReverseRegistrar": { + "address": "0xEE9A59532B50514d4Cb2440b19E9539b6380CEcf", + "deploymentBlock": "0x2a" + }, + "NameWrapper": { + "address": "0xf6b7c221342BC966f4396790895530bF4E9799eB", + "deploymentBlock": "0x2d" + }, + "DummyOracle": { + "address": "0x38Dae41aEed12884D39dabD8F8a81Bb5B4562756", + "deploymentBlock": "0x2f" + }, + "StablePriceOracle": { + "address": "0xf7c9FD8E37863CF3f2da6A3ea0B1D1E3FaadeAD4", + "deploymentBlock": "0x30" + }, + "ETHRegistrarController": { + "address": "0x4Eb9dE1Fc0d800e941F326d6699E9E11969557ea", + "deploymentBlock": "0x31" + }, + "PublicResolver": { + "address": "0xeC71E4E674fe0f432693DC2b935C34bd2774C003", + "deploymentBlock": "0x3a" + }, + "ProxyAdmin": { + "address": "0x4e44E79e0cEc05D9e62e952B2088c02A3C450aeC", + "deploymentBlock": "0x36" + }, + "ENSCustody": { + "address": "0xf7313E5a57432B8A90233F7158D6dDE03572Dd9f", + "deploymentBlock": "0x3f", + "implementation": "0x38b83990077ac5DA78Aa9e72f5747D3A0b2374D9", + "forwarder": "0xf7313E5a57432B8A90233F7158D6dDE03572Dd9f" + }, + "LegacyENSRegistry": { + "address": "0xB17aAe5B3A7815433fC82Cfd67Ea245767894f03", + "deploymentBlock": "0x3c" + }, + "LegacyETHRegistrarController": { + "address": "0x3F1194B9019002863Ad0DBBE295294547c0d36A9", + "deploymentBlock": "0x32" + } + } + } + } +} diff --git a/src/contracts/ens/baseRegistrar.ts b/src/contracts/ens/baseRegistrar.ts new file mode 100644 index 00000000..fea9efa0 --- /dev/null +++ b/src/contracts/ens/baseRegistrar.ts @@ -0,0 +1,753 @@ +export default [ + { + inputs: [ + { + internalType: 'contract ENS', + name: '_ens', + type: 'address', + }, + { + internalType: 'bytes32', + name: '_baseNode', + type: 'bytes32', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'controller', + type: 'address', + }, + ], + name: 'ControllerAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'controller', + type: 'address', + }, + ], + name: 'ControllerRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'expires', + type: 'uint256', + }, + ], + name: 'NameMigrated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'expires', + type: 'uint256', + }, + ], + name: 'NameRegistered', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'expires', + type: 'uint256', + }, + ], + name: 'NameRenewed', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + constant: true, + inputs: [], + name: 'GRACE_PERIOD', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'controller', + type: 'address', + }, + ], + name: 'addController', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'available', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'baseNode', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'controllers', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'ens', + outputs: [ + { + internalType: 'contract ENS', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getApproved', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'isOwner', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'nameExpires', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'reclaim', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + ], + name: 'register', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + ], + name: 'registerOnly', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'controller', + type: 'address', + }, + ], + name: 'removeController', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + ], + name: 'renew', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [], + name: 'renounceOwnership', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'safeTransferFrom', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + ], + name: 'setResolver', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceID', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, +]; diff --git a/src/contracts/ens/ens.ts b/src/contracts/ens/ens.ts new file mode 100644 index 00000000..4b9cb723 --- /dev/null +++ b/src/contracts/ens/ens.ts @@ -0,0 +1,108 @@ +export default [ + { + constant: true, + inputs: [{name: 'node', type: 'bytes32'}], + name: 'resolver', + outputs: [{name: '', type: 'address'}], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [{name: 'node', type: 'bytes32'}], + name: 'owner', + outputs: [{name: '', type: 'address'}], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + {name: 'node', type: 'bytes32'}, + {name: 'label', type: 'bytes32'}, + {name: 'owner', type: 'address'}, + ], + name: 'setSubnodeOwner', + outputs: [], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + {name: 'node', type: 'bytes32'}, + {name: 'ttl', type: 'uint64'}, + ], + name: 'setTTL', + outputs: [], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [{name: 'node', type: 'bytes32'}], + name: 'ttl', + outputs: [{name: '', type: 'uint64'}], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + {name: 'node', type: 'bytes32'}, + {name: 'resolver', type: 'address'}, + ], + name: 'setResolver', + outputs: [], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + {name: 'node', type: 'bytes32'}, + {name: 'owner', type: 'address'}, + ], + name: 'setOwner', + outputs: [], + payable: false, + type: 'function', + }, + { + anonymous: false, + inputs: [ + {indexed: true, name: 'node', type: 'bytes32'}, + {indexed: false, name: 'owner', type: 'address'}, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, name: 'node', type: 'bytes32'}, + {indexed: true, name: 'label', type: 'bytes32'}, + {indexed: false, name: 'owner', type: 'address'}, + ], + name: 'NewOwner', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, name: 'node', type: 'bytes32'}, + {indexed: false, name: 'resolver', type: 'address'}, + ], + name: 'NewResolver', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, name: 'node', type: 'bytes32'}, + {indexed: false, name: 'ttl', type: 'uint64'}, + ], + name: 'NewTTL', + type: 'event', + }, +]; diff --git a/src/contracts/ens/nameWrapper.ts b/src/contracts/ens/nameWrapper.ts new file mode 100644 index 00000000..b12e5ac7 --- /dev/null +++ b/src/contracts/ens/nameWrapper.ts @@ -0,0 +1,1456 @@ +export default [ + { + inputs: [ + { + internalType: 'contract ENS', + name: '_ens', + type: 'address', + }, + { + internalType: 'contract IBaseRegistrar', + name: '_registrar', + type: 'address', + }, + { + internalType: 'contract IMetadataService', + name: '_metadataService', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'CannotUpgrade', + type: 'error', + }, + { + inputs: [], + name: 'IncompatibleParent', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'IncorrectTargetOwner', + type: 'error', + }, + { + inputs: [], + name: 'IncorrectTokenType', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'labelHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'expectedLabelhash', + type: 'bytes32', + }, + ], + name: 'LabelMismatch', + type: 'error', + }, + { + inputs: [ + { + internalType: 'string', + name: 'label', + type: 'string', + }, + ], + name: 'LabelTooLong', + type: 'error', + }, + { + inputs: [], + name: 'LabelTooShort', + type: 'error', + }, + { + inputs: [], + name: 'NameIsNotWrapped', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + ], + name: 'OperationProhibited', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + ], + name: 'Unauthorised', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'controller', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'active', + type: 'bool', + }, + ], + name: 'ControllerChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + name: 'ExpiryExtended', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint32', + name: 'fuses', + type: 'uint32', + }, + ], + name: 'FusesSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'NameUnwrapped', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'bytes', + name: 'name', + type: 'bytes', + }, + { + indexed: false, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'uint32', + name: 'fuses', + type: 'uint32', + }, + { + indexed: false, + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + name: 'NameWrapped', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'values', + type: 'uint256[]', + }, + ], + name: 'TransferBatch', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'TransferSingle', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'string', + name: 'value', + type: 'string', + }, + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'URI', + type: 'event', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: '_tokens', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'uint32', + name: 'fuseMask', + type: 'uint32', + }, + ], + name: 'allFusesBurned', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address[]', + name: 'accounts', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + ], + name: 'balanceOfBatch', + outputs: [ + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + ], + name: 'canExtendSubnames', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + ], + name: 'canModifyName', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'controllers', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'ens', + outputs: [ + { + internalType: 'contract ENS', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'parentNode', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'labelhash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + name: 'extendExpiry', + outputs: [ + { + internalType: 'uint64', + name: '', + type: 'uint64', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'getApproved', + outputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'getData', + outputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'uint32', + name: 'fuses', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + // NOTE: prefer to use the other isWrapped abi function + // { + // "inputs": [ + // { + // "internalType": "bytes32", + // "name": "parentNode", + // "type": "bytes32" + // }, + // { + // "internalType": "bytes32", + // "name": "labelhash", + // "type": "bytes32" + // } + // ], + // "name": "isWrapped", + // "outputs": [ + // { + // "internalType": "bool", + // "name": "", + // "type": "bool" + // } + // ], + // "stateMutability": "view", + // "type": "function" + // }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + ], + name: 'isWrapped', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'metadataService', + outputs: [ + { + internalType: 'contract IMetadataService', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + name: 'names', + outputs: [ + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'onERC721Received', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + { + internalType: 'address', + name: '_to', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'recoverFunds', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'string', + name: 'label', + type: 'string', + }, + { + internalType: 'address', + name: 'wrappedOwner', + type: 'address', + }, + { + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + { + internalType: 'uint16', + name: 'ownerControlledFuses', + type: 'uint16', + }, + ], + name: 'registerAndWrapETH2LD', + outputs: [ + { + internalType: 'uint256', + name: 'registrarExpiry', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'registrar', + outputs: [ + { + internalType: 'contract IBaseRegistrar', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + ], + name: 'renew', + outputs: [ + { + internalType: 'uint256', + name: 'expires', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'safeBatchTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'parentNode', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'labelhash', + type: 'bytes32', + }, + { + internalType: 'uint32', + name: 'fuses', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + name: 'setChildFuses', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'controller', + type: 'address', + }, + { + internalType: 'bool', + name: 'active', + type: 'bool', + }, + ], + name: 'setController', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'uint16', + name: 'ownerControlledFuses', + type: 'uint16', + }, + ], + name: 'setFuses', + outputs: [ + { + internalType: 'uint32', + name: '', + type: 'uint32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IMetadataService', + name: '_metadataService', + type: 'address', + }, + ], + name: 'setMetadataService', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + { + internalType: 'uint64', + name: 'ttl', + type: 'uint64', + }, + ], + name: 'setRecord', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + ], + name: 'setResolver', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'parentNode', + type: 'bytes32', + }, + { + internalType: 'string', + name: 'label', + type: 'string', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'uint32', + name: 'fuses', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + name: 'setSubnodeOwner', + outputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'parentNode', + type: 'bytes32', + }, + { + internalType: 'string', + name: 'label', + type: 'string', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + { + internalType: 'uint64', + name: 'ttl', + type: 'uint64', + }, + { + internalType: 'uint32', + name: 'fuses', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + name: 'setSubnodeRecord', + outputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'ttl', + type: 'uint64', + }, + ], + name: 'setTTL', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract INameWrapperUpgrade', + name: '_upgradeAddress', + type: 'address', + }, + ], + name: 'setUpgradeContract', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'parentNode', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'labelhash', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'controller', + type: 'address', + }, + ], + name: 'unwrap', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'labelhash', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'registrant', + type: 'address', + }, + { + internalType: 'address', + name: 'controller', + type: 'address', + }, + ], + name: 'unwrapETH2LD', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'name', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'extraData', + type: 'bytes', + }, + ], + name: 'upgrade', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'upgradeContract', + outputs: [ + { + internalType: 'contract INameWrapperUpgrade', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'uri', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'name', + type: 'bytes', + }, + { + internalType: 'address', + name: 'wrappedOwner', + type: 'address', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + ], + name: 'wrap', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'string', + name: 'label', + type: 'string', + }, + { + internalType: 'address', + name: 'wrappedOwner', + type: 'address', + }, + { + internalType: 'uint16', + name: 'ownerControlledFuses', + type: 'uint16', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + ], + name: 'wrapETH2LD', + outputs: [ + { + internalType: 'uint64', + name: 'expiry', + type: 'uint64', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +]; diff --git a/src/contracts/ens/newResolver.ts b/src/contracts/ens/newResolver.ts new file mode 100644 index 00000000..61a0a05e --- /dev/null +++ b/src/contracts/ens/newResolver.ts @@ -0,0 +1,368 @@ +export default [ + { + constant: true, + inputs: [{internalType: 'bytes4', name: 'interfaceID', type: 'bytes4'}], + name: 'supportsInterface', + outputs: [{internalType: 'bool', name: '', type: 'bool'}], + payable: false, + stateMutability: 'pure', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'string', name: 'key', type: 'string'}, + {internalType: 'string', name: 'value', type: 'string'}, + ], + name: 'setText', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'bytes4', name: 'interfaceID', type: 'bytes4'}, + ], + name: 'interfaceImplementer', + outputs: [{internalType: 'address', name: '', type: 'address'}], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'uint256', name: 'contentTypes', type: 'uint256'}, + ], + name: 'ABI', + outputs: [ + {internalType: 'uint256', name: '', type: 'uint256'}, + {internalType: 'bytes', name: '', type: 'bytes'}, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'bytes32', name: 'x', type: 'bytes32'}, + {internalType: 'bytes32', name: 'y', type: 'bytes32'}, + ], + name: 'setPubkey', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'bytes', name: 'hash', type: 'bytes'}, + ], + name: 'setContenthash', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + // This funciton is defined, however there is other version with 2 arguments + // that we prefer to use all the time to be consistent + // { + // constant: true, + // inputs: [{ internalType: 'bytes32', name: 'node', type: 'bytes32' }], + // name: 'addr', + // outputs: [{ internalType: 'address', name: '', type: 'address' }], + // payable: false, + // stateMutability: 'view', + // type: 'function', + // }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'address', name: 'target', type: 'address'}, + {internalType: 'bool', name: 'isAuthorised', type: 'bool'}, + ], + name: 'setAuthorisation', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'string', name: 'key', type: 'string'}, + ], + name: 'text', + outputs: [{internalType: 'string', name: '', type: 'string'}], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'uint256', name: 'contentType', type: 'uint256'}, + {internalType: 'bytes', name: 'data', type: 'bytes'}, + ], + name: 'setABI', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [{internalType: 'bytes32', name: 'node', type: 'bytes32'}], + name: 'name', + outputs: [{internalType: 'string', name: '', type: 'string'}], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'string', name: 'name', type: 'string'}, + ], + name: 'setName', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'uint256', name: 'coinType', type: 'uint256'}, + {internalType: 'bytes', name: 'a', type: 'bytes'}, + ], + name: 'setAddr', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [{internalType: 'bytes32', name: 'node', type: 'bytes32'}], + name: 'contenthash', + outputs: [{internalType: 'bytes', name: '', type: 'bytes'}], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [{internalType: 'bytes32', name: 'node', type: 'bytes32'}], + name: 'pubkey', + outputs: [ + {internalType: 'bytes32', name: 'x', type: 'bytes32'}, + {internalType: 'bytes32', name: 'y', type: 'bytes32'}, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'address', name: 'a', type: 'address'}, + ], + name: 'setAddr', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'bytes4', name: 'interfaceID', type: 'bytes4'}, + {internalType: 'address', name: 'implementer', type: 'address'}, + ], + name: 'setInterface', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + {internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {internalType: 'uint256', name: 'coinType', type: 'uint256'}, + ], + name: 'addr', + outputs: [{internalType: 'bytes', name: '', type: 'bytes'}], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + {internalType: 'bytes32', name: '', type: 'bytes32'}, + {internalType: 'address', name: '', type: 'address'}, + {internalType: 'address', name: '', type: 'address'}, + ], + name: 'authorisations', + outputs: [{internalType: 'bool', name: '', type: 'bool'}], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{internalType: 'contract ENS', name: '_ens', type: 'address'}], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'target', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isAuthorised', + type: 'bool', + }, + ], + name: 'AuthorisationChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + { + indexed: false, + internalType: 'string', + name: 'indexedKey', + type: 'string', + }, + {indexed: false, internalType: 'string', name: 'key', type: 'string'}, + ], + name: 'TextChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {indexed: false, internalType: 'bytes32', name: 'x', type: 'bytes32'}, + {indexed: false, internalType: 'bytes32', name: 'y', type: 'bytes32'}, + ], + name: 'PubkeyChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {indexed: false, internalType: 'string', name: 'name', type: 'string'}, + ], + name: 'NameChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + { + indexed: true, + internalType: 'bytes4', + name: 'interfaceID', + type: 'bytes4', + }, + { + indexed: false, + internalType: 'address', + name: 'implementer', + type: 'address', + }, + ], + name: 'InterfaceChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {indexed: false, internalType: 'bytes', name: 'hash', type: 'bytes'}, + ], + name: 'ContenthashChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + {indexed: false, internalType: 'address', name: 'a', type: 'address'}, + ], + name: 'AddrChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + { + indexed: false, + internalType: 'uint256', + name: 'coinType', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes', + name: 'newAddress', + type: 'bytes', + }, + ], + name: 'AddressChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'bytes32', name: 'node', type: 'bytes32'}, + { + indexed: true, + internalType: 'uint256', + name: 'contentType', + type: 'uint256', + }, + ], + name: 'ABIChanged', + type: 'event', + }, +]; diff --git a/src/contracts/ens/oldResolver.ts b/src/contracts/ens/oldResolver.ts new file mode 100644 index 00000000..6f06cbfe --- /dev/null +++ b/src/contracts/ens/oldResolver.ts @@ -0,0 +1,389 @@ +export default [ + { + inputs: [{name: 'ensAddr', type: 'address'}], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + name: 'a', + type: 'address', + }, + ], + name: 'AddrChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + name: 'name', + type: 'string', + }, + ], + name: 'NameChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'node', + type: 'bytes32', + }, + { + indexed: true, + name: 'contentType', + type: 'uint256', + }, + ], + name: 'ABIChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + name: 'x', + type: 'bytes32', + }, + { + indexed: false, + name: 'y', + type: 'bytes32', + }, + ], + name: 'PubkeyChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + name: 'indexedKey', + type: 'string', + }, + { + indexed: false, + name: 'key', + type: 'string', + }, + ], + name: 'TextChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'node', + type: 'bytes32', + }, + { + indexed: false, + name: 'hash', + type: 'bytes', + }, + ], + name: 'ContenthashChanged', + type: 'event', + }, + { + constant: false, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'addr', + type: 'address', + }, + ], + name: 'setAddr', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'hash', + type: 'bytes', + }, + ], + name: 'setContenthash', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'name', + type: 'string', + }, + ], + name: 'setName', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'contentType', + type: 'uint256', + }, + { + name: 'data', + type: 'bytes', + }, + ], + name: 'setABI', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'x', + type: 'bytes32', + }, + { + name: 'y', + type: 'bytes32', + }, + ], + name: 'setPubkey', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'key', + type: 'string', + }, + { + name: 'value', + type: 'string', + }, + ], + name: 'setText', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'key', + type: 'string', + }, + ], + name: 'text', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + ], + name: 'pubkey', + outputs: [ + { + name: 'x', + type: 'bytes32', + }, + { + name: 'y', + type: 'bytes32', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + { + name: 'contentTypes', + type: 'uint256', + }, + ], + name: 'ABI', + outputs: [ + { + name: '', + type: 'uint256', + }, + { + name: '', + type: 'bytes', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + ], + name: 'name', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + ], + name: 'addr', + outputs: [ + { + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'node', + type: 'bytes32', + }, + ], + name: 'contenthash', + outputs: [ + { + name: '', + type: 'bytes', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'interfaceID', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'pure', + type: 'function', + }, +]; diff --git a/src/contracts/ens/resolver.ts b/src/contracts/ens/resolver.ts new file mode 100644 index 00000000..e141fc49 --- /dev/null +++ b/src/contracts/ens/resolver.ts @@ -0,0 +1,33 @@ +import { + ResolutionError, + ResolutionErrorCode, +} from '../../errors/resolutionError'; +import {default as newResolver} from './newResolver'; +import {default as oldResolver} from './oldResolver'; +import {EthCoinIndex} from '../../types'; +import {NamingServiceName} from '../../types/publicTypes'; +import {JsonFragment} from '@ethersproject/abi'; + +export const OldResolverAddresses = [ + '0x5ffc014343cd971b7eb70732021e26c35b744cc4', + '0x1da022710df5002339274aadee8d58218e9d6ab5', + '0xda1756bb923af5d1a05e277cb1e54f1d0a127890', +]; + +export default (addr: string, coinType?: string): JsonFragment[] => { + if (coinType === undefined || coinType === EthCoinIndex) { + // Old interface is only compatible to output the ETH address + // New interface is compatible to that API + // So we prefer old interface when currency is ETH + return oldResolver; + } else { + if (OldResolverAddresses.includes(addr.toLowerCase())) { + throw new ResolutionError( + ResolutionErrorCode.IncorrectResolverInterface, + {method: NamingServiceName.ENS}, + ); + } + + return newResolver; + } +}; diff --git a/src/contracts/ens/reverseRegistrar.ts b/src/contracts/ens/reverseRegistrar.ts new file mode 100644 index 00000000..bb7b53b9 --- /dev/null +++ b/src/contracts/ens/reverseRegistrar.ts @@ -0,0 +1,336 @@ +export default [ + { + inputs: [ + { + internalType: 'contract ENS', + name: 'ensAddr', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'controller', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'enabled', + type: 'bool', + }, + ], + name: 'ControllerChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract NameResolver', + name: 'resolver', + type: 'address', + }, + ], + name: 'DefaultResolverChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'addr', + type: 'address', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'node', + type: 'bytes32', + }, + ], + name: 'ReverseClaimed', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'claim', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + ], + name: 'claimForAddr', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + ], + name: 'claimWithResolver', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'controllers', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'defaultResolver', + outputs: [ + { + internalType: 'contract NameResolver', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'ens', + outputs: [ + { + internalType: 'contract ENS', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + ], + name: 'node', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'controller', + type: 'address', + }, + { + internalType: 'bool', + name: 'enabled', + type: 'bool', + }, + ], + name: 'setController', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + ], + name: 'setDefaultResolver', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'string', + name: 'name', + type: 'string', + }, + ], + name: 'setName', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'resolver', + type: 'address', + }, + { + internalType: 'string', + name: 'name', + type: 'string', + }, + ], + name: 'setNameForAddr', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +]; diff --git a/src/errors/configurationError.ts b/src/errors/configurationError.ts index 7959df93..73d1e82a 100644 --- a/src/errors/configurationError.ts +++ b/src/errors/configurationError.ts @@ -19,6 +19,7 @@ export enum ConfigurationErrorCode { NetworkConfigMissing = 'NetworkConfigMissing', CustomNetworkConfigMissing = 'CustomNetworkConfigMissing', InvalidConfigurationField = 'InvalidProxyReader', + DependencyMissing = 'DependencyMissing', } /** @@ -49,6 +50,11 @@ const HandlersByCode = { method: ResolutionMethod; field: string; }) => `Invalid '${params.field}' in Resolution ${params.method}`, + [ConfigurationErrorCode.DependencyMissing]: (params: { + dependency: string; + version: string; + }) => + `Missing dependency for this functionality. Please install ${params.dependency} @ ${params.version} via npm or yarn`, }; /** diff --git a/src/errors/resolutionError.ts b/src/errors/resolutionError.ts index ff1903a2..8b1998a7 100644 --- a/src/errors/resolutionError.ts +++ b/src/errors/resolutionError.ts @@ -124,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); @@ -137,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); } } diff --git a/src/tests/Ens.test.ts b/src/tests/Ens.test.ts new file mode 100644 index 00000000..1cf6f9c1 --- /dev/null +++ b/src/tests/Ens.test.ts @@ -0,0 +1,415 @@ +import nock from 'nock'; +import Resolution, {NamingServiceName, ResolutionErrorCode} from '../index'; +import {NullAddress} from '../types'; +import { + expectResolutionErrorCode, + expectSpyToBeCalled, + mockAsyncMethods, + getProtocolLinkFromEnv, + ProviderProtocol, + skipItInLive, +} from './helpers'; +import Ens from '../Ens'; + +let resolution: Resolution; +let ens: Ens; + +beforeEach(() => { + nock.cleanAll(); + jest.restoreAllMocks(); + resolution = new Resolution({ + sourceConfig: { + ens: { + url: getProtocolLinkFromEnv( + ProviderProtocol.http, + NamingServiceName.ENS, + ), + network: 'goerli', + }, + }, + }); + ens = resolution.serviceMap[NamingServiceName.ENS].native as Ens; +}); + +describe('ENS', () => { + it('allows ens network specified as string', async () => { + expect(ens.url).toBe( + getProtocolLinkFromEnv(ProviderProtocol.http, NamingServiceName.ENS), + ); + expect(ens.network).toEqual(5); + }); + + it('resolves .eth name using blockchain', async () => { + expect(ens.url).toBe( + getProtocolLinkFromEnv(ProviderProtocol.http, NamingServiceName.ENS), + ); + expect(ens.network).toEqual(5); + + const eyes = mockAsyncMethods(ens, { + resolver: '0x5FfC014343cd971B7eb70732021E26C35B744cc4', + fetchAddress: '0xa59C818Ddb801f1253edEbf0Cf08c9E481EA2fE5', + }); + const spy = mockAsyncMethods(ens, { + owner: '0xa59C818Ddb801f1253edEbf0Cf08c9E481EA2fE5', + }); + expect(await resolution.addr('matthewgould.eth', 'ETH')).toEqual( + '0xa59C818Ddb801f1253edEbf0Cf08c9E481EA2fE5', + ); + expect(await resolution.owner('matthewgould.eth')).toEqual( + '0xa59C818Ddb801f1253edEbf0Cf08c9E481EA2fE5', + ); + expectSpyToBeCalled(eyes); + expectSpyToBeCalled(spy, 1); + }); + + skipItInLive('reverses address to ENS domain', async () => { + const eyes = mockAsyncMethods(ens, { + reverseRegistrarCallToNode: + '0x4da70a332a7a98a58486f551a455b1398ce309d9bd3a4f0800da4eec299829a4', + callMethod: '0xDa1756Bb923Af5d1a05E277CB1E54f1D0A127890', + resolverCallToAddr: '0xb0E7a465D255aE83eb7F8a50504F3867B945164C', + resolverCallToName: 'adrian.argent.xyz', + }); + const result = await ens?.reverseOf( + '0xb0E7a465D255aE83eb7F8a50504F3867B945164C', + ); + expectSpyToBeCalled(eyes); + expect(result).toEqual('adrian.argent.xyz'); + }); + + it('reverses address to ENS domain null', async () => { + const spy = mockAsyncMethods(ens, { + reverseRegistrarCallToNode: + '0x4da70a332a7a98a58486f551a455b1398ce309d9bd3a4f0800da4eec299829a4', + callMethod: NullAddress, + }); + const result = await ens?.reverseOf( + '0x112234455c3a32fd11230c42e7bccd4a84e02010', + ); + expectSpyToBeCalled(spy); + expect(result).toEqual(null); + }); + + it('resolves .xyz name using ENS blockchain', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0xDa1756Bb923Af5d1a05E277CB1E54f1D0A127890', + fetchAddress: '0xb0E7a465D255aE83eb7F8a50504F3867B945164C', + }); + + const result = await resolution.addr('adrian.argent.xyz', 'ETH'); + expectSpyToBeCalled(eyes); + expect(result).toEqual('0xb0E7a465D255aE83eb7F8a50504F3867B945164C'); + }); + + it('resolves .luxe name using ENS blockchain', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', + fetchAddress: '0xf3dE750A73C11a6a2863761E930BF5fE979d5663', + }); + + const result = await resolution.addr('john.luxe', 'ETH'); + expectSpyToBeCalled(eyes); + expect(result).toEqual('0xf3dE750A73C11a6a2863761E930BF5fE979d5663'); + }); + + it('resolves .kred name using ENS blockchain', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x96184444629F3489c4dE199871E6F99568229d8f', + fetchAddress: '0x96184444629F3489c4dE199871E6F99568229d8f', + }); + const result = await resolution.addr('brantly.kred', 'ETH'); + expectSpyToBeCalled(eyes); + expect(result).toEqual('0x96184444629F3489c4dE199871E6F99568229d8f'); + }); + + it('resolves .luxe name using ENS blockchain with thrown error', async () => { + const spies = mockAsyncMethods(ens, { + resolver: undefined, + owner: undefined, + }); + + await expectResolutionErrorCode( + resolution.addr('something.luxe', 'ETH'), + ResolutionErrorCode.UnregisteredDomain, + ); + expectSpyToBeCalled(spies); + }); + + it('resolves name with resolver but without an owner', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: 'DBXu2kgc3xtvCUWFcxFE3r9hEYgmuaaCyD', + }); + const doge = await resolution.addr('testthing.eth', 'DOGE'); + expectSpyToBeCalled(eyes); + expect(doge).toBe('DBXu2kgc3xtvCUWFcxFE3r9hEYgmuaaCyD'); + }); + + it('checks normalizeSource ens (object)', async () => { + expect(ens.network).toBe(5); + expect(ens.url).toBe( + getProtocolLinkFromEnv(ProviderProtocol.http, NamingServiceName.ENS), + ); + expect(ens.registryContract.address).toBe( + '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + ); + }); + + it('normalizeSource ens (object) should throw error', async () => { + expect( + () => + new Resolution({ + sourceConfig: { + ens: {network: 'notRealNetwork'}, + }, + }), + ).toThrowError( + 'Missing configuration in Resolution ENS. Please specify registryAddress when using a custom network', + ); + }); + + it('checks normalizeSource ens (object) #13', async () => { + expect( + () => + new Resolution({ + sourceConfig: { + ens: {network: 'custom', url: 'https://custom.notinfura.io'}, + }, + }), + ).toThrowError( + 'Missing configuration in Resolution ENS. Please specify registryAddress when using a custom network', + ); + }); + + it('checks ens multicoin support #1', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: 'DBXu2kgc3xtvCUWFcxFE3r9hEYgmuaaCyD', + }); + const doge = await resolution.addr('testthing.eth', 'DOGE'); + expectSpyToBeCalled(eyes); + expect(doge).toBe('DBXu2kgc3xtvCUWFcxFE3r9hEYgmuaaCyD'); + }); + + it('checks ens multicoin support #2', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: 'MV5rN5EcX1imDS2gEh5jPJXeiW5QN8YrK3', + }); + const ltc = await resolution.addr('testthing.eth', 'LTC'); + expectSpyToBeCalled(eyes); + expect(ltc).toBe('MV5rN5EcX1imDS2gEh5jPJXeiW5QN8YrK3'); + }); + + it('checks ens multicoin support #3', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: '0x314159265dD8dbb310642f98f50C066173C1259b', + }); + const eth = await resolution.addr('testthing.eth', 'ETH'); + expectSpyToBeCalled(eyes); + expect(eth).toBe('0x314159265dD8dbb310642f98f50C066173C1259b'); + }); + + it('checks ens multicoin support #4', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: '0x314159265dD8dbb310642f98f50C066173C1259b', + }); + const etc = await resolution.addr('testthing.eth', 'etc'); + expectSpyToBeCalled(eyes); + expect(etc).toBe('0x314159265dD8dbb310642f98f50C066173C1259b'); + }); + + it('checks ens multicoin support #5', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: '0x314159265dD8DbB310642F98f50C066173c1259B', + }); + const rsk = await resolution.addr('testthing.eth', 'rsk'); + expectSpyToBeCalled(eyes); + expect(rsk).toBe('0x314159265dD8DbB310642F98f50C066173c1259B'); + }); + + it('checks ens multicoin support #6', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: 'X7qvLs7gSnNoKvZzNWUT2e8st17QPY64PPe7zriLNuJszeg', + }); + const xrp = await resolution.addr('testthing.eth', 'xrp'); + expectSpyToBeCalled(eyes); + expect(xrp).toBe('X7qvLs7gSnNoKvZzNWUT2e8st17QPY64PPe7zriLNuJszeg'); + }); + + it('checks ens multicoin support #7', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a', + }); + const bch = await resolution.addr('testthing.eth', 'bch'); + expectSpyToBeCalled(eyes); + expect(bch).toBe('bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a'); + }); + + it('checks ens multicoin support #8', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + fetchAddress: + 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx', + }); + const btc = await resolution.addr('testthing.eth', 'BTC'); + expectSpyToBeCalled(eyes); + expect(btc).toBe( + 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx', + ); + }); + + it('checks UnsupportedCurrency error', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + }); + await expectResolutionErrorCode( + resolution.addr('testthing.eth', 'UNREALTICKER'), + ResolutionErrorCode.UnsupportedCurrency, + ); + expectSpyToBeCalled(eyes); + }); + + describe('.resolve', () => { + it('should return correct resolver address', async () => { + const spies = mockAsyncMethods(ens, { + resolver: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', + }); + const resolverAddress = await resolution.resolver('monkybrain.eth'); + expectSpyToBeCalled(spies); + expect(resolverAddress).toBe( + '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', + ); + }); + + it('should not find a resolver address', async () => { + const spies = mockAsyncMethods(ens, { + resolver: undefined, + }); + await expectResolutionErrorCode( + resolution.resolver('empty.eth'), + ResolutionErrorCode.UnspecifiedResolver, + ); + expectSpyToBeCalled(spies); + }); + }); + + describe('.Hashing', () => { + describe('.namehash', () => { + it('supports root node', async () => { + expect(await resolution.isSupportedDomain('eth')).toEqual(true); + expect(resolution.namehash('eth', NamingServiceName.ENS)).toEqual( + '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae', + ); + }); + + it('should hash appropriately', async () => { + expect(resolution.namehash('alice.eth', NamingServiceName.ENS)).toBe( + '0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec', + ); + }); + }); + }); + + describe('.Metadata', () => { + it('should return a valid ipfsHash', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', + callMethod: + '0xe301017012208723b9b5834fe60801e19af3a3554a6f229dad9cfbb18ce4e80ffc2a457f83aa', + }); + const ipfsHash = await resolution.ipfsHash('monkybrain.eth'); + expectSpyToBeCalled(eyes); + expect(ipfsHash).toBe( + 'ipfs://QmXSBLw6VMegqkCHSDBPg7xzfLhUyuRBzTb927KVzKC1vq', + ); + }); + + // todo(johny) find some domains with url property set + it('should not find an appropriate httpUrl', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x5FfC014343cd971B7eb70732021E26C35B744cc4', + callMethod: '', + }); + await expectResolutionErrorCode( + resolution.httpUrl('matthewgould.eth'), + ResolutionErrorCode.RecordNotFound, + ); + expectSpyToBeCalled(eyes); + }); + + it('should return resolution error for not finding the email', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x5FfC014343cd971B7eb70732021E26C35B744cc4', + callMethod: '', + }); + const emailPromise = resolution.email('matthewgould.eth'); + await expectResolutionErrorCode( + emailPromise, + ResolutionErrorCode.RecordNotFound, + ); + expectSpyToBeCalled(eyes); + }); + + it('should resolve gundb id and public key', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', + callMethod: + '0x7e1d12f34e038a2bda3d5f6ee0809d72f668c357d9e64fd7f622513f06ea652146ab5fdee35dc4ce77f1c089fd74972691fccd48130306d9eafcc6e1437d1ab21b', + }); + const chatId = await resolution + .chatId('crunk.eth') + .catch((err) => err.code); + expectSpyToBeCalled(eyes); + expect(chatId).toBe( + '0x7e1d12f34e038a2bda3d5f6ee0809d72f668c357d9e64fd7f622513f06ea652146ab5fdee35dc4ce77f1c089fd74972691fccd48130306d9eafcc6e1437d1ab21b', + ); + }); + + it('should resolve gundb public key', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', + callMethod: + 'yxbMDgFrzemQEcDwJYccE_TDbGmRL_iqZ2JhQxYi2s8.nBEAyMfM2ZBtOf2C-GHe3zEn42Q1vrfPAVqNzgGhXvQ', + }); + const publicKey = await resolution + .chatPk('crunk.eth') + .catch((err) => err.code); + expectSpyToBeCalled(eyes); + expect(publicKey).toBe( + 'yxbMDgFrzemQEcDwJYccE_TDbGmRL_iqZ2JhQxYi2s8.nBEAyMfM2ZBtOf2C-GHe3zEn42Q1vrfPAVqNzgGhXvQ', + ); + }); + + it('should return resolution error for not finding the gundb chat id', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + callMethod: undefined, + }); + const emailPromise = resolution.chatId('test.eth'); + await expectResolutionErrorCode( + emailPromise, + ResolutionErrorCode.RecordNotFound, + ); + expectSpyToBeCalled(eyes); + }); + + it('should return resolution error for not finding the gundb publicKey', async () => { + const eyes = mockAsyncMethods(ens, { + resolver: '0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8', + callMethod: undefined, + }); + const emailPromise = resolution.chatPk('test.eth'); + await expectResolutionErrorCode( + emailPromise, + ResolutionErrorCode.RecordNotFound, + ); + expectSpyToBeCalled(eyes); + }); + }); +}); diff --git a/src/tests/Resolution.test.ts b/src/tests/Resolution.test.ts index 4eeba635..65bcde74 100644 --- a/src/tests/Resolution.test.ts +++ b/src/tests/Resolution.test.ts @@ -19,7 +19,7 @@ import { expectResolutionErrorCode, expectSpyToBeCalled, mockAsyncMethods, - getUnsProtocolLinkFromEnv, + getProtocolLinkFromEnv, ProviderProtocol, caseMock, mockAsyncMethod, @@ -37,6 +37,7 @@ import {RpcProviderTestCases} from './providerMockData'; import fetch, {FetchError} from 'node-fetch'; import Uns from '../Uns'; import Zns from '../Zns'; +import Ens from '../Ens'; import FetchProvider from '../FetchProvider'; import { ConfigurationErrorCode, @@ -47,10 +48,12 @@ import {Eip1993Factories as Eip1193Factories} from '../utils/Eip1993Factories'; import UnsConfig from '../config/uns-config.json'; import {NullAddress} from '../types'; import Networking from '../utils/Networking'; +import {findNamingServiceName} from '../utils'; let resolution: Resolution; let uns: Uns; let zns: Zns; +let ens: Ens; beforeEach(() => { nock.cleanAll(); @@ -60,20 +63,28 @@ beforeEach(() => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, }, + ens: { + url: getProtocolLinkFromEnv( + ProviderProtocol.http, + NamingServiceName.ENS, + ), + network: 'goerli', + }, zns: {network: 'testnet'}, }, }); uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; zns = resolution.serviceMap[NamingServiceName.ZNS].native as Zns; + ens = resolution.serviceMap[NamingServiceName.ENS].native as Ens; }); describe('Resolution', () => { @@ -91,16 +102,11 @@ describe('Resolution', () => { describe('.Basic setup', () => { it('should work with autonetwork url configuration', async () => { - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); - const goerliUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); + const goerliUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); // mocking getNetworkConfigs because no access to inner provider.request const UnsGetNetworkOriginal = Uns.autoNetwork; + const EnsGetNetworkOriginal = Ens.autoNetwork; if (!isLive()) { Uns.autoNetwork = jest.fn().mockReturnValue( new Uns({ @@ -116,14 +122,23 @@ describe('Resolution', () => { }, }), ); + Ens.autoNetwork = jest.fn().mockReturnValue( + new Ens({ + url: goerliUrl, + network: 'goerli', + provider: new FetchProvider(NamingServiceName.ENS, goerliUrl), + }), + ); } const resolution = await Resolution.autoNetwork({ uns: { locations: {Layer1: {url: goerliUrl}, Layer2: {url: polygonUrl}}, }, + ens: {url: goerliUrl}, }); // We need to manually restore the function as jest.restoreAllMocks and simillar works only with spyOn Uns.autoNetwork = UnsGetNetworkOriginal; + Ens.autoNetwork = EnsGetNetworkOriginal; expect( (resolution.serviceMap[NamingServiceName.UNS].native as Uns).unsl1 .network, @@ -132,18 +147,15 @@ describe('Resolution', () => { (resolution.serviceMap[NamingServiceName.UNS].native as Uns).unsl2 .network, ).toBe('polygon-mumbai'); + expect( + (resolution.serviceMap[NamingServiceName.ENS].native as Ens).network, + ).toBe(5); }); it('should not work with invalid proxyReader configuration #1', async () => { - const goerliUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ); + const goerliUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); const customNetwork = 'goerli'; - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); await expectConfigurationErrorCode(() => { new Uns({ locations: { @@ -162,15 +174,9 @@ describe('Resolution', () => { }, ConfigurationErrorCode.InvalidConfigurationField); }); it('should not work with invalid proxyReader configuration #2', async () => { - const goerliUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ); + const goerliUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); const customNetwork = 'goerli'; - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); await expect(() => { new Uns({ locations: { @@ -198,15 +204,9 @@ describe('Resolution', () => { }); it('should not work with invalid proxyReader configuration #3', async () => { - const goerliUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ); + const goerliUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); const provider = new FetchProvider(NamingServiceName.UNS, goerliUrl); - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); const polygonProvider = new FetchProvider(UnsLocation.Layer2, polygonUrl); const customNetwork = 'goerli'; await expectConfigurationErrorCode(() => { @@ -228,14 +228,8 @@ describe('Resolution', () => { }); it('should work with proxyReader configuration', async () => { - const goerliUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ); - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); + const goerliUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); const customNetwork = 'goerli'; const uns = new Uns({ @@ -256,15 +250,9 @@ describe('Resolution', () => { }); it('should work with custom network configuration with provider', async () => { - const goerliUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ); + const goerliUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); const provider = new FetchProvider(NamingServiceName.UNS, goerliUrl); - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); const polygonProvider = new FetchProvider(UnsLocation.Layer2, polygonUrl); const customNetwork = 'goerli'; const uns = new Uns({ @@ -287,13 +275,10 @@ describe('Resolution', () => { it('should work with autonetwork provider configuration', async () => { const provider = new FetchProvider( 'UDAPI', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); const polygonProvider = new FetchProvider(UnsLocation.Layer2, polygonUrl); const spy = mockAsyncMethod(provider, 'request', '1'); const spyTwo = mockAsyncMethod(polygonProvider, 'request', '80001'); @@ -301,8 +286,9 @@ describe('Resolution', () => { uns: { locations: {Layer1: {provider}, Layer2: {provider: polygonProvider}}, }, + ens: {provider}, }); - expect(spy).toBeCalledTimes(1); + expect(spy).toBeCalledTimes(2); expect(spyTwo).toBeCalledTimes(1); expect( (resolution.serviceMap[NamingServiceName.UNS].native as Uns).unsl1 @@ -312,6 +298,9 @@ describe('Resolution', () => { (resolution.serviceMap[NamingServiceName.UNS].native as Uns).unsl2 .network, ).toBe('polygon-mumbai'); + expect( + (resolution.serviceMap[NamingServiceName.ENS].native as Ens).network, + ).toBe(1); }); it('should fail because provided url failled net_version call', async () => { @@ -350,15 +339,39 @@ describe('Resolution', () => { expectSpyToBeCalled([factorySpy, providerSpy]); }); + it('should fail because provided provider failed to make a net_version call', async () => { + const mockedProvider = new FetchProvider( + NamingServiceName.ENS, + 'http://unstoppabledomains.com', + ); + const providerSpy = mockAsyncMethod( + mockedProvider, + 'request', + new FetchError( + 'invalid json response body at https://unstoppabledomains.com/ reason: Unexpected token < in JSON at position 0', + 'invalid_json', + 'invalid_json', + ), + ); + try { + await Resolution.autoNetwork({ + ens: {provider: mockedProvider}, + }); + } catch (error) { + expect(error).toBeInstanceOf(FetchError); + expect(error.message).toBe( + 'invalid json response body at https://unstoppabledomains.com/ reason: Unexpected token < in JSON at position 0', + ); + } + expect(providerSpy).toBeCalled(); + }); + it('should fail because of unsupported test network for uns', async () => { - const blockchainUrl = getUnsProtocolLinkFromEnv( + const blockchainUrl = getProtocolLinkFromEnv( ProviderProtocol.http, 'UNSL1', ); - const polygonUrl = getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ); + const polygonUrl = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'); const mockedProvider = new FetchProvider( NamingServiceName.UNS, blockchainUrl, @@ -398,10 +411,13 @@ describe('Resolution', () => { Layer2: {network: 'polygon-mumbai'}, }, }, + ens: {network: 'goerli'}, }); uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; + ens = resolution.serviceMap[NamingServiceName.ENS].native as Ens; expect(uns.unsl1.url).toBe(`https://goerli.infura.io/v3/api-key`); expect(uns.unsl2.url).toBe(`https://polygon-mumbai.infura.io/v3/api-key`); + expect(ens.url).toBe(`https://goerli.infura.io/v3/api-key`); }); it('should get a valid resolution instance with .alchemy', async () => { @@ -422,7 +438,7 @@ describe('Resolution', () => { it('should throw on unspecified network', async () => { expect(() => Resolution.fromResolutionProvider({})).toThrowError( - '< Must specify network for uns or zns >', + '< Must specify network for uns, ens, or zns >', ); }); @@ -505,6 +521,11 @@ describe('Resolution', () => { }); describe('.ServiceName', () => { + it('checks ens service name', () => { + const serviceName = findNamingServiceName('domain.eth'); + expect(serviceName).toBe('ENS'); + }); + it('should resolve gundb chat id', async () => { const eyes = mockAsyncMethods(uns, { get: { @@ -578,1219 +599,1409 @@ describe('Resolution', () => { }, ); }); - }); - - describe('.Errors', () => { - it('checks Resolution#addr error #1', async () => { - const resolution = new Resolution(); - uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; - zns = resolution.serviceMap[NamingServiceName.ZNS].native as Zns; - const unsSpy = mockAsyncMethod(uns, 'record', async () => { - throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); - }); - const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', undefined); - await expectResolutionErrorCode( - resolution.addr('sdncdoncvdinvcsdncs.zil', 'ZIL'), - ResolutionErrorCode.UnregisteredDomain, - ); - expectSpyToBeCalled([unsSpy, znsSpy]); - }); - it('checks Resolution#addr error #2', async () => { - const resolution = new Resolution({ - sourceConfig: { - uns: { - locations: { - Layer1: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ), - network: 'goerli', - }, - Layer2: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), - network: 'polygon-mumbai', - }, - }, - }, - }, - }); - uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; - const spy = mockAsyncMethods(uns, { - get: {}, - }); - await expectResolutionErrorCode( - resolution.addr('sdncdoncvdinvcsdncs.crypto', 'ETH'), - ResolutionErrorCode.UnregisteredDomain, - ); - expectSpyToBeCalled(spy); - }); - - it('should throw error for unsupported domain', async () => { - await expectResolutionErrorCode( - resolution.addr('sdncdoncvdinvcsdncs.eth', 'ETH'), - ResolutionErrorCode.UnsupportedDomain, - ); - }); - - it('checks error for email on ryan-testing.zil', async () => { - const unsSpy = mockAsyncMethod(uns, 'record', async () => { - throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); - }); - const znsSpy = mockAsyncMethod(zns, 'allRecords', async () => ({ - 'crypto.ETH.address': '0xc101679df8e2d6092da6d7ca9bced5bfeeb5abd8', - 'crypto.ZIL.address': 'zil1k78e8zkh79lc47mrpcwqyhdrdkz7ptumk7ud90', - })); - await expectResolutionErrorCode( - resolution.email('ryan-testing.zil'), - ResolutionErrorCode.RecordNotFound, - ); - expectSpyToBeCalled([unsSpy, znsSpy]); - }); - describe('.Namehash errors', () => { - it('should be invalid domain', async () => { - const unsInvalidDomain = 'hello..crypto'; - const znsInvalidDomain = 'hello..zil'; - await expectResolutionErrorCode( - () => resolution.namehash(unsInvalidDomain, NamingServiceName.UNS), - ResolutionErrorCode.UnsupportedDomain, - ); + describe('.Errors', () => { + it('checks Resolution#addr error #1', async () => { + const resolution = new Resolution(); + uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; + zns = resolution.serviceMap[NamingServiceName.ZNS].native as Zns; + const unsSpy = mockAsyncMethod(uns, 'record', async () => { + throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); + }); + const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', undefined); await expectResolutionErrorCode( - () => resolution.namehash(znsInvalidDomain, NamingServiceName.UNS), - ResolutionErrorCode.UnsupportedDomain, + resolution.addr('sdncdoncvdinvcsdncs.zil', 'ZIL'), + ResolutionErrorCode.UnregisteredDomain, ); + expectSpyToBeCalled([unsSpy, znsSpy]); }); - }); - }); - - describe('.Records', () => { - describe('.DNS', () => { - skipItInLive('getting dns get', async () => { - const spies = mockAsyncMethods(uns, { - get: { - resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', - records: { - 'dns.ttl': '128', - 'dns.A': '["10.0.0.1","10.0.0.2"]', - 'dns.A.ttl': '90', - 'dns.AAAA': '["10.0.0.120"]', - }, - }, - }); - const dnsRecords = await resolution.dns('someTestDomain.crypto', [ - DnsRecordType.A, - DnsRecordType.AAAA, - ]); - expectSpyToBeCalled(spies); - expect(dnsRecords).toStrictEqual([ - {TTL: 90, data: '10.0.0.1', type: 'A'}, - {TTL: 90, data: '10.0.0.2', type: 'A'}, - {TTL: 128, data: '10.0.0.120', type: 'AAAA'}, - ]); - }); - - skipItInLive('getting dns records for subdomains', async () => { - const spies = mockAsyncMethods(uns, { - get: { - resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', - records: { - 'dns.ttl': '128', - 'dns.A': '["10.0.0.1","10.0.0.2"]', - 'dns.A.ttl': '90', - 'dns.AAAA': '["10.0.0.120"]', + it('checks Resolution#addr error #2', async () => { + const resolution = new Resolution({ + sourceConfig: { + uns: { + locations: { + Layer1: { + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + network: 'goerli', + }, + Layer2: { + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + network: 'polygon-mumbai', + }, + }, }, }, }); - const dnsRecords = await resolution.dns(SubdomainLayerTwo, [ - DnsRecordType.A, - DnsRecordType.AAAA, - ]); - expectSpyToBeCalled(spies); - expect(dnsRecords).toStrictEqual([ - {TTL: 90, data: '10.0.0.1', type: 'A'}, - {TTL: 90, data: '10.0.0.2', type: 'A'}, - {TTL: 128, data: '10.0.0.120', type: 'AAAA'}, - ]); - }); - - skipItInLive('should work with others records', async () => { - const spies = mockAsyncMethods(uns, { - get: { - resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', - records: { - 'dns.ttl': '128', - 'dns.A': '["10.0.0.1","10.0.0.2"]', - 'dns.A.ttl': '90', - 'dns.AAAA': '["10.0.0.120"]', - ['crypto.ETH.address']: - '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', - ['crypto.ADA.address']: - '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', - ['crypto.ARK.address']: - '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', - }, - }, + uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; + const spy = mockAsyncMethods(uns, { + get: {}, }); - const dnsRecords = await resolution.dns('someTestDomain.crypto', [ - DnsRecordType.A, - DnsRecordType.AAAA, - ]); - expectSpyToBeCalled(spies); - expect(dnsRecords).toStrictEqual([ - {TTL: 90, data: '10.0.0.1', type: 'A'}, - {TTL: 90, data: '10.0.0.2', type: 'A'}, - {TTL: 128, data: '10.0.0.120', type: 'AAAA'}, - ]); + await expectResolutionErrorCode( + resolution.addr('sdncdoncvdinvcsdncs.crypto', 'ETH'), + ResolutionErrorCode.UnregisteredDomain, + ); + expectSpyToBeCalled(spy); }); - }); - describe('.Metadata', () => { - it('checks return of email for testing.zil', async () => { + it('checks error for email on ryan-testing.zil', async () => { const unsSpy = mockAsyncMethod(uns, 'record', async () => { throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); }); const znsSpy = mockAsyncMethod(zns, 'allRecords', async () => ({ - 'ipfs.html.hash': 'QmefehFs5n8yQcGCVJnBMY3Hr6aMRHtsoniAhsM1KsHMSe', - 'ipfs.html.value': 'QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHu', - 'ipfs.redirect_domain.value': 'www.unstoppabledomains.com', - 'whois.email.value': 'derainberk@gmail.com', - 'whois.for_sale.value': 'true', - })); - const email = await resolution.email('testing.zil'); - expectSpyToBeCalled([unsSpy, znsSpy]); - expect(email).toBe('derainberk@gmail.com'); - }); - - it('should return IPFS value for subdomain', async () => { - mockAsyncMethod(uns, 'records', async () => ({ - 'ipfs.html.value': 'QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHu', - 'ipfs.redirect_domain.value': 'www.unstoppabledomains.com', + 'crypto.ETH.address': '0xc101679df8e2d6092da6d7ca9bced5bfeeb5abd8', + 'crypto.ZIL.address': 'zil1k78e8zkh79lc47mrpcwqyhdrdkz7ptumk7ud90', })); - const ipfsHash = await resolution.ipfsHash(SubdomainLayerTwo); - expect(ipfsHash).toBe( - 'QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHu', + await expectResolutionErrorCode( + resolution.email('ryan-testing.zil'), + ResolutionErrorCode.RecordNotFound, ); + expectSpyToBeCalled([unsSpy, znsSpy]); }); - }); - - describe('.Crypto', () => { - it(`domains "brad.crypto" and "Brad.crypto" should return the same results`, async () => { - const eyes = mockAsyncMethods(uns, { - get: { - resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', - records: { - ['crypto.ETH.address']: - '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', - }, - }, - }); - const capital = await resolution.addr('Brad.crypto', 'eth'); - const lower = await resolution.addr('brad.crypto', 'eth'); - expectSpyToBeCalled(eyes, 2); - expect(capital).toStrictEqual(lower); - }); - describe('.multichain', () => { - it('should work with usdt on different erc20', async () => { - const erc20Spy = mockAsyncMethod(uns, 'get', { - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - records: { - ['crypto.USDT.version.ERC20.address']: - '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - }, - }); - const erc20 = await resolution.multiChainAddr( - CryptoDomainWithUsdtMultiChainRecords, - 'usdt', - 'erc20', - ); - expect(erc20).toBe('0xe7474D07fD2FA286e7e0aa23cd107F8379085037'); - expect(erc20Spy).toBeCalled(); - }); - - it('should work with usdt tron chain', async () => { - const tronSpy = mockAsyncMethod(uns, 'get', { - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - records: { - ['crypto.USDT.version.TRON.address']: - 'TNemhXhpX7MwzZJa3oXvfCjo5pEeXrfN2h', - }, - }); - const tron = await resolution.multiChainAddr( - CryptoDomainWithUsdtMultiChainRecords, - 'usdt', - 'tron', - ); - expect(tron).toBe('TNemhXhpX7MwzZJa3oXvfCjo5pEeXrfN2h'); - expect(tronSpy).toBeCalled(); - }); - it('should work with usdt omni chain', async () => { - const omniSpy = mockAsyncMethod(uns, 'get', { - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - records: { - ['crypto.USDT.version.OMNI.address']: - '19o6LvAdCPkjLi83VsjrCsmvQZUirT4KXJ', - }, - }); - const omni = await resolution.multiChainAddr( - CryptoDomainWithUsdtMultiChainRecords, - 'usdt', - 'omni', + describe('.Namehash errors', () => { + it('should be invalid domain', async () => { + const unsInvalidDomain = 'hello..crypto'; + const ensInvalidDomain = 'hello..eth'; + const znsInvalidDomain = 'hello..zil'; + await expectResolutionErrorCode( + () => + resolution.namehash(unsInvalidDomain, NamingServiceName.UNS), + ResolutionErrorCode.UnsupportedDomain, ); - expect(omni).toBe('19o6LvAdCPkjLi83VsjrCsmvQZUirT4KXJ'); - expect(omniSpy).toBeCalled(); - }); - - it('should work with usdt eos chain', async () => { - const eosSpy = mockAsyncMethod(uns, 'get', { - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - records: { - ['crypto.USDT.version.EOS.address']: 'letsminesome', - }, - }); - const eos = await resolution.multiChainAddr( - CryptoDomainWithUsdtMultiChainRecords, - 'usdt', - 'eos', + await expectResolutionErrorCode( + () => + resolution.namehash(ensInvalidDomain, NamingServiceName.ENS), + ResolutionErrorCode.UnsupportedDomain, ); - expect(eosSpy).toBeCalled(); - expect(eos).toBe('letsminesome'); - }); - - it('should work with subdomain', async () => { - mockAsyncMethod(uns, 'get', { - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - records: { - ['crypto.AAVE.version.MATIC.address']: 'abcabc', - }, - }); - const matic = await resolution.multiChainAddr( - SubdomainLayerTwo, - 'aave', - 'matic', + await expectResolutionErrorCode( + () => + resolution.namehash(znsInvalidDomain, NamingServiceName.ZNS), + ResolutionErrorCode.UnsupportedDomain, ); - expect(matic).toBe('abcabc'); }); }); }); - describe('.Providers', () => { - it('should work with web3HttpProvider', async () => { - // web3-providers-http has problems with type definitions - const provider = new (Web3HttpProvider as any)( - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), - ); - const polygonProvider = new (Web3HttpProvider as any)( - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), - ); - // mock the send function with different implementations (each should call callback right away with different answers) - const eye = mockAsyncMethod( - provider, - 'send', - (payload: JsonRpcPayload, callback) => { - const result = caseMock( - payload.params?.[0], - RpcProviderTestCases, - ); - callback && - callback(null, { - jsonrpc: '2.0', - id: 1, - result, - }); - }, - ); - const resolution = Resolution.fromWeb3Version1Provider({ - uns: { - locations: { - Layer1: { - network: 'goerli', - provider: provider as unknown as Web3Version1Provider, - }, - Layer2: { - network: 'polygon-mumbai', - provider: polygonProvider as unknown as Web3Version1Provider, + describe('.Records', () => { + describe('.DNS', () => { + skipItInLive('getting dns get', async () => { + const spies = mockAsyncMethods(uns, { + get: { + resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', + records: { + 'dns.ttl': '128', + 'dns.A': '["10.0.0.1","10.0.0.2"]', + 'dns.A.ttl': '90', + 'dns.AAAA': '["10.0.0.120"]', }, }, - }, - }); - const uns = resolution.serviceMap['UNS'].native as Uns; - mockAsyncMethod(uns.unsl2.readerContract, 'call', () => - Promise.resolve([NullAddress, NullAddress, {}]), - ); - const ethAddress = await resolution.addr('brad.crypto', 'ETH'); - - // expect each mock to be called at least once. - expectSpyToBeCalled([eye]); - expect(ethAddress).toBe('0x8aaD44321A86b170879d7A244c1e8d360c99DdA8'); - }); - - it('should work with webSocketProvider', async () => { - // web3-providers-ws has problems with type definitions - const provider = new (Web3WsProvider as any)( - getUnsProtocolLinkFromEnv(ProviderProtocol.wss, 'UNSL1'), - ); - const polygonProvider = new (Web3WsProvider as any)( - getUnsProtocolLinkFromEnv(ProviderProtocol.wss, 'UNSL2'), - ); - const eye = mockAsyncMethod(provider, 'send', (payload, callback) => { - const result = caseMock(payload.params?.[0], RpcProviderTestCases); - callback(null, { - jsonrpc: '2.0', - id: 1, - result, }); + const dnsRecords = await resolution.dns('someTestDomain.crypto', [ + DnsRecordType.A, + DnsRecordType.AAAA, + ]); + expectSpyToBeCalled(spies); + expect(dnsRecords).toStrictEqual([ + {TTL: 90, data: '10.0.0.1', type: 'A'}, + {TTL: 90, data: '10.0.0.2', type: 'A'}, + {TTL: 128, data: '10.0.0.120', type: 'AAAA'}, + ]); }); - const resolution = Resolution.fromWeb3Version1Provider({ - uns: { - locations: { - Layer1: { - network: 'goerli', - provider: provider as unknown as Web3Version1Provider, - }, - Layer2: { - network: 'polygon-mumbai', - provider: polygonProvider as unknown as Web3Version1Provider, + skipItInLive('should work with others records', async () => { + const spies = mockAsyncMethods(uns, { + get: { + resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', + records: { + 'dns.ttl': '128', + 'dns.A': '["10.0.0.1","10.0.0.2"]', + 'dns.A.ttl': '90', + 'dns.AAAA': '["10.0.0.120"]', + ['crypto.ETH.address']: + '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', + ['crypto.ADA.address']: + '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', + ['crypto.ARK.address']: + '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', }, }, - }, - }); - const uns = resolution.serviceMap['UNS'].native as Uns; - mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => - Promise.resolve([NullAddress, NullAddress, {}]), - ); - const ethAddress = await resolution.addr('brad.crypto', 'ETH'); - provider.disconnect(1000, 'end of test'); - expectSpyToBeCalled([eye]); - expect(ethAddress).toBe('0x8aaD44321A86b170879d7A244c1e8d360c99DdA8'); - }); - - it('should work for ethers jsonrpc provider', async () => { - const provider = new JsonRpcProvider( - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), - 'goerli', - ); - const polygonProvider = new JsonRpcProvider( - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), - 'maticmum', - ); - const resolution = Resolution.fromEthersProvider({ - uns: { - locations: { - Layer1: {network: 'goerli', provider}, - Layer2: {network: 'polygon-mumbai', provider: polygonProvider}, - }, - }, - }); - const uns = resolution.serviceMap['UNS'].native as Uns; - mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => - Promise.resolve([NullAddress, NullAddress, {}]), - ); - const eye = mockAsyncMethod(provider, 'call', (params) => - Promise.resolve(caseMock(params, RpcProviderTestCases)), - ); - const ethAddress = await resolution.addr('brad.crypto', 'ETH'); - expectSpyToBeCalled([eye]); - expect(ethAddress).toBe('0x8aaD44321A86b170879d7A244c1e8d360c99DdA8'); - }); - - it('should work with ethers default provider', async () => { - const provider = new InfuraProvider( - 'mainnet', - '213fff28936343858ca9c5115eff1419', - ); - const polygonProvider = new InfuraProvider( - 'maticmum', - 'c4bb906ed6904c42b19c95825fe55f39', - ); - - const eye = mockAsyncMethod(provider, 'call', (params) => - Promise.resolve(caseMock(params, RpcProviderTestCases)), - ); - const resolution = Resolution.fromEthersProvider({ - uns: { - locations: { - Layer1: {network: 'goerli', provider}, - Layer2: {network: 'polygon-mumbai', provider: polygonProvider}, - }, - }, + }); + const dnsRecords = await resolution.dns('someTestDomain.crypto', [ + DnsRecordType.A, + DnsRecordType.AAAA, + ]); + expectSpyToBeCalled(spies); + expect(dnsRecords).toStrictEqual([ + {TTL: 90, data: '10.0.0.1', type: 'A'}, + {TTL: 90, data: '10.0.0.2', type: 'A'}, + {TTL: 128, data: '10.0.0.120', type: 'AAAA'}, + ]); }); - const uns = resolution.serviceMap['UNS'].native as Uns; - mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => - Promise.resolve([NullAddress, NullAddress, {}]), - ); - const ethAddress = await resolution.addr('brad.crypto', 'eth'); - expectSpyToBeCalled([eye]); - expect(ethAddress).toBe('0x8aaD44321A86b170879d7A244c1e8d360c99DdA8'); }); - }); - describe('.Dweb', () => { - describe('.IPFS', () => { - it('checks return of IPFS hash for brad.zil', async () => { - const unsSpy = mockAsyncMethod(uns, 'records', async () => { + describe('.Metadata', () => { + it('checks return of email for testing.zil', async () => { + const unsSpy = mockAsyncMethod(uns, 'record', async () => { throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); }); const znsSpy = mockAsyncMethod(zns, 'allRecords', async () => ({ + 'ipfs.html.hash': + 'QmefehFs5n8yQcGCVJnBMY3Hr6aMRHtsoniAhsM1KsHMSe', 'ipfs.html.value': - 'QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHuK', - 'whois.email.value': 'derainberk@gmail.com', - 'crypto.BCH.address': - 'qrq4sk49ayvepqz7j7ep8x4km2qp8lauvcnzhveyu6', - 'crypto.BTC.address': '1EVt92qQnaLDcmVFtHivRJaunG2mf2C3mB', - 'crypto.ETH.address': - '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', - 'crypto.LTC.address': 'LetmswTW3b7dgJ46mXuiXMUY17XbK29UmL', - 'crypto.XMR.address': - '447d7TVFkoQ57k3jm3wGKoEAkfEym59mK96Xw5yWamDNFGaLKW5wL2qK5RMTDKGSvYfQYVN7dLSrLdkwtKH3hwbSCQCu26d', - 'crypto.ZEC.address': 't1h7ttmQvWCSH1wfrcmvT4mZJfGw2DgCSqV', - 'crypto.ZIL.address': - 'zil1yu5u4hegy9v3xgluweg4en54zm8f8auwxu0xxj', - 'crypto.DASH.address': 'XnixreEBqFuSLnDSLNbfqMH1GsZk7cgW4j', + 'QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHu', 'ipfs.redirect_domain.value': 'www.unstoppabledomains.com', - 'crypto.USDT.version.ERC20.address': - '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + 'whois.email.value': 'derainberk@gmail.com', + 'whois.for_sale.value': 'true', })); - const hash = await resolution.ipfsHash('testing.zil'); + const email = await resolution.email('testing.zil'); expectSpyToBeCalled([unsSpy, znsSpy]); - expect(hash).toBe('QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHuK'); + expect(email).toBe('derainberk@gmail.com'); }); }); - describe('.Gundb', () => { - it('should resolve gundb chat id', async () => { + describe('.Crypto', () => { + it(`domains "brad.crypto" and "Brad.crypto" should return the same results`, async () => { const eyes = mockAsyncMethods(uns, { get: { - resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + resolver: '0xBD5F5ec7ed5f19b53726344540296C02584A5237', records: { - ['gundb.username.value']: - '0x47992daf742acc24082842752fdc9c875c87c56864fee59d8b779a91933b159e48961566eec6bd6ce3ea2441c6cb4f112d0eb8e8855cc9cf7647f0d9c82f00831c', + ['crypto.ETH.address']: + '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', }, }, }); - const gundb = await resolution.chatId('homecakes.crypto'); - expectSpyToBeCalled(eyes); - expect(gundb).toBe( - '0x47992daf742acc24082842752fdc9c875c87c56864fee59d8b779a91933b159e48961566eec6bd6ce3ea2441c6cb4f112d0eb8e8855cc9cf7647f0d9c82f00831c', - ); + const capital = await resolution.addr('Brad.crypto', 'eth'); + const lower = await resolution.addr('brad.crypto', 'eth'); + expectSpyToBeCalled(eyes, 2); + expect(capital).toStrictEqual(lower); }); - }); - }); + describe('.multichain', () => { + it('should work with usdt on different erc20', async () => { + const erc20Spy = mockAsyncMethod(uns, 'get', { + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + records: { + ['crypto.USDT.version.ERC20.address']: + '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + }, + }); + const erc20 = await resolution.multiChainAddr( + CryptoDomainWithUsdtMultiChainRecords, + 'usdt', + 'erc20', + ); + expect(erc20).toBe('0xe7474D07fD2FA286e7e0aa23cd107F8379085037'); + expect(erc20Spy).toBeCalled(); + }); - describe('.Verifications', () => { - describe('.Twitter', () => { - it('should return verified twitter handle', async () => { - const readerSpies = mockAsyncMethods(uns, { - get: { + it('should work with usdt tron chain', async () => { + const tronSpy = mockAsyncMethod(uns, 'get', { resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - owner: '0x499dd6d875787869670900a2130223d85d4f6aa7', + owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', records: { - ['validation.social.twitter.username']: - '0x01882395ce631866b76f43535843451444ef4a8ff44db0a9432d5d00658a510512c7519a87c78ba9cad7553e26262ada55c254434a1a3784cd98d06fb4946cfb1b', - ['social.twitter.username']: 'Marlene12Bob', + ['crypto.USDT.version.TRON.address']: + 'TNemhXhpX7MwzZJa3oXvfCjo5pEeXrfN2h', }, - }, + }); + const tron = await resolution.multiChainAddr( + CryptoDomainWithUsdtMultiChainRecords, + 'usdt', + 'tron', + ); + expect(tron).toBe('TNemhXhpX7MwzZJa3oXvfCjo5pEeXrfN2h'); + expect(tronSpy).toBeCalled(); + }); + + it('should work with usdt omni chain', async () => { + const omniSpy = mockAsyncMethod(uns, 'get', { + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + records: { + ['crypto.USDT.version.OMNI.address']: + '19o6LvAdCPkjLi83VsjrCsmvQZUirT4KXJ', + }, + }); + const omni = await resolution.multiChainAddr( + CryptoDomainWithUsdtMultiChainRecords, + 'usdt', + 'omni', + ); + expect(omni).toBe('19o6LvAdCPkjLi83VsjrCsmvQZUirT4KXJ'); + expect(omniSpy).toBeCalled(); + }); + + it('should work with usdt eos chain', async () => { + const eosSpy = mockAsyncMethod(uns, 'get', { + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + owner: '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + records: { + ['crypto.USDT.version.EOS.address']: 'letsminesome', + }, + }); + const eos = await resolution.multiChainAddr( + CryptoDomainWithUsdtMultiChainRecords, + 'usdt', + 'eos', + ); + expect(eosSpy).toBeCalled(); + expect(eos).toBe('letsminesome'); }); - const twitterHandle = await resolution.twitter( - CryptoDomainWithTwitterVerification, - ); - expectSpyToBeCalled(readerSpies); - expect(twitterHandle).toBe('Marlene12Bob'); }); + }); - it('should throw unsupported method', async () => { - const resolution = new Resolution(); - const unsSpy = mockAsyncMethod( - resolution.serviceMap.UNS.native, - 'twitter', - async () => { - throw new ResolutionError( - ResolutionErrorCode.UnregisteredDomain, + describe('.Providers', () => { + it('should work with web3HttpProvider', async () => { + // web3-providers-http has problems with type definitions + const provider = new (Web3HttpProvider as any)( + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + ); + const polygonProvider = new (Web3HttpProvider as any)( + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + ); + // mock the send function with different implementations (each should call callback right away with different answers) + const eye = mockAsyncMethod( + provider, + 'send', + (payload: JsonRpcPayload, callback) => { + const result = caseMock( + payload.params?.[0], + RpcProviderTestCases, ); + callback && + callback(null, { + jsonrpc: '2.0', + id: 1, + result, + }); }, ); - await expectResolutionErrorCode( - resolution.twitter('ryan.zil'), - ResolutionErrorCode.UnsupportedMethod, - ); - expectSpyToBeCalled([unsSpy]); + const resolution = Resolution.fromWeb3Version1Provider({ + uns: { + locations: { + Layer1: { + network: 'goerli', + provider: provider as unknown as Web3Version1Provider, + }, + Layer2: { + network: 'polygon-mumbai', + provider: + polygonProvider as unknown as Web3Version1Provider, + }, + }, + }, + }); + const uns = resolution.serviceMap['UNS'].native as Uns; + mockAsyncMethod(uns.unsl2.readerContract, 'call', () => + Promise.resolve([NullAddress, NullAddress, {}]), + ); + const ethAddress = await resolution.addr('brad.crypto', 'ETH'); + + // expect each mock to be called at least once. + expectSpyToBeCalled([eye]); + expect(ethAddress).toBe( + '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + ); + }); + + it('should work with webSocketProvider', async () => { + // web3-providers-ws has problems with type definitions + const provider = new (Web3WsProvider as any)( + getProtocolLinkFromEnv(ProviderProtocol.wss, 'UNSL1'), + ); + const polygonProvider = new (Web3WsProvider as any)( + getProtocolLinkFromEnv(ProviderProtocol.wss, 'UNSL2'), + ); + const eye = mockAsyncMethod( + provider, + 'send', + (payload, callback) => { + const result = caseMock( + payload.params?.[0], + RpcProviderTestCases, + ); + callback(null, { + jsonrpc: '2.0', + id: 1, + result, + }); + }, + ); + const resolution = Resolution.fromWeb3Version1Provider({ + uns: { + locations: { + Layer1: { + network: 'goerli', + provider: provider as unknown as Web3Version1Provider, + }, + Layer2: { + network: 'polygon-mumbai', + provider: + polygonProvider as unknown as Web3Version1Provider, + }, + }, + }, + }); + const uns = resolution.serviceMap['UNS'].native as Uns; + mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => + Promise.resolve([NullAddress, NullAddress, {}]), + ); + const ethAddress = await resolution.addr('brad.crypto', 'ETH'); + provider.disconnect(1000, 'end of test'); + expectSpyToBeCalled([eye]); + expect(ethAddress).toBe( + '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + ); + }); + + it('should work for ethers jsonrpc provider', async () => { + const provider = new JsonRpcProvider( + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + 'goerli', + ); + const polygonProvider = new JsonRpcProvider( + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + 'maticmum', + ); + const resolution = Resolution.fromEthersProvider({ + uns: { + locations: { + Layer1: {network: 'goerli', provider}, + Layer2: { + network: 'polygon-mumbai', + provider: polygonProvider, + }, + }, + }, + }); + const uns = resolution.serviceMap['UNS'].native as Uns; + mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => + Promise.resolve([NullAddress, NullAddress, {}]), + ); + const eye = mockAsyncMethod(provider, 'call', (params) => + Promise.resolve(caseMock(params, RpcProviderTestCases)), + ); + const ethAddress = await resolution.addr('brad.crypto', 'ETH'); + expectSpyToBeCalled([eye]); + expect(ethAddress).toBe( + '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + ); + }); + + it('should work with webSocketProvider', async () => { + // web3-providers-ws has problems with type definitions + const provider = new (Web3WsProvider as any)( + getProtocolLinkFromEnv(ProviderProtocol.wss, 'UNSL1'), + ); + const polygonProvider = new (Web3WsProvider as any)( + getProtocolLinkFromEnv(ProviderProtocol.wss, 'UNSL2'), + ); + const eye = mockAsyncMethod( + provider, + 'send', + (payload, callback) => { + const result = caseMock( + payload.params?.[0], + RpcProviderTestCases, + ); + callback(null, { + jsonrpc: '2.0', + id: 1, + result, + }); + }, + ); + + const resolution = Resolution.fromWeb3Version1Provider({ + uns: { + locations: { + Layer1: { + network: 'goerli', + provider: provider as unknown as Web3Version1Provider, + }, + Layer2: { + network: 'polygon-mumbai', + provider: + polygonProvider as unknown as Web3Version1Provider, + }, + }, + }, + }); + const uns = resolution.serviceMap['UNS'].native as Uns; + mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => + Promise.resolve([NullAddress, NullAddress, {}]), + ); + const ethAddress = await resolution.addr('brad.crypto', 'ETH'); + provider.disconnect(1000, 'end of test'); + expectSpyToBeCalled([eye]); + expect(ethAddress).toBe( + '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + ); + }); + + it('should work for ethers jsonrpc provider', async () => { + const provider = new JsonRpcProvider( + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + 'goerli', + ); + const polygonProvider = new JsonRpcProvider( + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + 'maticmum', + ); + const resolution = Resolution.fromEthersProvider({ + uns: { + locations: { + Layer1: {network: 'goerli', provider}, + Layer2: { + network: 'polygon-mumbai', + provider: polygonProvider, + }, + }, + }, + }); + const uns = resolution.serviceMap['UNS'].native as Uns; + mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => + Promise.resolve([NullAddress, NullAddress, {}]), + ); + const eye = mockAsyncMethod(provider, 'call', (params) => + Promise.resolve(caseMock(params, RpcProviderTestCases)), + ); + const ethAddress = await resolution.addr('brad.crypto', 'ETH'); + expectSpyToBeCalled([eye]); + expect(ethAddress).toBe( + '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + ); + }); + + it('should work with ethers default provider', async () => { + const provider = new InfuraProvider( + 'goerli', + '213fff28936343858ca9c5115eff1419', + ); + const polygonProvider = new InfuraProvider( + 'maticmum', + 'a32aa2ace9704ee9a1a9906418bcabe5', + ); + + const eye = mockAsyncMethod(provider, 'call', (params) => + Promise.resolve(caseMock(params, RpcProviderTestCases)), + ); + const resolution = Resolution.fromEthersProvider({ + uns: { + locations: { + Layer1: {network: 'goerli', provider}, + Layer2: { + network: 'polygon-mumbai', + provider: polygonProvider, + }, + }, + }, + }); + const uns = resolution.serviceMap['UNS'].native as Uns; + mockAsyncMethod(uns.unsl2.readerContract, 'call', (params) => + Promise.resolve([NullAddress, NullAddress, {}]), + ); + const ethAddress = await resolution.addr('brad.crypto', 'eth'); + expectSpyToBeCalled([eye]); + expect(ethAddress).toBe( + '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + ); }); }); - }); - }); - }); - describe('.registryAddress', () => { - it('should return zns mainnet registry address', async () => { - const unsSpy = mockAsyncMethod( - resolution.serviceMap.UNS.native, - 'registryAddress', - async () => { - throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); - }, - ); - const registryAddress = await resolution.registryAddress('testi.zil'); - expect(registryAddress).toBe( - 'zil1hyj6m5w4atcn7s806s69r0uh5g4t84e8gp6nps', - ); - expectSpyToBeCalled([unsSpy]); - }); + describe('.Dweb', () => { + describe('.IPFS', () => { + it('checks return of IPFS hash for brad.zil', async () => { + const unsSpy = mockAsyncMethod(uns, 'records', async () => { + throw new ResolutionError( + ResolutionErrorCode.UnregisteredDomain, + ); + }); + const znsAllRecordsMock = mockAsyncMethods(zns, { + resolver: 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', + getResolverRecords: { + 'crypto.ZIL.address': + 'zil1yu5u4hegy9v3xgluweg4en54zm8f8auwxu0xxj', + }, + }); + const znsRecords = await resolution.allRecords('brad.zil'); + expectSpyToBeCalled([unsSpy, ...znsAllRecordsMock]); + expect(znsRecords['crypto.ZIL.address']).toEqual( + 'zil1yu5u4hegy9v3xgluweg4en54zm8f8auwxu0xxj', + ); + }); + }); - it('should return cns mainnet registry address #1', async () => { - const spies = mockAsyncMethods(uns, { - registryAddress: UnsConfig.networks[5].contracts.CNSRegistry.address, - }); - const registryAddress = await resolution.registryAddress( - 'udtestdev-crewe.crypto', - ); - expectSpyToBeCalled(spies); - expect(registryAddress).toBe( - UnsConfig.networks[5].contracts.CNSRegistry.address, - ); - }); + describe('.Dweb', () => { + describe('.IPFS', () => { + it('checks return of IPFS hash for brad.zil', async () => { + const unsSpy = mockAsyncMethod(uns, 'records', async () => { + throw new ResolutionError( + ResolutionErrorCode.UnregisteredDomain, + ); + }); + const znsSpy = mockAsyncMethod(zns, 'allRecords', async () => ({ + 'ipfs.html.value': + 'QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHuK', + 'whois.email.value': 'derainberk@gmail.com', + 'crypto.BCH.address': + 'qrq4sk49ayvepqz7j7ep8x4km2qp8lauvcnzhveyu6', + 'crypto.BTC.address': '1EVt92qQnaLDcmVFtHivRJaunG2mf2C3mB', + 'crypto.ETH.address': + '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', + 'crypto.LTC.address': 'LetmswTW3b7dgJ46mXuiXMUY17XbK29UmL', + 'crypto.XMR.address': + '447d7TVFkoQ57k3jm3wGKoEAkfEym59mK96Xw5yWamDNFGaLKW5wL2qK5RMTDKGSvYfQYVN7dLSrLdkwtKH3hwbSCQCu26d', + 'crypto.ZEC.address': 't1h7ttmQvWCSH1wfrcmvT4mZJfGw2DgCSqV', + 'crypto.ZIL.address': + 'zil1yu5u4hegy9v3xgluweg4en54zm8f8auwxu0xxj', + 'crypto.DASH.address': 'XnixreEBqFuSLnDSLNbfqMH1GsZk7cgW4j', + 'ipfs.redirect_domain.value': 'www.unstoppabledomains.com', + 'crypto.USDT.version.ERC20.address': + '0x8aaD44321A86b170879d7A244c1e8d360c99DdA8', + })); + const hash = await resolution.ipfsHash('testing.zil'); + expectSpyToBeCalled([unsSpy, znsSpy]); + expect(hash).toBe( + 'QmVaAtQbi3EtsfpKoLzALm6vXphdi2KjMgxEDKeGg6wHuK', + ); + }); + }); - it('should return uns mainnet registry address', async () => { - const spies = mockAsyncMethods(uns, { - registryAddress: UnsConfig.networks[5].contracts.UNSRegistry.address, - }); - const registryAddress = await resolution.registryAddress( - 'udtestdev-check.wallet', - ); - expectSpyToBeCalled(spies); - expect(registryAddress).toBe( - UnsConfig.networks[5].contracts.UNSRegistry.address, - ); - }); - it('should return uns l2 mainnet registry address if domain exists on both', async () => { - const spies = mockAsyncMethods(uns.unsl1, { - registryAddress: UnsConfig.networks[5].contracts.UNSRegistry.address, - }); - const spies2 = mockAsyncMethods(uns.unsl2, { - registryAddress: - UnsConfig.networks[80001].contracts.UNSRegistry.address, + describe('.Gundb', () => { + it('should resolve gundb chat id', async () => { + const eyes = mockAsyncMethods(uns, { + get: { + resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + records: { + ['gundb.username.value']: + '0x47992daf742acc24082842752fdc9c875c87c56864fee59d8b779a91933b159e48961566eec6bd6ce3ea2441c6cb4f112d0eb8e8855cc9cf7647f0d9c82f00831c', + }, + }, + }); + const gundb = await resolution.chatId('homecakes.crypto'); + expectSpyToBeCalled(eyes); + expect(gundb).toBe( + '0x47992daf742acc24082842752fdc9c875c87c56864fee59d8b779a91933b159e48961566eec6bd6ce3ea2441c6cb4f112d0eb8e8855cc9cf7647f0d9c82f00831c', + ); + }); + }); + }); + + describe('.Verifications', () => { + describe('.Twitter', () => { + it('should return verified twitter handle', async () => { + const readerSpies = mockAsyncMethods(uns, { + get: { + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + owner: '0x499dd6d875787869670900a2130223d85d4f6aa7', + records: { + ['validation.social.twitter.username']: + '0x01882395ce631866b76f43535843451444ef4a8ff44db0a9432d5d00658a510512c7519a87c78ba9cad7553e26262ada55c254434a1a3784cd98d06fb4946cfb1b', + ['social.twitter.username']: 'Marlene12Bob', + }, + }, + }); + const twitterHandle = await resolution.twitter( + CryptoDomainWithTwitterVerification, + ); + expectSpyToBeCalled(readerSpies); + expect(twitterHandle).toBe('Marlene12Bob'); + }); + + it('should throw unsupported method', async () => { + const resolution = new Resolution(); + const unsSpy = mockAsyncMethod( + resolution.serviceMap.UNS.native, + 'twitter', + async () => { + throw new ResolutionError( + ResolutionErrorCode.UnregisteredDomain, + ); + }, + ); + await expectResolutionErrorCode( + resolution.twitter('ryan.zil'), + ResolutionErrorCode.UnsupportedMethod, + ); + expectSpyToBeCalled([unsSpy]); + }); + }); + }); + }); }); - const registryAddress = await resolution.registryAddress( - WalletDomainOnBothLayers, - ); - expectSpyToBeCalled(spies); - expectSpyToBeCalled(spies2); - expect(registryAddress).toBe( - UnsConfig.networks[80001].contracts.UNSRegistry.address, - ); - }); - }); - describe('.records', () => { - it('returns l2 records if domain exists on both', async () => { - const eyes = mockAsyncMethods(uns.unsl1, { - get: { - owner: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', - resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', - records: { - 'crypto.ADA.address': 'blahblah-dont-care-about-these-records', - 'crypto.ETH.address': 'blahblah-dont-care-about-these-records', - }, - }, + describe('.registryAddress', () => { + it('should return zns mainnet registry address', async () => { + const unsSpy = mockAsyncMethod( + resolution.serviceMap.UNS.native, + 'registryAddress', + async () => { + throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); + }, + ); + const registryAddress = await resolution.registryAddress('testi.zil'); + expect(registryAddress).toBe( + 'zil1hyj6m5w4atcn7s806s69r0uh5g4t84e8gp6nps', + ); + expectSpyToBeCalled([unsSpy]); + }); + + it('should return cns mainnet registry address #1', async () => { + const spies = mockAsyncMethods(uns, { + registryAddress: + UnsConfig.networks[5].contracts.CNSRegistry.address, + }); + const registryAddress = await resolution.registryAddress( + 'udtestdev-crewe.crypto', + ); + expectSpyToBeCalled(spies); + expect(registryAddress).toBe( + UnsConfig.networks[5].contracts.CNSRegistry.address, + ); + }); + + it('should return uns mainnet registry address', async () => { + const spies = mockAsyncMethods(uns, { + registryAddress: + UnsConfig.networks[5].contracts.UNSRegistry.address, + }); + const registryAddress = await resolution.registryAddress( + 'udtestdev-check.wallet', + ); + expectSpyToBeCalled(spies); + expect(registryAddress).toBe( + UnsConfig.networks[5].contracts.UNSRegistry.address, + ); + }); + it('should return uns l2 mainnet registry address if domain exists on both', async () => { + const spies = mockAsyncMethods(uns.unsl1, { + registryAddress: + UnsConfig.networks[5].contracts.UNSRegistry.address, + }); + const spies2 = mockAsyncMethods(uns.unsl2, { + registryAddress: + UnsConfig.networks[80001].contracts.UNSRegistry.address, + }); + const registryAddress = await resolution.registryAddress( + WalletDomainOnBothLayers, + ); + expectSpyToBeCalled(spies); + expectSpyToBeCalled(spies2); + expect(registryAddress).toBe( + UnsConfig.networks[80001].contracts.UNSRegistry.address, + ); + }); }); - const eyes2 = mockAsyncMethods(uns.unsl2, { - get: { - owner: '0x6EC0DEeD30605Bcd19342f3c30201DB263291589', - resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', - records: { + + describe('.records', () => { + it('returns l2 records if domain exists on both', async () => { + const eyes = mockAsyncMethods(uns.unsl1, { + get: { + owner: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + records: { + 'crypto.ADA.address': 'blahblah-dont-care-about-these-records', + 'crypto.ETH.address': 'blahblah-dont-care-about-these-records', + }, + }, + }); + const eyes2 = mockAsyncMethods(uns.unsl2, { + get: { + owner: '0x6EC0DEeD30605Bcd19342f3c30201DB263291589', + resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + records: { + 'crypto.ADA.address': + 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', + 'crypto.ETH.address': + '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + }, + }, + }); + expect( + await resolution.records(CryptoDomainWithAllRecords, [ + 'crypto.ADA.address', + 'crypto.ETH.address', + ]), + ).toEqual({ 'crypto.ADA.address': 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', 'crypto.ETH.address': '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - }, - }, - }); - expect( - await resolution.records(CryptoDomainWithAllRecords, [ - 'crypto.ADA.address', - 'crypto.ETH.address', - ]), - ).toEqual({ - 'crypto.ADA.address': - 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', - 'crypto.ETH.address': '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + }); + expectSpyToBeCalled([...eyes, ...eyes2]); + }); + + it('should return uns mainnet registry address', async () => { + const spies = mockAsyncMethods(uns, { + registryAddress: + UnsConfig.networks[5].contracts.UNSRegistry.address, + }); + const registryAddress = await resolution.registryAddress( + 'udtestdev-check.wallet', + ); + expectSpyToBeCalled(spies); + expect(registryAddress).toBe( + UnsConfig.networks[5].contracts.UNSRegistry.address, + ); + }); + it('should return uns l2 mainnet registry address if domain exists on both', async () => { + const spies = mockAsyncMethods(uns.unsl1, { + registryAddress: + UnsConfig.networks[5].contracts.UNSRegistry.address, + }); + const spies2 = mockAsyncMethods(uns.unsl2, { + registryAddress: + UnsConfig.networks[80001].contracts.UNSRegistry.address, + }); + const registryAddress = await resolution.registryAddress( + WalletDomainOnBothLayers, + ); + expectSpyToBeCalled(spies); + expectSpyToBeCalled(spies2); + expect(registryAddress).toBe( + UnsConfig.networks[80001].contracts.UNSRegistry.address, + ); + }); }); - expectSpyToBeCalled([...eyes, ...eyes2]); - }); - it('works', async () => { - const eyes = mockAsyncMethods(uns, { - get: { - owner: '0x6EC0DEeD30605Bcd19342f3c30201DB263291589', - resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', - records: { + + describe('.records', () => { + it('returns l2 records if domain exists on both', async () => { + const eyes = mockAsyncMethods(uns.unsl1, { + get: { + owner: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + records: { + 'crypto.ADA.address': 'blahblah-dont-care-about-these-records', + 'crypto.ETH.address': 'blahblah-dont-care-about-these-records', + }, + }, + }); + const eyes2 = mockAsyncMethods(uns.unsl2, { + get: { + owner: '0x6EC0DEeD30605Bcd19342f3c30201DB263291589', + resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + records: { + 'crypto.ADA.address': + 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', + 'crypto.ETH.address': + '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + }, + }, + }); + expect( + await resolution.records(CryptoDomainWithAllRecords, [ + 'crypto.ADA.address', + 'crypto.ETH.address', + ]), + ).toEqual({ 'crypto.ADA.address': 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', 'crypto.ETH.address': '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', - }, - }, - }); - expect( - await resolution.records(CryptoDomainWithAllRecords, [ - 'crypto.ADA.address', - 'crypto.ETH.address', - ]), - ).toEqual({ - 'crypto.ADA.address': - 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', - 'crypto.ETH.address': '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + }); + expectSpyToBeCalled([...eyes, ...eyes2]); + }); + it('works', async () => { + const eyes = mockAsyncMethods(uns, { + get: { + owner: '0x6EC0DEeD30605Bcd19342f3c30201DB263291589', + resolver: '0x878bC2f3f717766ab69C0A5f9A6144931E61AEd3', + records: { + 'crypto.ADA.address': + 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', + 'crypto.ETH.address': + '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + }, + }, + }); + expect( + await resolution.records(CryptoDomainWithAllRecords, [ + 'crypto.ADA.address', + 'crypto.ETH.address', + ]), + ).toEqual({ + 'crypto.ADA.address': + 'DdzFFzCqrhssjmxkChyAHE9MdHJkEc4zsZe7jgum6RtGzKLkUanN1kPZ1ipVPBLwVq2TWrhmPsAvArcr47Pp1VNKmZTh6jv8ctAFVCkj', + 'crypto.ETH.address': '0xe7474D07fD2FA286e7e0aa23cd107F8379085037', + }); + expectSpyToBeCalled([...eyes]); + }); }); - expectSpyToBeCalled([...eyes]); - }); - }); - describe('.isRegistered', () => { - it('should return true', async () => { - const spies = mockAsyncMethods(uns, { - get: { - owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - records: { - ['ipfs.html.value']: - 'QmQ38zzQHVfqMoLWq2VeiMLHHYki9XktzXxLYTWXt8cydu', - }, - }, - }); - const isRegistered = await resolution.isRegistered('brad.crypto'); - expectSpyToBeCalled(spies); - expect(isRegistered).toBe(true); - }); + describe('.isRegistered', () => { + it('should return true', async () => { + const spies = mockAsyncMethods(uns, { + get: { + owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + records: { + ['ipfs.html.value']: + 'QmQ38zzQHVfqMoLWq2VeiMLHHYki9XktzXxLYTWXt8cydu', + }, + }, + }); + const isRegistered = await resolution.isRegistered('brad.crypto'); + expectSpyToBeCalled(spies); + expect(isRegistered).toBe(true); + }); - it('should return false', async () => { - const spies = mockAsyncMethods(uns, { - get: { - owner: '', - resolver: '', - records: {}, - }, - }); - const isRegistered = await resolution.isRegistered( - 'thisdomainisdefinitelynotregistered123.crypto', - ); - expectSpyToBeCalled(spies); - expect(isRegistered).toBe(false); - }); + it('should return false', async () => { + const spies = mockAsyncMethods(uns, { + get: { + owner: '', + resolver: '', + records: {}, + }, + }); + const isRegistered = await resolution.isRegistered( + 'thisdomainisdefinitelynotregistered123.crypto', + ); + expectSpyToBeCalled(spies); + expect(isRegistered).toBe(false); + }); - it('should return true for a .zil domain', async () => { - const unsSpy = mockAsyncMethod(uns, 'isRegistered', async () => { - throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); - }); - const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', [ - 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', - ]); - const isRegistered = await resolution.isRegistered('testing.zil'); - expectSpyToBeCalled([unsSpy, znsSpy]); - expect(isRegistered).toBe(true); - }); + it('should return true for a .zil domain', async () => { + const unsSpy = mockAsyncMethod(uns, 'isRegistered', async () => { + throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); + }); + const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', [ + 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', + ]); + const isRegistered = await resolution.isRegistered('testing.zil'); + expectSpyToBeCalled([unsSpy, znsSpy]); + expect(isRegistered).toBe(true); + }); - skipItInLive('should return false for a .zil domain', async () => { - const unsSpy = mockAsyncMethod(uns, 'isRegistered', async () => { - throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); - }); - const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', ['']); - const isRegistered = await resolution.isRegistered( - 'thisdomainisdefinitelynotregistered123.zil', - ); - expectSpyToBeCalled([unsSpy, znsSpy]); - expect(isRegistered).toBe(false); - }); + skipItInLive('should return false for a .zil domain', async () => { + const unsSpy = mockAsyncMethod(uns, 'isRegistered', async () => { + throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); + }); + const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', ['']); + const isRegistered = await resolution.isRegistered( + 'thisdomainisdefinitelynotregistered123.zil', + ); + expectSpyToBeCalled([unsSpy, znsSpy]); + expect(isRegistered).toBe(false); + }); - it('should return true if registered on l2 but not l1', async () => { - const spies = mockAsyncMethods(uns.unsl1, { - get: { - owner: '', - resolver: '', - records: {}, - location: UnsLocation.Layer1, - }, - }); - const spies2 = mockAsyncMethods(uns.unsl2, { - get: { - owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - records: {}, - location: UnsLocation.Layer2, - }, - }); - const isRegistered = await resolution.isRegistered( - WalletDomainLayerTwoWithAllRecords, - ); - expectSpyToBeCalled(spies); - expectSpyToBeCalled(spies2); - expect(isRegistered).toBe(true); - }); + it('should return true if registered on l2 but not l1', async () => { + const spies = mockAsyncMethods(uns.unsl1, { + get: { + owner: '', + resolver: '', + records: {}, + location: UnsLocation.Layer1, + }, + }); + const spies2 = mockAsyncMethods(uns.unsl2, { + get: { + owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + records: {}, + location: UnsLocation.Layer2, + }, + }); + const isRegistered = await resolution.isRegistered( + WalletDomainLayerTwoWithAllRecords, + ); + expectSpyToBeCalled(spies); + expectSpyToBeCalled(spies2); + expect(isRegistered).toBe(true); + }); - it('should return true if registered on l1 but not l2', async () => { - const spies = mockAsyncMethods(uns.unsl1, { - get: { - owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - records: {}, - }, - }); - const spies2 = mockAsyncMethods(uns.unsl2, { - get: { - owner: '', - resolver: '', - records: {}, - }, + it('should return true if registered on l1 but not l2', async () => { + const spies = mockAsyncMethods(uns.unsl1, { + get: { + owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + records: {}, + }, + }); + const spies2 = mockAsyncMethods(uns.unsl2, { + get: { + owner: '', + resolver: '', + records: {}, + }, + }); + const isRegistered = await resolution.isRegistered( + CryptoDomainWithAllRecords, + ); + expectSpyToBeCalled(spies); + expectSpyToBeCalled(spies2); + expect(isRegistered).toBe(true); + }); }); - const isRegistered = await resolution.isRegistered( - CryptoDomainWithAllRecords, - ); - expectSpyToBeCalled(spies); - expectSpyToBeCalled(spies2); - expect(isRegistered).toBe(true); - }); - }); - describe('.isAvailable', () => { - it('should return false', async () => { - const spies = mockAsyncMethods(uns, { - get: { - owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - records: { - ['ipfs.html.value']: - 'QmQ38zzQHVfqMoLWq2VeiMLHHYki9XktzXxLYTWXt8cydu', - }, - }, - }); - const isAvailable = await resolution.isAvailable('brad.crypto'); - expectSpyToBeCalled(spies); - expect(isAvailable).toBe(false); - }); + describe('.isAvailable', () => { + it('should return false', async () => { + const spies = mockAsyncMethods(uns, { + get: { + owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + records: { + ['ipfs.html.value']: + 'QmQ38zzQHVfqMoLWq2VeiMLHHYki9XktzXxLYTWXt8cydu', + }, + }, + }); + const isAvailable = await resolution.isAvailable('brad.crypto'); + expectSpyToBeCalled(spies); + expect(isAvailable).toBe(false); + }); - it('should return true', async () => { - const spies = mockAsyncMethods(uns, { - get: { - owner: '', - resolver: '', - records: {}, - }, - }); - const isAvailable = await resolution.isAvailable( - 'qwdqwdjkqhdkqdqwjd.crypto', - ); - expectSpyToBeCalled(spies); - expect(isAvailable).toBe(true); - }); + it('should return true', async () => { + const spies = mockAsyncMethods(uns, { + get: { + owner: '', + resolver: '', + records: {}, + }, + }); + const isAvailable = await resolution.isAvailable( + 'qwdqwdjkqhdkqdqwjd.crypto', + ); + expectSpyToBeCalled(spies); + expect(isAvailable).toBe(true); + }); - it('should return false is available on l1 but not l2', async () => { - const spies = mockAsyncMethods(uns.unsl1, { - get: { - owner: '', - resolver: '', - records: {}, - }, - }); - const spies2 = mockAsyncMethods(uns.unsl2, { - get: { - owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', - resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', - records: {}, - }, - }); - const isAvailable = await resolution.isAvailable( - CryptoDomainWithAllRecords, - ); - expectSpyToBeCalled(spies); - expectSpyToBeCalled(spies2); - expect(isAvailable).toBe(false); - }); + it('should return false is available on l1 but not l2', async () => { + const spies = mockAsyncMethods(uns.unsl1, { + get: { + owner: '', + resolver: '', + records: {}, + }, + }); + const spies2 = mockAsyncMethods(uns.unsl2, { + get: { + owner: '0x58cA45E932a88b2E7D0130712B3AA9fB7c5781e2', + resolver: '0x95AE1515367aa64C462c71e87157771165B1287A', + records: {}, + }, + }); + const isAvailable = await resolution.isAvailable( + CryptoDomainWithAllRecords, + ); + expectSpyToBeCalled(spies); + expectSpyToBeCalled(spies2); + expect(isAvailable).toBe(false); + }); - skipItInLive('should return false for a .zil domain', async () => { - const unsSpy = mockAsyncMethod(uns, 'isAvailable', async () => { - throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); - }); - const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', [ - 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', - ]); - const isAvailable = await resolution.isAvailable('testing.zil'); - expectSpyToBeCalled([unsSpy, znsSpy]); - expect(isAvailable).toBe(false); - }); + skipItInLive('should return false for a .zil domain', async () => { + const unsSpy = mockAsyncMethod(uns, 'isAvailable', async () => { + throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); + }); + const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', [ + 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', + ]); + const isAvailable = await resolution.isAvailable('testing.zil'); + expectSpyToBeCalled([unsSpy, znsSpy]); + expect(isAvailable).toBe(false); + }); - it('should return true', async () => { - const unsSpy = mockAsyncMethod(uns, 'isAvailable', async () => { - throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); + it('should return true', async () => { + const unsSpy = mockAsyncMethod(uns, 'isAvailable', async () => { + throw new ResolutionError(ResolutionErrorCode.UnregisteredDomain); + }); + const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', ['']); + const isAvailable = await resolution.isAvailable('ryan.zil'); + expectSpyToBeCalled([unsSpy, znsSpy]); + expect(isAvailable).toBe(true); + }); }); - const znsSpy = mockAsyncMethod(zns, 'getRecordsAddresses', ['']); - const isAvailable = await resolution.isAvailable('ryan.zil'); - expectSpyToBeCalled([unsSpy, znsSpy]); - expect(isAvailable).toBe(true); - }); - }); - describe('.namehash', () => { - it('brad.crypto', () => { - const expectedNamehash = - '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9'; - const namehash = resolution.namehash( - 'brad.crypto', - NamingServiceName.UNS, - ); - expect(namehash).toEqual(expectedNamehash); - }); - - it('brad.zil (UNS)', () => { - const expectedNamehash = - '0x88e6867a2a7c3884e6565d03a9baf909232426adb433d908f9ae9541a66db9ac'; - const namehash = resolution.namehash('brad.zil', NamingServiceName.UNS); - expect(namehash).toEqual(expectedNamehash); - }); - - it('brad.zil (ZNS)', () => { - const expectedNamehash = - '0x5fc604da00f502da70bfbc618088c0ce468ec9d18d05540935ae4118e8f50787'; - const namehash = resolution.namehash('brad.zil', NamingServiceName.ZNS); - expect(namehash).toEqual(expectedNamehash); - }); - }); - - describe('.childhash', () => { - it('brad.crypto', () => { - const expectedNamehash = - '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9'; - const namehash = resolution.childhash( - '0x0f4a10a4f46c288cea365fcf45cccf0e9d901b945b9829ccdb54c10dc3cb7a6f', - 'brad', - NamingServiceName.UNS, - ); - expect(namehash).toEqual(expectedNamehash); - }); - - it('brad.zil (UNS)', () => { - const expectedNamehash = - '0x88e6867a2a7c3884e6565d03a9baf909232426adb433d908f9ae9541a66db9ac'; - const namehash = resolution.childhash( - '0xd81bbfcee722494b885e891546eeac23d0eedcd44038d7a2f6ef9ec2f9e0d239', - 'brad', - NamingServiceName.UNS, - ); - expect(namehash).toEqual(expectedNamehash); - }); - - it('brad.zil', () => { - const expectedNamehash = - '0x5fc604da00f502da70bfbc618088c0ce468ec9d18d05540935ae4118e8f50787'; - const namehash = resolution.childhash( - '0x9915d0456b878862e822e2361da37232f626a2e47505c8795134a95d36138ed3', - 'brad', - NamingServiceName.ZNS, - ); - expect(namehash).toEqual(expectedNamehash); - }); - - it('should throw error if service is not supported', () => { - expect(() => - resolution.childhash( - '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae', - 'beresnev', - 'COM' as NamingServiceName, - ), - ).toThrowError('Naming service COM is not supported'); - }); - }); + describe('.namehash', () => { + it('brad.crypto', () => { + const expectedNamehash = + '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9'; + const namehash = resolution.namehash( + 'brad.crypto', + NamingServiceName.UNS, + ); + expect(namehash).toEqual(expectedNamehash); + }); - describe('.location', () => { - skipItInLive( - 'should get location for uns l1, uns l2 and zns domains', - async () => { - // https://github.com/rust-ethereum/ethabi - // - // # getDataForMany return data - // ethabi encode params -v 'address[]' '[070e83FCed225184E67c86302493ffFCDB953f71,95ae1515367aa64c462c71e87157771165b1287a,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000]' -v 'address[]' '[0e43f36e4b986dfbe1a75cacfa60ca2bd44ae962,499dd6d875787869670900a2130223d85d4f6aa7,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000]' -v 'string[][]' '[[],[],[],[],[],[],[]]' - // # registryOf return data - // ethabi encode params -v address 070e83FCed225184E67c86302493ffFCDB953f71 - // ethabi encode params -v address 801452cFAC27e79a11c6b185986fdE09e8637589 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // # multicall return data - // ethabi encode params -v 'bytes[]' '[...]' # put the output of the commands above into the array - const mockValuesL1 = { - callEth: - '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000000000000000000000000000000000000000000620000000000000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000007000000000000000000000000070e83FCed225184E67c86302493ffFCDB953f7100000000000000000000000095ae1515367aa64c462c71e87157771165b1287a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000e43f36e4b986dfbe1a75cacfa60ca2bd44ae962000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000070e83FCed225184E67c86302493ffFCDB953f710000000000000000000000000000000000000000000000000000000000000020000000000000000000000000801452cFAC27e79a11c6b185986fdE09e86375890000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000', - }; - // # getDataForMany return data - // ethabi encode params -v 'address[]' '[0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,2a93c52e7b6e7054870758e15a1446e769edfb93,2a93c52e7b6e7054870758e15a1446e769edfb93,0000000000000000000000000000000000000000,2a93c52e7b6e7054870758e15a1446e769edfb93,0000000000000000000000000000000000000000]' -v 'address[]' '[0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,499dd6d875787869670900a2130223d85d4f6aa7,499dd6d875787869670900a2130223d85d4f6aa7,0000000000000000000000000000000000000000,499dd6d875787869670900a2130223d85d4f6aa7,0000000000000000000000000000000000000000]' -v 'string[][]' '[[],[],[],[],[],[],[]]' - // # registryOf return data - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // ethabi encode params -v address 2a93c52e7b6e7054870758e15a1446e769edfb93 - // ethabi encode params -v address 2a93c52e7b6e7054870758e15a1446e769edfb93 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // ethabi encode params -v address 2a93c52e7b6e7054870758e15a1446e769edfb93 - // ethabi encode params -v address 0000000000000000000000000000000000000000 - // # multicall return data - // ethabi encode params -v 'bytes[]' '[...]' # put the output of the commands above into the array - const mockValuesL2 = { - callEth: - '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000000000000000000000000000000000000000000620000000000000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb930000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb9300000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa7000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb9300000000000000000000000000000000000000000000000000000000000000200000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb930000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb9300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000', - }; - - mockAsyncMethods(uns.unsl1.readerContract, mockValuesL1); - mockAsyncMethods(uns.unsl2.readerContract, mockValuesL2); - mockAsyncMethods(uns, {isSupportedDomain: true}); - mockAsyncMethod(zns, 'getRecordsAddresses', [ - 'zil1xftz4cd425mer6jxmtl29l28xr0zu8s5hnp9he', - 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', - ]); - - const location = await resolution.locations([ - 'udtestdev-check.wallet', - 'brad.crypto', - 'udtestdev-test-l2-domain-784391.wallet', - 'udtestdev-test-l1-and-l2-ownership.wallet', - 'testing-domain-doesnt-exist-12345abc.blockchain', - 'uns-devtest-testnet-domain.zil', - 'zns-devtest-testnet-domain.zil', - ]); - expect(location['udtestdev-check.wallet']).toEqual({ - registryAddress: '0x070e83FCed225184E67c86302493ffFCDB953f71', - resolverAddress: '0x070e83FCed225184E67c86302493ffFCDB953f71', - networkId: 5, - blockchain: BlockchainType.ETH, - ownerAddress: '0x0e43F36e4B986dfbE1a75cacfA60cA2bD44Ae962', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ), + it('brad.zil (UNS)', () => { + const expectedNamehash = + '0x88e6867a2a7c3884e6565d03a9baf909232426adb433d908f9ae9541a66db9ac'; + const namehash = resolution.namehash( + 'brad.zil', + NamingServiceName.UNS, + ); + expect(namehash).toEqual(expectedNamehash); }); - expect(location['brad.crypto']).toEqual({ - registryAddress: '0x801452cFAC27e79a11c6b185986fdE09e8637589', - resolverAddress: '0x95AE1515367aa64C462c71e87157771165B1287A', - networkId: 5, - blockchain: BlockchainType.ETH, - ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ), + + it('brad.zil (ZNS)', () => { + const expectedNamehash = + '0x5fc604da00f502da70bfbc618088c0ce468ec9d18d05540935ae4118e8f50787'; + const namehash = resolution.namehash( + 'brad.zil', + NamingServiceName.ZNS, + ); + expect(namehash).toEqual(expectedNamehash); }); - expect(location['udtestdev-test-l2-domain-784391.wallet']).toEqual({ - registryAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', - resolverAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', - networkId: 80001, - blockchain: BlockchainType.MATIC, - ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), + + it('brad.eth', () => { + const expectedNamehash = + '0xe2cb672a04d6270338f15a428216ca714514dc01fdbdd76e97038a8d4080e01c'; + const namehash = resolution.namehash( + 'brad.eth', + NamingServiceName.ENS, + ); + expect(namehash).toEqual(expectedNamehash); }); - expect(location['udtestdev-test-l1-and-l2-ownership.wallet']).toEqual({ - registryAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', - resolverAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', - networkId: 80001, - blockchain: BlockchainType.MATIC, - ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), + }); + + describe('.childhash', () => { + it('brad.crypto', () => { + const expectedNamehash = + '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9'; + const namehash = resolution.childhash( + '0x0f4a10a4f46c288cea365fcf45cccf0e9d901b945b9829ccdb54c10dc3cb7a6f', + 'brad', + NamingServiceName.UNS, + ); + expect(namehash).toEqual(expectedNamehash); }); - expect( - location['testing-domain-doesnt-exist-12345abc.blockchain'], - ).toBeNull(); - // TODO! mint a domain that will be owned by the devtools team for the LIVE tests. - expect(location['uns-devtest-testnet-domain.zil']).toEqual({ - registryAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', - resolverAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', - networkId: 80001, - blockchain: BlockchainType.MATIC, - ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), + + it('brad.zil (UNS)', () => { + const expectedNamehash = + '0x88e6867a2a7c3884e6565d03a9baf909232426adb433d908f9ae9541a66db9ac'; + const namehash = resolution.childhash( + '0xd81bbfcee722494b885e891546eeac23d0eedcd44038d7a2f6ef9ec2f9e0d239', + 'brad', + NamingServiceName.UNS, + ); + expect(namehash).toEqual(expectedNamehash); }); - expect(location['zns-devtest-testnet-domain.zil']).toEqual({ - registryAddress: 'zil1hyj6m5w4atcn7s806s69r0uh5g4t84e8gp6nps', - resolverAddress: 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', - networkId: 333, - blockchain: BlockchainType.ZIL, - ownerAddress: 'zil1xftz4cd425mer6jxmtl29l28xr0zu8s5hnp9he', - blockchainProviderUrl: 'https://dev-api.zilliqa.com', + + it('brad.zil', () => { + const expectedNamehash = + '0x5fc604da00f502da70bfbc618088c0ce468ec9d18d05540935ae4118e8f50787'; + const namehash = resolution.childhash( + '0x9915d0456b878862e822e2361da37232f626a2e47505c8795134a95d36138ed3', + 'brad', + NamingServiceName.ZNS, + ); + expect(namehash).toEqual(expectedNamehash); }); - }, - ); - it('should check all methods for domain validation', async () => { - await expectResolutionErrorCode( - () => resolution.twitter('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.ipfsHash('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.httpUrl('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.resolver('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.owner('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.isRegistered('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.isAvailable('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.namehash('hello#blockchain', NamingServiceName.UNS), - ResolutionErrorCode.InvalidDomainAddress, - ); - await expectResolutionErrorCode( - () => resolution.isSupportedDomain('hello#blockchain'), - ResolutionErrorCode.InvalidDomainAddress, - ); - }); - }); + it('brad.eth', () => { + const expectedNamehash = + '0x96a270260d2f9e37845776c17a47ae9b8b7e7e576b2365afd2e7f30f43e9bb49'; + const namehash = resolution.childhash( + '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae', + 'beresnev', + NamingServiceName.ENS, + ); + expect(namehash).toEqual(expectedNamehash); + }); - describe('.Unhash token by UdApi', () => { - it('should unhash token', async () => { - resolution = new Resolution({ - sourceConfig: { - zns: {api: true}, - uns: {api: true}, - }, + it('should throw error if service is not supported', () => { + expect(() => + resolution.childhash( + '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae', + 'beresnev', + 'COM' as NamingServiceName, + ), + ).toThrowError('Naming service COM is not supported'); + }); }); - mockAsyncMethod(Networking, 'fetch', { - json: () => ({ - meta: { - domain: 'brad.crypto', - namehash: - '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9', - tokenId: - '53115498937382692782103703677178119840631903773202805882273058578308100329417', + + describe('.location', () => { + skipItInLive( + 'should get location for uns l1, uns l2 and zns domains', + async () => { + // https://github.com/rust-ethereum/ethabi + // + // # getDataForMany return data + // ethabi encode params -v 'address[]' '[7fb83000b8ed59d3ead22f0d584df3a85fbc0086,95ae1515367aa64c462c71e87157771165b1287a,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000]' -v 'address[]' '[0e43f36e4b986dfbe1a75cacfa60ca2bd44ae962,499dd6d875787869670900a2130223d85d4f6aa7,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000]' -v 'string[][]' '[[],[],[],[],[],[],[]]' + // # registryOf return data + // ethabi encode params -v address 7fb83000b8ed59d3ead22f0d584df3a85fbc0086 + // ethabi encode params -v address aad76bea7cfec82927239415bb18d2e93518ecbb + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // # multicall return data + // ethabi encode params -v 'bytes[]' '[...]' # put the output of the commands above into the array + const mockValuesL1 = { + callEth: + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000000000000000000000000000000000000000000620000000000000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000006e0000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000070000000000000000000000007fb83000b8ed59d3ead22f0d584df3a85fbc008600000000000000000000000095ae1515367aa64c462c71e87157771165b1287a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000e43f36e4b986dfbe1a75cacfa60ca2bd44ae962000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000007fb83000b8ed59d3ead22f0d584df3a85fbc00860000000000000000000000000000000000000000000000000000000000000020000000000000000000000000aad76bea7cfec82927239415bb18d2e93518ecbb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000', + }; + // # getDataForMany return data + // ethabi encode params -v 'address[]' '[0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,2a93c52e7b6e7054870758e15a1446e769edfb93,2a93c52e7b6e7054870758e15a1446e769edfb93,0000000000000000000000000000000000000000,2a93c52e7b6e7054870758e15a1446e769edfb93,0000000000000000000000000000000000000000]' -v 'address[]' '[0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,499dd6d875787869670900a2130223d85d4f6aa7,499dd6d875787869670900a2130223d85d4f6aa7,0000000000000000000000000000000000000000,499dd6d875787869670900a2130223d85d4f6aa7,0000000000000000000000000000000000000000]' -v 'string[][]' '[[],[],[],[],[],[],[]]' + // # registryOf return data + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // ethabi encode params -v address 2a93c52e7b6e7054870758e15a1446e769edfb93 + // ethabi encode params -v address 2a93c52e7b6e7054870758e15a1446e769edfb93 + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // ethabi encode params -v address 2a93c52e7b6e7054870758e15a1446e769edfb93 + // ethabi encode params -v address 0000000000000000000000000000000000000000 + // # multicall return data + // ethabi encode params -v 'bytes[]' '[...]' # put the output of the commands above into the array + const mockValuesL2 = { + callEth: + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000000000000000000000000000000000000000000620000000000000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb930000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb9300000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa7000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000499dd6d875787869670900a2130223d85d4f6aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb9300000000000000000000000000000000000000000000000000000000000000200000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb930000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002a93c52e7b6e7054870758e15a1446e769edfb9300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000', + }; + + mockAsyncMethods(uns.unsl1.readerContract, mockValuesL1); + mockAsyncMethods(uns.unsl2.readerContract, mockValuesL2); + mockAsyncMethods(uns, {isSupportedDomain: true}); + mockAsyncMethod(zns, 'getRecordsAddresses', [ + 'zil1xftz4cd425mer6jxmtl29l28xr0zu8s5hnp9he', + 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', + ]); + + const location = await resolution.locations([ + 'udtestdev-check.wallet', + 'brad.crypto', + 'udtestdev-test-l2-domain-784391.wallet', + 'udtestdev-test-l1-and-l2-ownership.wallet', + 'testing-domain-doesnt-exist-12345abc.blockchain', + 'uns-devtest-testnet-domain.zil', + 'zns-devtest-testnet-domain.zil', + ]); + expect(location['udtestdev-check.wallet']).toEqual({ + registryAddress: '0x7fb83000B8eD59D3eAD22f0D584Df3a85fBC0086', + resolverAddress: '0x7fb83000B8eD59D3eAD22f0D584Df3a85fBC0086', + networkId: 5, + blockchain: BlockchainType.ETH, + ownerAddress: '0x0e43F36e4B986dfbE1a75cacfA60cA2bD44Ae962', + blockchainProviderUrl: getProtocolLinkFromEnv( + ProviderProtocol.http, + NamingServiceName.ENS, + ), + }); + expect(location['brad.crypto']).toEqual({ + registryAddress: '0xAad76bea7CFEc82927239415BB18D2e93518ecBB', + resolverAddress: '0x95AE1515367aa64C462c71e87157771165B1287A', + networkId: 5, + blockchain: BlockchainType.ETH, + ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', + blockchainProviderUrl: getProtocolLinkFromEnv( + ProviderProtocol.http, + NamingServiceName.ENS, + ), + }); + expect(location['udtestdev-test-l2-domain-784391.wallet']).toEqual({ + registryAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', + resolverAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', + networkId: 80001, + blockchain: BlockchainType.MATIC, + ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', + blockchainProviderUrl: getProtocolLinkFromEnv( + ProviderProtocol.http, + 'UNSL2', + ), + }); + expect( + location['udtestdev-test-l1-and-l2-ownership.wallet'], + ).toEqual({ + registryAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', + resolverAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', + networkId: 80001, + blockchain: BlockchainType.MATIC, + ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', + blockchainProviderUrl: getProtocolLinkFromEnv( + ProviderProtocol.http, + 'UNSL2', + ), + }); + expect( + location['testing-domain-doesnt-exist-12345abc.blockchain'], + ).toBeNull(); + // TODO! mint a domain that will be owned by the devtools team for the LIVE tests. + expect(location['uns-devtest-testnet-domain.zil']).toEqual({ + registryAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', + resolverAddress: '0x2a93C52E7B6E7054870758e15A1446E769EdfB93', + networkId: 80001, + blockchain: BlockchainType.MATIC, + ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', + blockchainProviderUrl: getProtocolLinkFromEnv( + ProviderProtocol.http, + 'UNSL2', + ), + }); + expect(location['zns-devtest-testnet-domain.zil']).toEqual({ + registryAddress: 'zil1hyj6m5w4atcn7s806s69r0uh5g4t84e8gp6nps', + resolverAddress: 'zil1jcgu2wlx6xejqk9jw3aaankw6lsjzeunx2j0jz', + networkId: 333, + blockchain: BlockchainType.ZIL, + ownerAddress: 'zil1xftz4cd425mer6jxmtl29l28xr0zu8s5hnp9he', + blockchainProviderUrl: 'https://dev-api.zilliqa.com', + }); }, - }), - }); - expect( - await resolution.unhash( - '53115498937382692782103703677178119840631903773202805882273058578308100329417', - NamingServiceName.UNS, - ), - ).toEqual('brad.crypto'); - }); + ); - it('should throw exception for empty response', async () => { - resolution = new Resolution({ - sourceConfig: { - zns: {api: true}, - uns: {api: true}, - }, + it('should check all methods for domain validation', async () => { + await expectResolutionErrorCode( + () => resolution.twitter('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.ipfsHash('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.httpUrl('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.resolver('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.owner('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.isRegistered('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.isAvailable('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => + resolution.namehash('hello#blockchain', NamingServiceName.UNS), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.isSupportedDomain('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + await expectResolutionErrorCode( + () => resolution.allRecords('hello#blockchain'), + ResolutionErrorCode.InvalidDomainAddress, + ); + }); }); - mockAsyncMethod(Networking, 'fetch', { - json: () => ({ - meta: { - domain: '', - namehash: - '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9', - tokenId: + + describe('.Unhash token by UdApi', () => { + it('should unhash token', async () => { + resolution = new Resolution({ + sourceConfig: { + zns: {api: true}, + uns: {api: true}, + }, + }); + mockAsyncMethod(Networking, 'fetch', { + json: () => ({ + meta: { + domain: 'brad.crypto', + namehash: + '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9', + tokenId: + '53115498937382692782103703677178119840631903773202805882273058578308100329417', + }, + }), + }); + expect( + await resolution.unhash( '53115498937382692782103703677178119840631903773202805882273058578308100329417', - }, - }), - }); - await expectResolutionErrorCode( - () => - resolution.unhash( - '53115498937382692782103703677178119840631903773202805882273058578308100329417', - NamingServiceName.UNS, - ), - ResolutionErrorCode.UnregisteredDomain, - ); - }); + NamingServiceName.UNS, + ), + ).toEqual('brad.crypto'); + }); - it('should unhash token for ZNS', async () => { - resolution = new Resolution({ - sourceConfig: { - zns: {api: true}, - uns: {api: true}, - }, - }); - mockAsyncMethod(Networking, 'fetch', { - json: () => ({ - meta: { - domain: 'brad.zil', - namehash: - '0x5fc604da00f502da70bfbc618088c0ce468ec9d18d05540935ae4118e8f50787', - tokenId: + it('should throw exception for empty response', async () => { + resolution = new Resolution({ + sourceConfig: { + zns: {api: true}, + uns: {api: true}, + }, + }); + mockAsyncMethod(Networking, 'fetch', { + json: () => ({ + meta: { + domain: '', + namehash: + '0x756e4e998dbffd803c21d23b06cd855cdc7a4b57706c95964a37e24b47c10fc9', + tokenId: + '53115498937382692782103703677178119840631903773202805882273058578308100329417', + }, + }), + }); + await expectResolutionErrorCode( + () => + resolution.unhash( + '53115498937382692782103703677178119840631903773202805882273058578308100329417', + NamingServiceName.UNS, + ), + ResolutionErrorCode.UnregisteredDomain, + ); + }); + + it('should unhash token for ZNS', async () => { + resolution = new Resolution({ + sourceConfig: { + zns: {api: true}, + uns: {api: true}, + }, + }); + mockAsyncMethod(Networking, 'fetch', { + json: () => ({ + meta: { + domain: 'brad.zil', + namehash: + '0x5fc604da00f502da70bfbc618088c0ce468ec9d18d05540935ae4118e8f50787', + tokenId: + '43319589818590979333002700458407583892978809980702780436022141697532225718151', + blockchain: 'ZIL', + }, + }), + }); + expect( + await resolution.unhash( '43319589818590979333002700458407583892978809980702780436022141697532225718151', - blockchain: 'ZIL', - }, - }), + NamingServiceName.ZNS, + ), + ).toEqual('brad.zil'); + }); }); - expect( - await resolution.unhash( - '43319589818590979333002700458407583892978809980702780436022141697532225718151', - NamingServiceName.ZNS, - ), - ).toEqual('brad.zil'); }); - }); - describe('Reverse resolution', () => { - it('should reverse resolve', async () => { - mockAsyncMethods(uns, { - reverseOf: - '53115498937382692782103703677178119840631903773202805882273058578308100329417', - getTokenUri: - 'https://metadata.staging.unstoppabledomains.com/metadata/', - }); - mockAsyncMethod(Networking, 'fetch', { - ok: true, - json: () => ({ - name: 'brad.crypto', - }), + describe('Reverse resolution', () => { + it('should reverse resolve', async () => { + mockAsyncMethods(uns, { + reverseOf: + '53115498937382692782103703677178119840631903773202805882273058578308100329417', + getTokenUri: + 'https://metadata.staging.unstoppabledomains.com/metadata/', + }); + mockAsyncMethod(Networking, 'fetch', { + ok: true, + json: () => ({ + name: 'brad.crypto', + }), + }); + const reverseDomain = await resolution.reverse( + '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', + ); + expect(reverseDomain).toBe('brad.crypto'); }); - const reverseDomain = await resolution.reverse( - '0x45b31e01AA6f42F0549aD482BE81635ED3149abb', - ); - expect(reverseDomain).toBe('brad.crypto'); - }); - it('should reverse resolve for a subdomain', async () => { - mockAsyncMethods(uns, { - reverseOf: - '44924347734549711771487655536240923593575753005984062220276294864587632017879', - getTokenUri: - 'https://metadata.staging.unstoppabledomains.com/metadata/', - }); - mockAsyncMethod(Networking, 'fetch', { - ok: true, - json: () => ({ - name: SubdomainLayerTwo, - }), + it('should reverse resolve for a subdomain', async () => { + mockAsyncMethods(uns, { + reverseOf: + '44924347734549711771487655536240923593575753005984062220276294864587632017879', + getTokenUri: + 'https://metadata.staging.unstoppabledomains.com/metadata/', + }); + mockAsyncMethod(Networking, 'fetch', { + ok: true, + json: () => ({ + name: SubdomainLayerTwo, + }), + }); + const reverseDomain = await resolution.reverse( + '0xA0a92d77D92934951F07E7CEb96a7f0ec387ebc1', + ); + expect(reverseDomain).toBe(SubdomainLayerTwo); }); - const reverseDomain = await resolution.reverse( - '0xA0a92d77D92934951F07E7CEb96a7f0ec387ebc1', - ); - expect(reverseDomain).toBe(SubdomainLayerTwo); }); }); }); diff --git a/src/tests/UdApi.test.ts b/src/tests/UdApi.test.ts index 02f7918a..19ce7967 100644 --- a/src/tests/UdApi.test.ts +++ b/src/tests/UdApi.test.ts @@ -23,6 +23,7 @@ beforeEach(() => { sourceConfig: { zns: {api: true}, uns: {api: true}, + ens: {api: true}, }, }); udApi = resolution.serviceMap[NamingServiceName.UNS].usedServices[0] as UdApi; @@ -79,11 +80,12 @@ describe('Unstoppable API', () => { }); it('should throw unsupported method', async () => { - const handle = 'ryan.zil'; + const handle = 'ryan.eth'; await expect(resolution.twitter(handle)).rejects.toThrowError( `Method twitter is not supported for ${handle}`, ); }); + it('returns owner of the domain', async () => { mockAPICalls('ud_api_generic_test', DefaultUrl); expect(await resolution.owner('cofounding.zil')).toEqual( diff --git a/src/tests/Uns.test.ts b/src/tests/Uns.test.ts index c9c97cd1..88c21b93 100644 --- a/src/tests/Uns.test.ts +++ b/src/tests/Uns.test.ts @@ -7,7 +7,7 @@ import { mockAsyncMethods, expectSpyToBeCalled, expectResolutionErrorCode, - getUnsProtocolLinkFromEnv, + getProtocolLinkFromEnv, expectConfigurationErrorCode, CryptoDomainWithoutGunDbRecords, CryptoDomainWithAllRecords, @@ -36,11 +36,11 @@ describe('UNS', () => { const uns = new Uns({ locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -48,11 +48,11 @@ describe('UNS', () => { expect(uns).toBeDefined(); expect(uns.unsl1.network).toBe('goerli'); expect(uns.unsl1.url).toBe( - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); expect(uns.unsl2.network).toBe('polygon-mumbai'); expect(uns.unsl2.url).toBe( - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), ); }); @@ -145,11 +145,11 @@ describe('UNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -326,7 +326,7 @@ describe('UNS', () => { it('should return true for supported domain', async () => { mockAPICalls( 'uns_domain_exists_test_true', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; mockAsyncMethods(uns.unsl2.readerContract, { @@ -343,7 +343,7 @@ describe('UNS', () => { it('should return false for unsupported domain', async () => { mockAPICalls( 'uns_domain_exists_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const uns = resolution.serviceMap[NamingServiceName.UNS].native as Uns; mockAsyncMethods(uns.unsl2.readerContract, { @@ -1198,7 +1198,7 @@ describe('UNS', () => { ); mockAPICalls( 'uns_registry_address_tests', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const registryAddress = await uns.registryAddress( 'udtestdev-265f8f.crypto', @@ -1216,7 +1216,7 @@ describe('UNS', () => { ); mockAPICalls( 'uns_registry_address_tests', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const registryAddress = await uns.registryAddress('some-domain.888'); expect(registryAddress).toBe( @@ -1265,7 +1265,7 @@ describe('UNS', () => { it('should throw error if tld is not supported', async () => { mockAPICalls( 'uns_registry_address_tests', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const unsl2 = uns.unsl2; mockAsyncMethod(unsl2.readerContract, 'call', (params) => @@ -1284,7 +1284,7 @@ describe('UNS', () => { ); mockAPICalls( 'uns_registry_address_tests', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); await expectResolutionErrorCode( () => uns.registryAddress('some-domain.unknown'), @@ -1524,11 +1524,11 @@ describe('UNS', () => { skipItInLive( 'should throw error when FetchProvider throws Error', async () => { - const url = getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); + const url = getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'); const provider = new FetchProvider(NamingServiceName.UNS, url); const polygonProvider = new FetchProvider( NamingServiceName.UNS, - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), ); resolution = new Resolution({ sourceConfig: { diff --git a/src/tests/UnsInternal.test.ts b/src/tests/UnsInternal.test.ts index 4763dd29..a9319325 100644 --- a/src/tests/UnsInternal.test.ts +++ b/src/tests/UnsInternal.test.ts @@ -3,7 +3,7 @@ import {NullAddress} from '../types'; import { mockAsyncMethods, expectSpyToBeCalled, - getUnsProtocolLinkFromEnv, + getProtocolLinkFromEnv, CryptoDomainWithAllRecords, WalletDomainLayerTwoWithAllRecords, mockAPICalls, @@ -28,7 +28,7 @@ beforeEach(async () => { unsInternalL1 = new UnsInternal( UnsLocation.Layer1, { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, BlockchainType.ETH, @@ -36,7 +36,7 @@ beforeEach(async () => { unsInternalL2 = new UnsInternal( UnsLocation.Layer2, { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, BlockchainType.MATIC, @@ -51,7 +51,7 @@ describe('UnsInternal', () => { new UnsInternal( UnsLocation.Layer1, { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), } as any, BlockchainType.ETH, ), @@ -67,7 +67,7 @@ describe('UnsInternal', () => { new UnsInternal( UnsLocation.Layer1, { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'custom', }, BlockchainType.ETH, @@ -88,7 +88,7 @@ describe('UnsInternal', () => { new UnsInternal( UnsLocation.Layer1, { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'custom', proxyReaderAddress: '0xinvalid', }, @@ -130,7 +130,7 @@ describe('UnsInternal', () => { unsInternalL1 = new UnsInternal( UnsLocation.Layer1, { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', proxyServiceApiKey: 'some key', }, @@ -313,7 +313,7 @@ describe('UnsInternal', () => { it('should return true for tld exists', async () => { mockAPICalls( 'uns_domain_exists_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const exists = await unsInternalL1.exists( CryptoDomainWithAllRecords.split('.')[1], @@ -323,7 +323,7 @@ describe('UnsInternal', () => { it('should return true for domain exists', async () => { mockAPICalls( 'uns_domain_exists_true_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const exists = await unsInternalL1.exists(CryptoDomainWithAllRecords); expect(exists).toBe(true); @@ -331,7 +331,7 @@ describe('UnsInternal', () => { it('should return true for tld exists on L2', async () => { mockAPICalls( 'uns_l2_domain_exists_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), ); const exists = await unsInternalL2.exists( WalletDomainLayerTwoWithAllRecords.split('.')[1], @@ -341,7 +341,7 @@ describe('UnsInternal', () => { it('should return true for domain exists on L2', async () => { mockAPICalls( 'uns_l2_domain_exists_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), ); const exists = await unsInternalL2.exists( WalletDomainLayerTwoWithAllRecords, @@ -352,7 +352,7 @@ describe('UnsInternal', () => { it('should return location for L1 domains', async () => { mockAPICalls( 'uns_l1_location_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const location = await unsInternalL1.locations([ 'udtestdev-check.wallet', @@ -365,7 +365,7 @@ describe('UnsInternal', () => { networkId: 5, blockchain: BlockchainType.ETH, ownerAddress: '0x0e43F36e4B986dfbE1a75cacfA60cA2bD44Ae962', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( + blockchainProviderUrl: getProtocolLinkFromEnv( ProviderProtocol.http, 'UNSL1', ), @@ -376,7 +376,7 @@ describe('UnsInternal', () => { networkId: 5, blockchain: BlockchainType.ETH, ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( + blockchainProviderUrl: getProtocolLinkFromEnv( ProviderProtocol.http, 'UNSL1', ), @@ -389,7 +389,7 @@ describe('UnsInternal', () => { it('should return location for L2 domains', async () => { mockAPICalls( 'uns_l2_location_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), ); const location = await unsInternalL2.locations([ 'udtestdev-test-l2-domain-784391.wallet', @@ -401,7 +401,7 @@ describe('UnsInternal', () => { networkId: 80001, blockchain: BlockchainType.MATIC, ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( + blockchainProviderUrl: getProtocolLinkFromEnv( ProviderProtocol.http, 'UNSL2', ), @@ -414,7 +414,7 @@ describe('UnsInternal', () => { it('should return location for domains starting with 0x', async () => { mockAPICalls( 'uns_l2_0x_location_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), ); const location = await unsInternalL2.locations([ '0xtestdomain-dev-test.wallet', @@ -425,7 +425,7 @@ describe('UnsInternal', () => { networkId: 80001, blockchain: BlockchainType.MATIC, ownerAddress: '0x499dD6D875787869670900a2130223D85d4F6Aa7', - blockchainProviderUrl: getUnsProtocolLinkFromEnv( + blockchainProviderUrl: getProtocolLinkFromEnv( ProviderProtocol.http, 'UNSL2', ), @@ -438,7 +438,7 @@ describe('UnsInternal', () => { unsInternalL1 = new UnsInternal( UnsLocation.Layer1, { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', proxyServiceApiKey: 'some key', }, @@ -447,14 +447,14 @@ describe('UnsInternal', () => { mockAPICalls( 'uns_domain_exists_true_test', - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), ); const exists = await unsInternalL1.exists(CryptoDomainWithAllRecords); expect(exists).toBe(true); expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenLastCalledWith( - getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), expect.objectContaining({ headers: expect.objectContaining({ 'Content-Type': 'application/json', diff --git a/src/tests/Zns.test.ts b/src/tests/Zns.test.ts index bad98d32..a1a154e1 100644 --- a/src/tests/Zns.test.ts +++ b/src/tests/Zns.test.ts @@ -1,6 +1,6 @@ import Resolution, {ResolutionErrorCode} from '../index'; import { - getUnsProtocolLinkFromEnv, + getProtocolLinkFromEnv, ProviderProtocol, mockAsyncMethod, expectSpyToBeCalled, @@ -27,11 +27,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -62,11 +62,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -85,11 +85,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -108,11 +108,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -133,17 +133,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -164,17 +158,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -196,11 +184,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -223,17 +211,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -256,17 +238,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL1', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv( - ProviderProtocol.http, - 'UNSL2', - ), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -288,11 +264,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -311,11 +287,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, @@ -340,11 +316,11 @@ describe('ZNS', () => { uns: { locations: { Layer1: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL1'), network: 'goerli', }, Layer2: { - url: getUnsProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), + url: getProtocolLinkFromEnv(ProviderProtocol.http, 'UNSL2'), network: 'polygon-mumbai', }, }, diff --git a/src/tests/helpers.ts b/src/tests/helpers.ts index f9545ce9..72654fe2 100644 --- a/src/tests/helpers.ts +++ b/src/tests/helpers.ts @@ -7,6 +7,7 @@ import ConfigurationError, { ConfigurationErrorCode, } from '../errors/configurationError'; import DnsRecordsError, {DnsRecordsErrorCode} from '../errors/dnsRecordsError'; +import {NamingServiceName} from '../types/publicTypes'; export const ZilliqaUrl = 'https://api.zilliqa.com'; export const DefaultUrl = 'https://unstoppabledomains.com/api/v1'; @@ -177,9 +178,9 @@ export function mockAPICalls(testName: string, url: string): void { /** * return URLs set in env vars if any */ -export function getUnsProtocolLinkFromEnv( +export function getProtocolLinkFromEnv( providerProtocol: ProviderProtocol, - namingService: 'UNSL1' | 'UNSL2', + namingService: NamingServiceName.ENS | 'UNSL1' | 'UNSL2', ): string { if ( namingService === 'UNSL1' && @@ -229,6 +230,30 @@ export function getUnsProtocolLinkFromEnv( throw new Error('missing env var L2_TEST_NET_RPC_WSS_URL'); } + if ( + namingService === NamingServiceName.ENS && + providerProtocol === ProviderProtocol.http && + process.env.L1_TEST_NET_RPC_URL + ) { + if (process.env.L1_TEST_NET_RPC_URL) { + return process.env.L1_TEST_NET_RPC_URL; + } + + throw new Error('missing env var L1_TEST_NET_RPC_URL'); + } + + if ( + namingService === NamingServiceName.ENS && + providerProtocol === ProviderProtocol.wss && + process.env.ENS_TEST_NET_RPC_WSS_URL + ) { + if (process.env.L1_TEST_NET_RPC_WSS_URL) { + return process.env.L1_TEST_NET_RPC_WSS_URL; + } + + throw new Error('missing env var L1_TEST_NET_RPC_WSS_URL'); + } + throw new Error('Invalid test config'); } diff --git a/src/tests/namehash.test.ts b/src/tests/namehash.test.ts index 78f826f4..067a1dd4 100644 --- a/src/tests/namehash.test.ts +++ b/src/tests/namehash.test.ts @@ -63,6 +63,13 @@ describe('Namehash', () => { ); expect(childhash).toEqual(expectedNamehash); }); + + it('should return namehash for ENS domain', () => { + const expectedNamehash = + '0x96a270260d2f9e37845776c17a47ae9b8b7e7e576b2365afd2e7f30f43e9bb49'; + const namehash = eip137Namehash('beresnev.eth'); + expect(namehash).toEqual(expectedNamehash); + }); }); describe('ZNS', () => { diff --git a/src/tests/requireOrFail.test.ts b/src/tests/requireOrFail.test.ts new file mode 100644 index 00000000..2e124dff --- /dev/null +++ b/src/tests/requireOrFail.test.ts @@ -0,0 +1,20 @@ +import {requireOrFail} from '../utils/requireOrFail'; + +describe('requireOrFail', () => { + it('should throw configuration error', () => { + expect(() => + requireOrFail('non-existent-module', 'requested-module', '1'), + ).toThrowError( + 'Missing dependency for this functionality. Please install requested-module @ 1 via npm or yarn', + ); + }); + + it('should succeed', () => { + const module = requireOrFail( + '../utils/requireOrFail', + 'require-or-fail', + '1', + ); + expect(module.requireOrFail).toEqual(requireOrFail); + }); +}); diff --git a/src/tests/testData/mockData.json b/src/tests/testData/mockData.json index 15f80d37..baa3a175 100644 --- a/src/tests/testData/mockData.json +++ b/src/tests/testData/mockData.json @@ -596,5 +596,41 @@ "transactionIndex": "0x9" } ] - } + }, + "ens_setup_call": [ + { + "METHOD": "POST", + "REQUEST": { + "method": "POST", + "url": "https://goerli.infura.io/v3/a32aa2ace9704ee9a1a9906418bcabe5", + "headers": { + "content-type": ["application/json"], + "accept": ["*/*"], + "content-length": ["202"], + "user-agent": [ + "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)" + ], + "accept-encoding": ["gzip,deflate"], + "connection": ["close"] + }, + "body": { + "jsonrpc": "2.0", + "id": "1", + "method": "eth_call", + "params": [ + { + "data": "0xfd0cd0d9d83b9ecd0138440a533df2ae4ef7e2b1467caa9bf9b00b9f6a680c9321ceae1e", + "to": "0x114D4603199df73e7D157787f8778E21fCd13066" + }, + "latest" + ] + } + }, + "RESPONSE": { + "jsonrpc": "2.0", + "id": 1, + "result": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + } + ] } diff --git a/src/types/index.ts b/src/types/index.ts index 534684f6..91fe80d4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,9 @@ import {Provider} from './publicTypes'; export type Dictionary = {[k: string]: T}; +export type EnsNetworkIdMap = { + [key: number]: string; +}; export interface ProxyReaderMap { [key: string]: string; } @@ -85,6 +88,8 @@ export enum NullAddresses { '0x0000000000000000000000000000000000000000000000000000000000000000', } +export const EthCoinIndex = '60'; + // TypeScript will infer a string union type from the literal values passed to // this function. Without `extends string`, it would instead generalize them // to the common string type. @@ -123,6 +128,8 @@ export const UnsSupportedNetwork = StringUnion( export type UnsSupportedNetwork = typeof UnsSupportedNetwork.type; +export const EnsSupportedNetwork = StringUnion('mainnet', 'goerli'); + export const ZnsSupportedNetwork = StringUnion('mainnet', 'testnet'); export function hasProvider(obj: any): obj is {provider: Provider} { diff --git a/src/types/publicTypes.ts b/src/types/publicTypes.ts index 0140a2b8..2d26c3da 100644 --- a/src/types/publicTypes.ts +++ b/src/types/publicTypes.ts @@ -19,6 +19,12 @@ export type UnsSource = { locations: {Layer1: UnsLayerSource; Layer2: UnsLayerSource}; }; +export type EnsSource = NamingServiceSource & { + network: string; + registryAddress?: string; + proxyServiceApiKey?: string; +}; + export type ZnsSource = NamingServiceSource & { network: string; registryAddress?: string; @@ -27,6 +33,7 @@ export type ZnsSource = NamingServiceSource & { export type SourceConfig = { uns?: UnsSource | Api; zns?: ZnsSource | Api; + ens?: EnsSource | Api; }; export type ResolutionConfig = { @@ -41,6 +48,7 @@ export enum UnsLocation { export enum NamingServiceName { UNS = 'UNS', + ENS = 'ENS', ZNS = 'ZNS', } @@ -53,6 +61,7 @@ export type AutoNetworkConfigs = { Layer2: {url: string} | {provider: Provider}; }; }; + ens?: {url: string} | {provider: Provider}; zns?: {url: string} | {provider: Provider}; }; diff --git a/src/utils/Eip1993Factories.ts b/src/utils/Eip1993Factories.ts index 98a01b52..5129e6f4 100644 --- a/src/utils/Eip1993Factories.ts +++ b/src/utils/Eip1993Factories.ts @@ -64,7 +64,7 @@ function fromWeb3Version0Provider(provider: Web3Version0Provider): Provider { * @see https://github.com/ethereum/web3.js/blob/1.x/packages/web3-providers-http/src/index.js#L95 */ function fromWeb3Version1Provider(provider: Web3Version1Provider): Provider { - if (provider.send === undefined) { + if (provider && provider.send === undefined) { throw new ConfigurationError(ConfigurationErrorCode.IncorrectProvider); } return { diff --git a/src/utils/index.ts b/src/utils/index.ts index 90d7b931..381ef436 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -32,6 +32,10 @@ export function getLibAgent(): string { } } +export function signedInfuraLink(infura: string, network = 'mainnet'): string { + return `https://${network}.infura.io/v3/${infura}`; +} + export function signedLink( key: string, network: UnsSupportedNetwork = 'mainnet', @@ -75,6 +79,11 @@ export function constructRecords( export const domainExtensionToNamingServiceName = { crypto: NamingServiceName.UNS, zil: NamingServiceName.ZNS, + eth: NamingServiceName.ENS, + luxe: NamingServiceName.ENS, + xyz: NamingServiceName.ENS, + kred: NamingServiceName.ENS, + reverse: NamingServiceName.ENS, }; export const findNamingServiceName = ( diff --git a/src/utils/namehash.ts b/src/utils/namehash.ts index 43641ef8..53f34281 100644 --- a/src/utils/namehash.ts +++ b/src/utils/namehash.ts @@ -3,6 +3,7 @@ import sha3 from 'crypto-js/sha3'; import hex from 'crypto-js/enc-hex'; import WordArray from 'crypto-js/lib-typedarrays'; import BN from 'bn.js'; +import {keccak256} from 'js-sha3'; export function eip137Namehash(domain: string): string { const arr = hashArray(domain, 'sha3'); @@ -71,3 +72,31 @@ export function fromDecStringToHex(value: string): string { return value; } + +export const splitDomainName = ( + domain: string, +): {label: string; tld: string} => { + const splitDomain = domain.split('.'); + let label = splitDomain[0]; + let tld = splitDomain[1]; + if (splitDomain.length - 1 < 2) { + return {label, tld}; + } + + tld = splitDomain[-1]; + label = splitDomain.slice(0, -1).join('.'); + return {label, tld}; +}; + +// returns the tokenId of an ens domain name. +// @see https://docs.ens.domains/dapp-developer-guide/ens-as-nft#deriving-tokenid-from-ens-name +export const labelNameHash = (domain: string): string => { + const splitDomain = splitDomainName(domain); + const labelHash = keccak256(Buffer.from(splitDomain.label, 'utf8')); + return `0x${labelHash}`; +}; + +export const getParentDomain = (domain: string): string => { + const splitDomain = domain.split('.'); + return `${splitDomain[-2]}.${splitDomain[-1]}`; +}; diff --git a/src/utils/requireOrFail.ts b/src/utils/requireOrFail.ts new file mode 100644 index 00000000..137b9ce9 --- /dev/null +++ b/src/utils/requireOrFail.ts @@ -0,0 +1,26 @@ +import ConfigurationError, { + ConfigurationErrorCode, +} from '../errors/configurationError'; + +/** + * Function tries to require module and throw error if module is not found. + * @param module - Module name or path + * @param dependencyName NPM name of the requested module + * @param allowedVersions Allowed versions of requested module + * @throws ConfigurationError + */ +export function requireOrFail( + module: string, + dependencyName: string, + allowedVersions: string, +): any { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require(module); + } catch (e) { + throw new ConfigurationError(ConfigurationErrorCode.DependencyMissing, { + dependency: dependencyName, + version: allowedVersions, + }); + } +}