├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── defaults │ ├── default-connections.ts │ ├── erc721.ud.proxy.abi.ts │ └── web3-resolver.ts ├── index.ts ├── networks │ └── connections │ │ ├── connection-library.ts │ │ ├── contract-connection.ts │ │ └── network-connection.types.ts ├── resolver-providers │ ├── providers │ │ ├── base-resolver-provider.ts │ │ ├── ens │ │ │ ├── ens-resolver-provider.consts.ts │ │ │ └── ens-resolver-provider.ts │ │ ├── freename │ │ │ ├── freename-resolver-provider.consts.ts │ │ │ ├── freename-resolver-provider.ts │ │ │ ├── freename-resolver-provider.types.ts │ │ │ └── freename-resolver-tools.ts │ │ └── ud │ │ │ ├── ud-resolver-provider.consts.ts │ │ │ ├── ud-resolver-provider.ts │ │ │ └── ud-resolver-tools.ts │ ├── resolver-provider-router.ts │ └── resolver-provider.interface.ts ├── resolvers │ ├── resolved-resource │ │ ├── resolved-resource.interface.ts │ │ └── resolved-resource.ts │ ├── resolver.ts │ └── types │ │ ├── resolved-resource-type.ts │ │ └── resolver-name.ts └── tools │ ├── api-caller.ts │ ├── name-tools.ts │ └── name-tools.types.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "overrides": [], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": "latest", 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "@typescript-eslint" 18 | ], 19 | "rules": { 20 | "indent": [ 21 | "error", 22 | "tab", 23 | { "SwitchCase": 1 } 24 | ], 25 | "linebreak-style": [ 26 | "error", 27 | "unix" 28 | ], 29 | "quotes": [ 30 | "error", 31 | "double" 32 | ], 33 | "semi": [ 34 | "error", 35 | "always" 36 | ], 37 | "object-curly-spacing": [ 38 | "error", 39 | "always" 40 | ], 41 | "prefer-const": "error", 42 | "camelcase": [ 43 | "error", 44 | { 45 | "properties": "always" 46 | } 47 | ], 48 | "comma-dangle": [ 49 | "error", 50 | "always-multiline" 51 | ], 52 | "comma-spacing": [ 53 | "error", 54 | { 55 | "before": false, 56 | "after": true 57 | } 58 | ], 59 | "no-mixed-spaces-and-tabs": [ 60 | "error", 61 | "smart-tabs" 62 | ], 63 | "no-redeclare": [ 64 | "error", 65 | { 66 | "builtinGlobals": true 67 | } 68 | ] 69 | } 70 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | #Compiled .js files 107 | lib 108 | 109 | #VSCode 110 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 FreenameDomains 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web3 Domain Name Resolver 2 | This library is part of the Freename Web3 ecosystem and wants to help Web3 developers to easly support multiple Web3 Domain Providers. This library standardizes interfaces for interaction enabling to resolve and manage these supported providers: 3 | * [ENS](https://ens.domains/) 4 | * [Unstoppable Domains](https://unstoppabledomains.com/) 5 | * [Freename](https://freename.io/) 6 | 7 | ## Installation 8 | To install the library use npm. 9 | 13 | 14 | ```shell 15 | npm install web3-domain-resolver --save 16 | ``` 17 | ## Usage 18 | 19 | ### Resolve a domain or TLD 20 | 21 | To resolve a domain or TLD using the web3-domain-resolver, you have to import it into your project then create a new istance of the default `Web3Resolver` class. 22 | 23 | ```ts 24 | import { Web3Resolver } from "web3-domain-resolver"; 25 | 26 | const web3resolver = new Web3Resolver(); 27 | ``` 28 | 29 | From the `Web3Resolver` you can call the `resolve` function. 30 | 31 | ```ts 32 | const resolvedDomain = await web3resolver.resolve("test.web3domain"); 33 | ``` 34 | 35 | If the domain is valid, exists on the blockchain and can be resolved a `IResolvedResource` is given, otherwise the result is `undefined`. 36 | ```ts 37 | { 38 | fullname: string, 39 | tld: string, 40 | type: ResolvedResourceType,//'domain' | 'tld' 41 | tokenId: string, 42 | providerName: ProviderName,//'unstoppable' | 'ens' | 'freename' 43 | resolverProvider: IResolverProvider, 44 | network: NetworkName,//'polygon' | 'ethereum' | ... 45 | proxyReaderAddress: string, 46 | proxyWriterAddress: string, 47 | ownerAddress: string, 48 | metadataUri: string | undefined, 49 | imageUrl: string | undefined, 50 | metadata: any | undefined, 51 | records: { [key: string]: string } | undefined, 52 | domain: string | undefined, 53 | realTimeUpdates: false 54 | } 55 | ``` 56 | To obtain the resolved resource a series of calls to the blockchain are made, depending on the chain traffic the `resolve` call can take a couple of seconds to be completed. 57 | 58 | 59 | ### Token ID Resolution 60 | 61 | You can also obtain a `IResolvedResource` from a domain NFT tokenId, in this case use the `resolveFromTokenId` function from the `Web3Resolver` instance. 62 | 63 | ```ts 64 | const web3resolver = new Web3Resolver(); 65 | const resolvedDomain = await web3resolver.resolveFromTokenId("web3-domain-nft-tokenId"); 66 | ``` 67 | To find the correct domain provider a call to every available provider is made, using the current priority of the `Web3Resolver` (see Priority chapter). 68 | 69 | To speed up the resolution the `ProviderName` can be provided, in this case only the given provider is checked. 70 | ```ts 71 | const web3resolver = new Web3Resolver(); 72 | const resolvedDomain = await web3resolver.resolveFromTokenId("web3-domain-nft-tokenId", ProviderName.UD); 73 | ``` 74 | 75 | ### Reverse Resolution 76 | The Reverse Resolution allows to get a `IResolvedResource` from a wallet address. 77 | To be able to reverse-resolve a domain the reverse address must have been previously set. 78 | 79 | To set the reverse value you can use the `setReverse` function of a `IResolvedResource`. 80 | 81 | ```ts 82 | const web3resolver = new Web3Resolver(); 83 | const resolvedDomain = await web3resolver.resolve("test.web3domain"); 84 | 85 | if(resolvedDomain){ 86 | const signer = new ethers.Wallet("wallet-secret-key"); 87 | //Set reverse wallet 88 | await resolvedDomain.setReverse(signer); 89 | } 90 | ``` 91 | 92 | To get a `IResolvedResource` you can use the `reverseResolve` function from the `Web3Resolver` instance. 93 | ```ts 94 | //Reverse resolve 95 | const web3resolver = new Web3Resolver(); 96 | const resolvedDomain = await web3resolver.reverseResolve("0x1234...9"); 97 | ``` 98 | 99 | ### Transfer the ownership 100 | 101 | You can transfer the ownership of the domain directly from a `IResolvedResource` instance by calling the `transfer` function. 102 | To do so you need a `ethers.Wallet` to use as signer for the transaction. 103 | 104 | Usually for the transfer to work the signer wallet must be the owner or an approved wallet of the domain. 105 | 106 | ```ts 107 | const resolvedDomain = await web3resolver.resolve("test.web3domain"); 108 | 109 | //Domain transfer 110 | const signer = new ethers.Wallet("wallet-secret-key") 111 | await resolvedDomain.transfer("receiver-wallet-address", signer); 112 | ``` 113 | 114 | ### Get and Set Records 115 | A web3 domain NFT can have a key-value object associated with it and saved on the blochain. Each key-value pair is called record. 116 | 117 | Currently there is no way to get a list of all the keys of an NFT from the blockchain, so providers save all the records of a web3 domain in a centralized public metadata file, stored on the Web2. 118 | The `records` field of a `IResolvedResource` contains the records available on the metadata file. 119 | ```ts 120 | const resolvedDomain = await web3resolver.resolve("test.web3domain"); 121 | 122 | console.log(resolvedDomain.records); 123 | // { 124 | // 'my-eth-address': '0x00000...1', 125 | // 'my-matic-address':'0x00000...2', 126 | // } 127 | ``` 128 | 129 | If you want to interact directly with the blockchain you can set and get the records of the domain from a `IResolvedResource` by calling the `getRecord`, `getManyRecords`, `setRecord` and `setManyRecords`. 130 | To interact with the blockchain records you need to know the key(s) of the key-value pair(s) you want to interact with. 131 | 132 | ```ts 133 | //Read "my-eth-address" and "my-matic-address" records 134 | const addrs = await resolvedDomain.getManyRecords(["my-eth-address", "my-matic-address"]); 135 | console.log(addrs); 136 | // [ 137 | // '0x00000...1', 138 | // '0x00000...2', 139 | // ] 140 | 141 | //Save new value for "my-eth-address" record 142 | const signer = new ethers.Wallet("wallet-secret-key") 143 | await resolvedDomain.setRecord("my-eth-address", "0x00000...3", signer); 144 | 145 | //Read "my-eth-address" record 146 | const updatedAddr = await resolvedDomain.getRecord("my-eth-address"); 147 | console.log(updatedAddr); 148 | //0x00000...3 149 | ``` 150 | 151 | ### Refresh 152 | If you want to update all the fields of a `IResolvedResource` you can call the `refresh` method. 153 | ```ts 154 | const resolvedDomain = await web3resolver.resolve("test.web3domain"); 155 | console.log(resolvedDomain.ownerAddress); 156 | //"0x12345...F" 157 | 158 | //Domain transfer 159 | const signer = new ethers.Wallet("wallet-secret-key") 160 | await resolvedDomain.transfer("0x98765...A", signer); 161 | 162 | await resolvedDomain.refresh(); 163 | console.log(resolvedDomain.ownerAddress); 164 | //"0x98765...A" 165 | ``` 166 | 167 | ## Custom Infura or RCP urls 168 | You can set your own custom Json RPC urls instead of using the default ones from the library. 169 | To do so create a `ConnectionLibrary` instance and provide it to the `Web3Resolver`. 170 | In this way every call to the specified network is made using your custom url. 171 | ```ts 172 | const networkConnection: NetworkConnection = { 173 | networkName: NetworkName.POLYGON, 174 | rpcUrl: "https://your-custom-url" 175 | }; 176 | const connectionLibrary: ConnectionLibrary = new ConnectionLibrary([networkConnection]); 177 | const web3res = new Web3Resolver(connectionLibrary); 178 | ``` 179 | 180 | ## Resolvers Priority 181 | You can set the resolvers order in which a domain **tokenId** is resolved by calling the `setResolversPriority` function. 182 | 183 | At the moment Freename, Unstoppble Domains and ENS don't have overlapping top-level domains, therefore a resolve by **name** does not need a priority. 184 | In the future a priority to resolve names could be necessary, in that case the priority setted by the `setResolversPriority` function will be followed. 185 | 186 | The default `Web3Resolver` priority is: 187 | 188 | 1. Freename 189 | 2. Unstoppable Domains 190 | 3. ENS 191 | ```ts 192 | const web3resolver = new Web3Resolver(); 193 | web3resolver.setResolversPriority([ProviderName.ENS, ProviderName.UD, ProviderName.FREENAME]); 194 | ``` 195 | ## Use a custom ResolverProvider 196 | A `ResolverProvider` is a class that implements the `IResolverProvider` interface. 197 | This class comunicate with the web3 domain NFT registry, reading and writing from and to the appropriate smart contracts. 198 | 199 | 200 | You can create a custom `ResolverProvider` and then use it on a custom `Resolver` (see "Use a custom Resolver" chapter). 201 | ```ts 202 | export class CustomResolverProvider implements IResolverProvider { 203 | //Implement interface 204 | } 205 | ``` 206 | 207 | You can also use the abstract class `BaseResolverProvider` that already handles most of the work. 208 | 209 | ```ts 210 | export class CustomResolverProvider extends BaseResolverProvider implements IResolverProvider { 211 | //Implement abstract methods 212 | } 213 | ``` 214 | 215 | ## Use a custom Resolver 216 | The `Web3Resolver` class is an extension of the `Resolver` class that uses the Freename, Unstoppble Domains and ENS `ResolverProvider` implementations. 217 | 218 | You can configure a custom `Resolver` to use only your preferred resolver providers. 219 | ```ts 220 | export class CustomResolver extends Resolver { 221 | constructor(connectionLibrary?: ConnectionLibrary) { 222 | const freenameResolverProvider = new FreenameResolverProvider({ connectionLibrary: connectionLibrary }); 223 | const customResolverProvider = new CustomResolverProvider({ connectionLibrary: connectionLibrary }); 224 | 225 | super([freenameResolverProvider, customResolverProvider]); 226 | } 227 | } 228 | ``` 229 | 230 | 232 | 233 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3-domain-resolver", 3 | "version": "1.0.7", 4 | "description": "Web3 Library that enable with just one function to resolve domains on multiple web3 providers such as ENS, UnstoppableDomains and Freename", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "start": "tsc && node --trace-warnings ./lib/index.js", 11 | "lint:js:fix": "eslint --ignore-path .gitignore . --fix" 12 | }, 13 | "files": [ 14 | "lib/**/*" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/FreenameDomains/web3-domain-resolver.git" 19 | }, 20 | "keywords": [ 21 | "web3", 22 | "domain", 23 | "resolver", 24 | "freename", 25 | "unstoppable", 26 | "ens" 27 | ], 28 | "author": "Cardinal", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/FreenameDomains/web3-domain-resolver/issues" 32 | }, 33 | "homepage": "https://github.com/FreenameDomains/web3-domain-resolver#readme", 34 | "devDependencies": { 35 | "@types/lodash": "^4.14.182", 36 | "@types/lodash.clonedeep": "^4.5.7", 37 | "@types/lodash.indexof": "^4.0.7", 38 | "@types/lodash.sortby": "^4.7.7", 39 | "@types/node": "^18.6.5", 40 | "@typescript-eslint/eslint-plugin": "^5.33.1", 41 | "@typescript-eslint/parser": "^5.33.1", 42 | "eslint": "^8.22.0" 43 | }, 44 | "peerDependencies": { 45 | "ethers": "^5.6.9" 46 | }, 47 | "dependencies": { 48 | "@unstoppabledomains/resolution": "9.2.2", 49 | "axios": "0.27.2", 50 | "lodash.clonedeep": "4.5.0", 51 | "lodash.indexof": "4.0.5", 52 | "lodash.sortby": "4.7.0", 53 | "punycode": "^2.1.1" 54 | } 55 | } -------------------------------------------------------------------------------- /src/defaults/default-connections.ts: -------------------------------------------------------------------------------- 1 | import { NetworkConnection, NetworkName } from "../networks/connections/network-connection.types"; 2 | 3 | export const DEFAULT_RPC_URL: Record = { 4 | bsc: "https://bsc-dataseed1.ninicoin.io", 5 | ethereum: "https://eth-mainnet.public.blastapi.io", 6 | polygon: "https://rpc-mainnet.matic.quiknode.pro", 7 | aurora: "https://mainnet.aurora.dev", 8 | cronos: "https://evm.cronos.org/", 9 | 10 | //TEST 11 | "polygon-mumbai": "https://rpc-mumbai.matic.today", 12 | hardhat: "http://127.0.0.1:8545/", 13 | zil: "", 14 | }; 15 | 16 | //theese urls can only connect to Freename smart contract addresses 17 | export const DEFAULT_INFURA_RPC_URL: Record = { 18 | polygon: "https://polygon-mainnet.infura.io/v3/de21d7dc37334e459e15e172ee9d45f2", 19 | ethereum: "https://mainnet.infura.io/v3/de21d7dc37334e459e15e172ee9d45f2", 20 | aurora: "https://aurora-mainnet.infura.io/v3/de21d7dc37334e459e15e172ee9d45f2", 21 | "polygon-mumbai": "", 22 | bsc: "", 23 | zil: "", 24 | hardhat: "", 25 | cronos: "", 26 | }; 27 | 28 | export class DefaultTools { 29 | static getDefaultConnection(networkName: NetworkName, options: { infuraIfAvailable?: boolean } = {}): NetworkConnection { 30 | const { infuraIfAvailable = false } = options; 31 | let url: string | undefined; 32 | 33 | if (infuraIfAvailable) { 34 | url = DEFAULT_INFURA_RPC_URL[networkName]; 35 | } 36 | 37 | if (!url) { 38 | url = DEFAULT_RPC_URL[networkName]; 39 | } 40 | 41 | return { 42 | networkName: networkName, 43 | rpcUrl: url, 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /src/defaults/erc721.ud.proxy.abi.ts: -------------------------------------------------------------------------------- 1 | export const ERC721_UD_PROXY_ABI = [{ "anonymous": false, "inputs": [{ "indexed": false, "internalType": "address", "name": "previousAdmin", "type": "address" }, { "indexed": false, "internalType": "address", "name": "newAdmin", "type": "address" }], "name": "AdminChanged", "type": "event" }, { "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": false, "internalType": "uint8", "name": "version", "type": "uint8" }], "name": "Initialized", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "indexed": true, "internalType": "string", "name": "keyIndex", "type": "string" }, { "indexed": false, "internalType": "string", "name": "key", "type": "string" }], "name": "NewKey", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "indexed": false, "internalType": "string", "name": "uri", "type": "string" }], "name": "NewURI", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "internalType": "string", "name": "prefix", "type": "string" }], "name": "NewURIPrefix", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "addr", "type": "address" }], "name": "RemoveReverse", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "ResetRecords", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "indexed": true, "internalType": "string", "name": "keyIndex", "type": "string" }, { "indexed": true, "internalType": "string", "name": "valueIndex", "type": "string" }, { "indexed": false, "internalType": "string", "name": "key", "type": "string" }, { "indexed": false, "internalType": "string", "name": "value", "type": "string" }], "name": "Set", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "addr", "type": "address" }, { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "SetReverse", "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" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "implementation", "type": "address" }], "name": "Upgraded", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "user", "type": "address" }, { "indexed": false, "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }], "name": "WithdrawnBatch", "type": "event" }, { "inputs": [], "name": "BATCH_LIMIT", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "NAME", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "VERSION", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "addKey", "outputs": [], "stateMutability": "nonpayable", "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": "owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "burn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "label", "type": "string" }], "name": "childIdOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "pure", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "user", "type": "address" }, { "internalType": "bytes", "name": "depositData", "type": "bytes" }], "name": "deposit", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "depositToPolygon", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "components": [{ "internalType": "address", "name": "from", "type": "address" }, { "internalType": "uint256", "name": "nonce", "type": "uint256" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "internalType": "struct IForwarder.ForwardRequest", "name": "req", "type": "tuple" }, { "internalType": "bytes", "name": "signature", "type": "bytes" }], "name": "execute", "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "exists", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "get", "outputs": [{ "internalType": "string", "name": "value", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "getApproved", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "keyHash", "type": "uint256" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "getByHash", "outputs": [{ "internalType": "string", "name": "key", "type": "string" }, { "internalType": "string", "name": "value", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "keyHash", "type": "uint256" }], "name": "getKey", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256[]", "name": "hashes", "type": "uint256[]" }], "name": "getKeys", "outputs": [{ "internalType": "string[]", "name": "values", "type": "string[]" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "getMany", "outputs": [{ "internalType": "string[]", "name": "values", "type": "string[]" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256[]", "name": "keyHashes", "type": "uint256[]" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "getManyByHash", "outputs": [{ "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "mintingManager", "type": "address" }], "name": "initialize", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "operator", "type": "address" }], "name": "isApprovedForAll", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "isApprovedOrOwner", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "forwarder", "type": "address" }], "name": "isTrustedForwarder", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "user", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "mint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "user", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "bytes", "name": "", "type": "bytes" }], "name": "mint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "uri", "type": "string" }], "name": "mint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "uri", "type": "string" }, { "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }], "name": "mintWithRecords", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "name", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "nonceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "", "type": "address" }, { "internalType": "address", "name": "from", "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": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "ownerOf", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "reconfigure", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "removeReverse", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "reset", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "resolverOf", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "addr", "type": "address" }], "name": "reverseOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "root", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "pure", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "uri", "type": "string" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "name": "safeMint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "uri", "type": "string" }], "name": "safeMint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "uri", "type": "string" }, { "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }], "name": "safeMintWithRecords", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "uri", "type": "string" }, { "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "name": "safeMintWithRecords", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "safeTransferFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "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": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }, { "internalType": "string", "name": "value", "type": "string" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "set", "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": "uint256", "name": "keyHash", "type": "uint256" }, { "internalType": "string", "name": "value", "type": "string" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "setByHash", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "registry", "type": "address" }], "name": "setCNSRegistry", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "clientChainManager", "type": "address" }], "name": "setChildChainManager", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "setMany", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256[]", "name": "keyHashes", "type": "uint256[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "setManyByHash", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "setOwner", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "setReverse", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "rootChainManager", "type": "address" }], "name": "setRootChainManager", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "string", "name": "prefix", "type": "string" }], "name": "setTokenURIPrefix", "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": [], "name": "symbol", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "tokenURI", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "transferFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "components": [{ "internalType": "address", "name": "from", "type": "address" }, { "internalType": "uint256", "name": "nonce", "type": "uint256" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "internalType": "struct IForwarder.ForwardRequest", "name": "req", "type": "tuple" }, { "internalType": "bytes", "name": "signature", "type": "bytes" }], "name": "verify", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "withdraw", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }], "name": "withdrawBatch", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "bytes", "name": "inputData", "type": "bytes" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string[]", "name": "keys", "type": "string[]" }, { "internalType": "string[]", "name": "values", "type": "string[]" }], "name": "withdrawFromPolygon", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "withdrawWithMetadata", "outputs": [], "stateMutability": "nonpayable", "type": "function" }]; -------------------------------------------------------------------------------- /src/defaults/web3-resolver.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionLibrary } from "../networks/connections/connection-library"; 2 | import { ENSResolverProvider } from "../resolver-providers/providers/ens/ens-resolver-provider"; 3 | import { FreenameResolverProvider } from "../resolver-providers/providers/freename/freename-resolver-provider"; 4 | import { UDResolverProvider } from "../resolver-providers/providers/ud/ud-resolver-provider"; 5 | import { Resolver } from "../resolvers/resolver"; 6 | 7 | export class Web3Resolver extends Resolver { 8 | constructor(connectionLibrary?: ConnectionLibrary) { 9 | const freenameResolverProvider = new FreenameResolverProvider({ connectionLibrary: connectionLibrary }); 10 | const udResolverProvider = new UDResolverProvider({ connectionLibrary: connectionLibrary }); 11 | const ensResolverProvider = new ENSResolverProvider({ connectionLibrary: connectionLibrary }); 12 | 13 | super([freenameResolverProvider, udResolverProvider, ensResolverProvider]); 14 | } 15 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./defaults/web3-resolver"; 2 | 3 | export * from "./networks/connections/connection-library"; 4 | export * from "./networks/connections/contract-connection"; 5 | export * from "./networks/connections/network-connection.types"; 6 | 7 | export * from "./resolver-providers/resolver-provider.interface"; 8 | export * from "./resolver-providers/providers/ens/ens-resolver-provider"; 9 | export * from "./resolver-providers/providers/freename/freename-resolver-provider"; 10 | export * from "./resolver-providers/providers/ud/ud-resolver-provider"; 11 | export * from "./resolver-providers/providers/base-resolver-provider"; 12 | 13 | export * from "./resolvers/resolver"; 14 | export * from "./resolvers/resolved-resource/resolved-resource.interface"; 15 | export * from "./resolvers/resolved-resource/resolved-resource"; 16 | export * from "./resolvers/types/resolved-resource-type"; 17 | export * from "./resolvers/types/resolver-name"; 18 | 19 | export * from "./tools/name-tools"; 20 | export * from "./tools/name-tools.types"; -------------------------------------------------------------------------------- /src/networks/connections/connection-library.ts: -------------------------------------------------------------------------------- 1 | import { NetworkConnection, NetworkName } from "./network-connection.types"; 2 | 3 | export class ConnectionLibrary { 4 | 5 | constructor(connections: NetworkConnection[]) { 6 | this._connections = connections || []; 7 | } 8 | 9 | private _connections: NetworkConnection[]; 10 | public get connections(): NetworkConnection[] { 11 | return this._connections; 12 | } 13 | public set connections(value: NetworkConnection[]) { 14 | this._connections = value; 15 | } 16 | 17 | public getConnection(network: NetworkName | string) { 18 | if (this._connections && network) { 19 | const connection = this._connections.find(x => x.networkName == network); 20 | return connection; 21 | } 22 | return undefined; 23 | } 24 | 25 | public setConnection(connection: NetworkConnection) { 26 | if (this._connections && connection.networkName && connection.rpcUrl) { 27 | const indexFound = this._connections.findIndex(x => x.networkName == connection.networkName); 28 | const newConnection: NetworkConnection = { 29 | networkName: connection.networkName, 30 | rpcUrl: connection.rpcUrl, 31 | }; 32 | if (indexFound !== -1) { 33 | this._connections[indexFound] = newConnection; 34 | } else { 35 | this._connections.push(newConnection); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/networks/connections/contract-connection.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { NetworkConnection, NetworkName } from "./network-connection.types"; 3 | 4 | export class ContractConnection { 5 | 6 | constructor( 7 | connection: NetworkConnection, 8 | address: string, 9 | abi: ethers.ContractInterface, 10 | ) { 11 | this._connection = connection; 12 | this._address = address; 13 | this._provider = new ethers.providers.JsonRpcProvider(connection.rpcUrl); 14 | this._abi = abi; 15 | this._contract = new ethers.Contract(address, abi, this._provider); 16 | } 17 | 18 | private _connection: NetworkConnection; 19 | public get connection(): NetworkConnection { 20 | return this._connection; 21 | } 22 | 23 | private _address: string; 24 | public get address(): string { 25 | return this._address; 26 | } 27 | 28 | private _provider: ethers.providers.Provider; 29 | public get provider(): ethers.providers.Provider { 30 | return this._provider; 31 | } 32 | 33 | private _contract: ethers.Contract; 34 | public get contract(): ethers.Contract { 35 | return this._contract; 36 | } 37 | 38 | public get network(): NetworkName | string { 39 | return this._connection.networkName; 40 | } 41 | 42 | private _abi: ethers.ContractInterface; 43 | } -------------------------------------------------------------------------------- /src/networks/connections/network-connection.types.ts: -------------------------------------------------------------------------------- 1 | export enum NetworkName { 2 | POLYGON = "polygon", 3 | POLYGON_MUMBAI = "polygon-mumbai", 4 | ETHEREUM = "ethereum", 5 | BSC = "bsc", 6 | ZILLIQA = "zil", 7 | HARDHAT = "hardhat", 8 | AURORA = "aurora", 9 | CRONOS = "cronos" 10 | } 11 | 12 | export type NetworkConnection = { 13 | networkName: NetworkName | string, 14 | rpcUrl: string 15 | } -------------------------------------------------------------------------------- /src/resolver-providers/providers/base-resolver-provider.ts: -------------------------------------------------------------------------------- 1 | import { Contract, ethers } from "ethers"; 2 | import { ConnectionLibrary } from "../../networks/connections/connection-library"; 3 | import { ContractConnection } from "../../networks/connections/contract-connection"; 4 | import { NetworkName } from "../../networks/connections/network-connection.types"; 5 | import { ResolvedResource } from "../../resolvers/resolved-resource/resolved-resource"; 6 | import { IResolvedResource } from "../../resolvers/resolved-resource/resolved-resource.interface"; 7 | import { ResolvedResourceType } from "../../resolvers/types/resolved-resource-type"; 8 | import { ProviderName } from "../../resolvers/types/resolver-name"; 9 | import { ApiCaller } from "../../tools/api-caller"; 10 | import { NameTools } from "../../tools/name-tools"; 11 | import { MappedName } from "../../tools/name-tools.types"; 12 | import { IResolverProvider } from "../resolver-provider.interface"; 13 | 14 | export abstract class BaseResolverProvider implements IResolverProvider { 15 | constructor( 16 | name: ProviderName | string, 17 | supportedTlds: string[], 18 | readContractConnections: ContractConnection[], 19 | writeContractConnections: ContractConnection[]) { 20 | this._name = name; 21 | this._supportedTlds = supportedTlds; 22 | this._readContractConnections = readContractConnections; 23 | this._writeContractConnections = writeContractConnections; 24 | } 25 | 26 | public get supportedNetworks(): (NetworkName | string)[] { 27 | return this._readContractConnections.map(x => x.network); 28 | } 29 | 30 | private _writeContractConnections: ContractConnection[]; 31 | public get writeContractConnections(): ContractConnection[] { 32 | return this._writeContractConnections; 33 | } 34 | public set writeContractConnections(value: ContractConnection[]) { 35 | this._writeContractConnections = value; 36 | } 37 | 38 | private _readContractConnections: ContractConnection[]; 39 | protected get readContractConnections(): ContractConnection[] { 40 | return this._readContractConnections; 41 | } 42 | protected set readContractConnections(value: ContractConnection[]) { 43 | this._readContractConnections = value; 44 | } 45 | 46 | protected _connectionLibrary?: ConnectionLibrary | undefined; 47 | public get connectionLibrary(): ConnectionLibrary | undefined { 48 | return this._connectionLibrary; 49 | } 50 | public set connectionLibrary(value: ConnectionLibrary | undefined) { 51 | this._connectionLibrary = value; 52 | } 53 | 54 | protected _name: ProviderName | string; 55 | public get name(): ProviderName | string { 56 | return this._name; 57 | } 58 | public set name(value: ProviderName | string) { 59 | this._name = value; 60 | } 61 | 62 | protected _supportedTlds: string[]; 63 | public get supportedTlds(): string[] { 64 | return this._supportedTlds; 65 | } 66 | public set supportedTlds(value: string[]) { 67 | this._supportedTlds = value; 68 | } 69 | 70 | protected getReadContractConnection(networkName: NetworkName | string): ContractConnection | undefined { 71 | return this.readContractConnections.find(x => x.network == networkName); 72 | } 73 | 74 | protected getWriteContractConnection(networkName: NetworkName | string): ContractConnection | undefined { 75 | return this.writeContractConnections.find(x => x.network == networkName); 76 | } 77 | 78 | protected getWriteContractWithSigner(networkName: NetworkName | string, signer: ethers.Signer) { 79 | const writeContractConnection = this.getWriteContractConnection(networkName); 80 | if (!writeContractConnection) { 81 | return undefined; 82 | } 83 | 84 | let signerToUse = signer; 85 | if (!signer.provider) { 86 | signerToUse = signer.connect(writeContractConnection.provider); 87 | } 88 | const contractConnected = writeContractConnection.contract.connect(signerToUse); 89 | return contractConnected; 90 | } 91 | 92 | public async resolve(domainOrTld: string): Promise { 93 | try { 94 | const mappedName = NameTools.mapName(domainOrTld); 95 | if (!mappedName) { 96 | return undefined; 97 | } 98 | 99 | const tokenId = await this.generateTokenId(mappedName); 100 | const network = await this.getNetworkFromName(mappedName); 101 | if (!tokenId) { 102 | return undefined; 103 | } 104 | 105 | return this.generateResolvedResource(mappedName, tokenId, network); 106 | } 107 | catch { 108 | return undefined; 109 | } 110 | } 111 | 112 | public async resolveFromTokenId(tokenId: string, network?: NetworkName | string | undefined): Promise { 113 | try { 114 | const name = await this.getNameFromTokenId(tokenId, network); 115 | if (!name) { 116 | return undefined; 117 | } 118 | 119 | const mappedName = NameTools.mapName(name); 120 | if (!mappedName) { 121 | 122 | return undefined; 123 | } 124 | 125 | return this.generateResolvedResource(mappedName, tokenId, network); 126 | } 127 | catch { 128 | return undefined; 129 | } 130 | } 131 | 132 | public async reverseResolve(address: string, network?: NetworkName | string | undefined): Promise { 133 | let readContracts: ContractConnection[] = []; 134 | if (network) { 135 | const readContract = this.getReadContractConnection(network); 136 | if (readContract) { 137 | readContracts.push(readContract); 138 | } 139 | } else { 140 | readContracts = this.readContractConnections; 141 | } 142 | 143 | for (const readContractConnection of readContracts) { 144 | try { 145 | const reverseOfRes: ethers.BigNumber = await readContractConnection.contract.reverseOf(address); 146 | if (reverseOfRes) { 147 | const tokenId = reverseOfRes.toString(); 148 | if (tokenId !== "0") { 149 | return tokenId; 150 | } 151 | } 152 | } 153 | catch (e) { 154 | continue; 155 | } 156 | } 157 | return undefined; 158 | } 159 | 160 | public async getTokenUri(tokenId: string, network?: NetworkName | string | undefined): Promise { 161 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 162 | if (!readContractConnection) { 163 | return undefined; 164 | } 165 | try { 166 | return await readContractConnection.contract.tokenURI(tokenId); 167 | } catch { 168 | return undefined; 169 | } 170 | } 171 | 172 | public async getMetadata(tokenId: string, network?: NetworkName | string | undefined): Promise { 173 | const tokenUri = await this.getTokenUri(tokenId, network); 174 | if (!tokenUri) { 175 | return undefined; 176 | } 177 | try { 178 | return ApiCaller.getHttpsCall(tokenUri); 179 | } 180 | catch { 181 | return undefined; 182 | } 183 | } 184 | 185 | public async getImageUrl(tokenId: string, network?: NetworkName | string | undefined): Promise { 186 | const metadata = await this.getMetadata(tokenId, network); 187 | return metadata?.image; 188 | } 189 | 190 | public async exists(tokenId: string, network?: NetworkName | string | undefined): Promise { 191 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 192 | if (!readContractConnection) { 193 | return false; 194 | } 195 | try { 196 | return await readContractConnection.contract.exists(tokenId); 197 | } 198 | catch { 199 | return false; 200 | } 201 | } 202 | 203 | public async getOwnerAddress(tokenId: string, network?: NetworkName | string | undefined): Promise { 204 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 205 | if (!readContractConnection) { 206 | return undefined; 207 | } 208 | try { 209 | return await readContractConnection.contract.ownerOf(tokenId); 210 | } 211 | catch { 212 | return undefined; 213 | } 214 | } 215 | 216 | public async isApprovedOrOwner(tokenId: string, addressToCheck: string, network?: NetworkName | string | undefined): Promise { 217 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 218 | if (!readContractConnection) { 219 | return false; 220 | } 221 | 222 | try { 223 | return await readContractConnection.contract.isApprovedOrOwner(tokenId, addressToCheck); 224 | } 225 | catch { 226 | return false; 227 | } 228 | } 229 | 230 | public async getRecord(tokenId: string, key: string, network?: NetworkName | string | undefined): Promise { 231 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 232 | if (!readContractConnection) { 233 | return undefined; 234 | } 235 | 236 | try { 237 | return await readContractConnection.contract.get(key, tokenId); 238 | } 239 | catch { 240 | return undefined; 241 | } 242 | } 243 | 244 | public async getManyRecords(tokenId: string, keys: string[], network?: NetworkName | string | undefined): Promise { 245 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 246 | if (!readContractConnection) { 247 | return undefined; 248 | } 249 | 250 | try { 251 | return await readContractConnection.contract.getMany(keys, tokenId); 252 | } catch { 253 | return undefined; 254 | } 255 | } 256 | 257 | public async transfer(resource: IResolvedResource, addressTo: string, signer: ethers.Signer): Promise { 258 | const writeContract = this.getWriteContractWithSigner(resource.network, signer); 259 | if (!writeContract) { 260 | return false; 261 | } 262 | 263 | try { 264 | const tx = await writeContract.transferFrom(resource.ownerAddress, addressTo, resource.tokenId); 265 | const approveReceipt = await tx.wait(); 266 | if (approveReceipt) { 267 | return true; 268 | } 269 | return false; 270 | } catch (e) { 271 | return false; 272 | } 273 | } 274 | 275 | public async setApproved(resource: IResolvedResource, addessToApprove: string, signer: ethers.Signer): Promise { 276 | const writeContract = this.getWriteContractWithSigner(resource.network, signer); 277 | if (!writeContract) { 278 | return false; 279 | } 280 | 281 | try { 282 | const tx = await writeContract.approve(addessToApprove, resource.tokenId); 283 | const approveReceipt = await tx.wait(); 284 | if (approveReceipt) { 285 | return true; 286 | } 287 | return false; 288 | } catch (e) { 289 | return false; 290 | } 291 | } 292 | 293 | public async setRecord(resource: IResolvedResource, key: string, value: string, signer: ethers.Signer): Promise { 294 | const writeContract = this.getWriteContractWithSigner(resource.network, signer); 295 | if (!writeContract) { 296 | return false; 297 | } 298 | 299 | try { 300 | const tx = await writeContract.set(key, value, resource.tokenId); 301 | const approveReceipt = await tx.wait(); 302 | if (approveReceipt) { 303 | return true; 304 | } 305 | return false; 306 | } catch (e) { 307 | return false; 308 | } 309 | } 310 | 311 | public async setRecords(resource: IResolvedResource, keys: string[], values: string[], signer: ethers.Signer): Promise { 312 | const writeContract = this.getWriteContractWithSigner(resource.network, signer); 313 | if (!writeContract) { 314 | return false; 315 | } 316 | 317 | try { 318 | const tx = await writeContract.setMany(keys, values, resource.tokenId); 319 | const approveReceipt = await tx.wait(); 320 | if (approveReceipt) { 321 | return true; 322 | } 323 | return false; 324 | } catch (e) { 325 | return false; 326 | } 327 | } 328 | 329 | public async setReverse(resource: IResolvedResource, signer: ethers.Signer): Promise { 330 | const writeContract = this.getWriteContractWithSigner(resource.network, signer); 331 | if (!writeContract) { 332 | return false; 333 | } 334 | 335 | try { 336 | const tx = await writeContract.setReverse(resource.tokenId); 337 | const approveReceipt = await tx.wait(); 338 | if (approveReceipt) { 339 | return true; 340 | } 341 | return false; 342 | } catch (e) { 343 | return false; 344 | } 345 | } 346 | 347 | protected async generateResolvedResource(mappedName: MappedName, tokenId: string, network?: NetworkName | string): Promise { 348 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 349 | if (!readContractConnection) { 350 | 351 | return undefined; 352 | } 353 | 354 | const writeContractConnection = await this.getWriteContractConnection(readContractConnection.network); 355 | if (!writeContractConnection) { 356 | 357 | return undefined; 358 | } 359 | 360 | const exists = await this.exists(tokenId, readContractConnection.network); 361 | if (!exists) { 362 | 363 | return undefined; 364 | } 365 | 366 | const ownerAddress = await this.getOwnerAddress(tokenId, readContractConnection.network); 367 | if (!ownerAddress) { 368 | 369 | return undefined; 370 | } 371 | 372 | const tokenUri = await this.getTokenUri(tokenId, readContractConnection.network); 373 | 374 | const metadata = await this.getMetadata(tokenId, readContractConnection.network); 375 | 376 | const records = await this.getRecords(tokenId, readContractConnection.network); 377 | 378 | const resolverResourceType: ResolvedResourceType = NameTools.getResolvedResourceType(mappedName.type); 379 | 380 | const resolvedResource = new ResolvedResource({ 381 | fullname: mappedName.fullname, 382 | tld: mappedName.tld, 383 | type: resolverResourceType, 384 | tokenId: tokenId, 385 | resolverName: this._name, 386 | resolverProvider: this, 387 | imageUrl: metadata?.image_url, 388 | metadataUri: tokenUri, 389 | network: readContractConnection.network, 390 | ownerAddress: ownerAddress, 391 | proxyReaderAddress: readContractConnection.address, 392 | proxyWriterAddress: writeContractConnection.address, 393 | domain: mappedName.domain, 394 | metadata: metadata, 395 | records: records, 396 | }); 397 | 398 | return resolvedResource; 399 | } 400 | 401 | protected async getTokenIdNetwork(tokenId: string): Promise { 402 | for (const readContractConnection of this.readContractConnections) { 403 | try { 404 | const exists = await readContractConnection.contract.exists(tokenId); 405 | if (exists) { 406 | return readContractConnection.network; 407 | } 408 | } catch (error) { 409 | continue; 410 | } 411 | } 412 | return undefined; 413 | } 414 | 415 | protected async getReadContractConnectionFromToken(tokenId: string, network?: NetworkName | string | undefined): Promise { 416 | let networkToUse: NetworkName | string | undefined = network; 417 | if (!network) { 418 | networkToUse = await this.getTokenIdNetwork(tokenId); 419 | } 420 | if (!networkToUse) { 421 | return undefined; 422 | } 423 | const readContractConnection = this.getReadContractConnection(networkToUse); 424 | if (!readContractConnection) { 425 | return undefined; 426 | } 427 | 428 | return readContractConnection; 429 | } 430 | 431 | abstract generateTokenId(mappedName: MappedName): Promise 432 | 433 | abstract getNetworkFromName(mappedName: MappedName): Promise 434 | 435 | abstract getRecords(tokenId: string, network?: NetworkName | string | undefined): Promise<{ [key: string]: string } | undefined> 436 | 437 | abstract getNameFromTokenId(tokenId: string, network?: NetworkName | string | undefined): Promise 438 | } -------------------------------------------------------------------------------- /src/resolver-providers/providers/ens/ens-resolver-provider.consts.ts: -------------------------------------------------------------------------------- 1 | export const ENS_SUPPORTED_TLDS: string[] = ["eth"]; 2 | 3 | export const ENS_CONTRACT_ADDRESS = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85"; 4 | export const ENS_MAINNET_METADATA_URL = "https://metadata.ens.domains/mainnet/"; 5 | 6 | export const ENS_ABI = [{ "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" }]; -------------------------------------------------------------------------------- /src/resolver-providers/providers/ens/ens-resolver-provider.ts: -------------------------------------------------------------------------------- 1 | import { NetworkName } from "../../../networks/connections/network-connection.types"; 2 | import { ProviderName } from "../../../resolvers/types/resolver-name"; 3 | import { MappedName } from "../../../tools/name-tools.types"; 4 | import { IResolverProvider } from "../../resolver-provider.interface"; 5 | import { BaseResolverProvider } from "../base-resolver-provider"; 6 | import { ENS_ABI, ENS_CONTRACT_ADDRESS, ENS_MAINNET_METADATA_URL, ENS_SUPPORTED_TLDS } from "./ens-resolver-provider.consts"; 7 | import { ContractConnection } from "../../../networks/connections/contract-connection"; 8 | import { ethers } from "ethers"; 9 | import { ApiCaller } from "../../../tools/api-caller"; 10 | import { ConnectionLibrary } from "../../../networks/connections/connection-library"; 11 | import { DefaultTools } from "../../../defaults/default-connections"; 12 | import { IResolvedResource } from "../../../resolvers/resolved-resource/resolved-resource.interface"; 13 | import { NameTools } from "../../../tools/name-tools"; 14 | 15 | export class ENSResolverProvider extends BaseResolverProvider implements IResolverProvider { 16 | 17 | constructor(options: { connectionLibrary?: ConnectionLibrary } = {}) { 18 | const ethConnection = options.connectionLibrary?.getConnection(NetworkName.ETHEREUM) || DefaultTools.getDefaultConnection(NetworkName.ETHEREUM); 19 | const ensContract = new ContractConnection(ethConnection, ENS_CONTRACT_ADDRESS, ENS_ABI); 20 | 21 | super(ProviderName.ENS, ENS_SUPPORTED_TLDS, [ensContract], [ensContract]); 22 | } 23 | 24 | public override async reverseResolve(address: string, network?: NetworkName | string | undefined): Promise { 25 | let readContracts: ContractConnection[] = []; 26 | if (network) { 27 | const readContract = this.getReadContractConnection(network); 28 | if (readContract) { 29 | readContracts.push(readContract); 30 | } 31 | } else { 32 | readContracts = this.readContractConnections; 33 | } 34 | 35 | for (const readContractConnection of readContracts) { 36 | try { 37 | const ensName = await readContractConnection.provider.lookupAddress(address); 38 | if (ensName) { 39 | const mappedName = NameTools.mapName(ensName); 40 | if (!mappedName) { 41 | return undefined; 42 | } 43 | 44 | const tokenId = await this.generateTokenId(mappedName); 45 | if (!tokenId) { 46 | return undefined; 47 | } 48 | 49 | const owner = await this.getOwnerAddress(tokenId, network); 50 | if (!owner) { 51 | return undefined; 52 | } 53 | 54 | //check https://docs.ens.domains/dapp-developer-guide/resolving-names#reverse-resolution 55 | if (owner !== address) { 56 | return undefined; 57 | } 58 | 59 | return tokenId; 60 | } 61 | } 62 | catch (e) { 63 | continue; 64 | } 65 | } 66 | return undefined; 67 | } 68 | 69 | public override async exists(tokenId: string, network?: NetworkName | undefined): Promise { 70 | const readContractConnection = await super.getReadContractConnectionFromToken(tokenId, network); 71 | if (!readContractConnection) { 72 | return false; 73 | } 74 | try { 75 | const res = await readContractConnection.contract.available(tokenId); 76 | return !res; 77 | } catch { 78 | return false; 79 | } 80 | } 81 | 82 | public override async generateTokenId(mappedName: MappedName): Promise { 83 | if (!mappedName.domain) { 84 | return undefined; 85 | } 86 | try { 87 | const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(mappedName.domain)); 88 | const tokenId = ethers.BigNumber.from(labelHash).toString(); 89 | return tokenId; 90 | } catch { 91 | return undefined; 92 | } 93 | } 94 | 95 | public override async getTokenUri(tokenId: string, network?: NetworkName | undefined): Promise { 96 | //There is no token uri https://docs.ens.domains/dapp-developer-guide/ens-as-nft#metadata 97 | return undefined; 98 | } 99 | 100 | public override async getMetadata(tokenId: string, network?: NetworkName | undefined): Promise { 101 | const readContractConnection = await super.getReadContractConnectionFromToken(tokenId, network); 102 | if (!readContractConnection) { 103 | return false; 104 | } 105 | //https://metadata.ens.domains/docs 106 | const metadataUrl = ENS_MAINNET_METADATA_URL + readContractConnection.address + "/" + tokenId; 107 | return ApiCaller.getHttpsCall(metadataUrl); 108 | } 109 | 110 | public override async getNetworkFromName(mappedName: MappedName): Promise { 111 | return NetworkName.ETHEREUM; 112 | } 113 | 114 | public override async getRecords(tokenId: string): Promise<{ [key: string]: string; } | undefined> { 115 | return undefined; 116 | } 117 | 118 | public override async getNameFromTokenId(tokenId: string, network?: NetworkName | undefined): Promise { 119 | const metadata = await this.getMetadata(tokenId, network); 120 | return metadata?.name; 121 | } 122 | 123 | public override async setRecord(resource: IResolvedResource, key: string, value: string, signer: ethers.Signer): Promise { 124 | return false; 125 | } 126 | 127 | public override async getManyRecords(tokenId: string, keys: string[], network?: NetworkName | undefined): Promise { 128 | return undefined; 129 | } 130 | 131 | protected override async getTokenIdNetwork(tokenId: string): Promise { 132 | for (const readContractConnection of this.readContractConnections) { 133 | try { 134 | const res = await readContractConnection.contract.available(tokenId); 135 | if (!res) { 136 | return readContractConnection.network; 137 | } 138 | } catch { 139 | continue; 140 | } 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /src/resolver-providers/providers/freename/freename-resolver-provider.consts.ts: -------------------------------------------------------------------------------- 1 | import { NetworkName } from "../../../networks/connections/network-connection.types"; 2 | 3 | export const FNS_ABI = [ 4 | { 5 | "anonymous": false, 6 | "inputs": [ 7 | { 8 | "indexed": false, 9 | "internalType": "address", 10 | "name": "previousAdmin", 11 | "type": "address", 12 | }, 13 | { 14 | "indexed": false, 15 | "internalType": "address", 16 | "name": "newAdmin", 17 | "type": "address", 18 | }, 19 | ], 20 | "name": "AdminChanged", 21 | "type": "event", 22 | }, 23 | { 24 | "anonymous": false, 25 | "inputs": [ 26 | { 27 | "indexed": true, 28 | "internalType": "address", 29 | "name": "owner", 30 | "type": "address", 31 | }, 32 | { 33 | "indexed": true, 34 | "internalType": "address", 35 | "name": "approved", 36 | "type": "address", 37 | }, 38 | { 39 | "indexed": true, 40 | "internalType": "uint256", 41 | "name": "tokenId", 42 | "type": "uint256", 43 | }, 44 | ], 45 | "name": "Approval", 46 | "type": "event", 47 | }, 48 | { 49 | "anonymous": false, 50 | "inputs": [ 51 | { 52 | "indexed": true, 53 | "internalType": "address", 54 | "name": "owner", 55 | "type": "address", 56 | }, 57 | { 58 | "indexed": true, 59 | "internalType": "address", 60 | "name": "operator", 61 | "type": "address", 62 | }, 63 | { 64 | "indexed": false, 65 | "internalType": "bool", 66 | "name": "approved", 67 | "type": "bool", 68 | }, 69 | ], 70 | "name": "ApprovalForAll", 71 | "type": "event", 72 | }, 73 | { 74 | "anonymous": false, 75 | "inputs": [ 76 | { 77 | "indexed": false, 78 | "internalType": "uint8", 79 | "name": "version", 80 | "type": "uint8", 81 | }, 82 | ], 83 | "name": "Initialized", 84 | "type": "event", 85 | }, 86 | { 87 | "anonymous": false, 88 | "inputs": [ 89 | { 90 | "indexed": true, 91 | "internalType": "uint256", 92 | "name": "tokenId", 93 | "type": "uint256", 94 | }, 95 | { 96 | "indexed": true, 97 | "internalType": "string", 98 | "name": "keyIndex", 99 | "type": "string", 100 | }, 101 | { 102 | "indexed": false, 103 | "internalType": "string", 104 | "name": "key", 105 | "type": "string", 106 | }, 107 | ], 108 | "name": "NewKey", 109 | "type": "event", 110 | }, 111 | { 112 | "anonymous": false, 113 | "inputs": [ 114 | { 115 | "indexed": true, 116 | "internalType": "uint256", 117 | "name": "tokenId", 118 | "type": "uint256", 119 | }, 120 | { 121 | "indexed": false, 122 | "internalType": "string", 123 | "name": "uri", 124 | "type": "string", 125 | }, 126 | ], 127 | "name": "NewURI", 128 | "type": "event", 129 | }, 130 | { 131 | "anonymous": false, 132 | "inputs": [ 133 | { 134 | "indexed": false, 135 | "internalType": "string", 136 | "name": "prefix", 137 | "type": "string", 138 | }, 139 | ], 140 | "name": "NewURIPrefix", 141 | "type": "event", 142 | }, 143 | { 144 | "anonymous": false, 145 | "inputs": [ 146 | { 147 | "indexed": true, 148 | "internalType": "address", 149 | "name": "addr", 150 | "type": "address", 151 | }, 152 | ], 153 | "name": "RemoveReverse", 154 | "type": "event", 155 | }, 156 | { 157 | "anonymous": false, 158 | "inputs": [ 159 | { 160 | "indexed": true, 161 | "internalType": "uint256", 162 | "name": "tokenId", 163 | "type": "uint256", 164 | }, 165 | ], 166 | "name": "ResetRecords", 167 | "type": "event", 168 | }, 169 | { 170 | "anonymous": false, 171 | "inputs": [ 172 | { 173 | "indexed": true, 174 | "internalType": "uint256", 175 | "name": "tokenId", 176 | "type": "uint256", 177 | }, 178 | { 179 | "indexed": true, 180 | "internalType": "string", 181 | "name": "keyIndex", 182 | "type": "string", 183 | }, 184 | { 185 | "indexed": true, 186 | "internalType": "string", 187 | "name": "valueIndex", 188 | "type": "string", 189 | }, 190 | { 191 | "indexed": false, 192 | "internalType": "string", 193 | "name": "key", 194 | "type": "string", 195 | }, 196 | { 197 | "indexed": false, 198 | "internalType": "string", 199 | "name": "value", 200 | "type": "string", 201 | }, 202 | ], 203 | "name": "SetRecord", 204 | "type": "event", 205 | }, 206 | { 207 | "anonymous": false, 208 | "inputs": [ 209 | { 210 | "indexed": true, 211 | "internalType": "address", 212 | "name": "addr", 213 | "type": "address", 214 | }, 215 | { 216 | "indexed": true, 217 | "internalType": "uint256", 218 | "name": "tokenId", 219 | "type": "uint256", 220 | }, 221 | ], 222 | "name": "SetReverse", 223 | "type": "event", 224 | }, 225 | { 226 | "anonymous": false, 227 | "inputs": [ 228 | { 229 | "indexed": true, 230 | "internalType": "address", 231 | "name": "from", 232 | "type": "address", 233 | }, 234 | { 235 | "indexed": true, 236 | "internalType": "address", 237 | "name": "to", 238 | "type": "address", 239 | }, 240 | { 241 | "indexed": true, 242 | "internalType": "uint256", 243 | "name": "tokenId", 244 | "type": "uint256", 245 | }, 246 | ], 247 | "name": "Transfer", 248 | "type": "event", 249 | }, 250 | { 251 | "anonymous": false, 252 | "inputs": [ 253 | { 254 | "indexed": true, 255 | "internalType": "address", 256 | "name": "implementation", 257 | "type": "address", 258 | }, 259 | ], 260 | "name": "Upgraded", 261 | "type": "event", 262 | }, 263 | { 264 | "inputs": [], 265 | "name": "NAME", 266 | "outputs": [ 267 | { 268 | "internalType": "string", 269 | "name": "", 270 | "type": "string", 271 | }, 272 | ], 273 | "stateMutability": "view", 274 | "type": "function", 275 | }, 276 | { 277 | "inputs": [], 278 | "name": "VERSION", 279 | "outputs": [ 280 | { 281 | "internalType": "string", 282 | "name": "", 283 | "type": "string", 284 | }, 285 | ], 286 | "stateMutability": "view", 287 | "type": "function", 288 | }, 289 | { 290 | "inputs": [ 291 | { 292 | "internalType": "string", 293 | "name": "key", 294 | "type": "string", 295 | }, 296 | ], 297 | "name": "addKey", 298 | "outputs": [], 299 | "stateMutability": "nonpayable", 300 | "type": "function", 301 | }, 302 | { 303 | "inputs": [ 304 | { 305 | "internalType": "string", 306 | "name": "key", 307 | "type": "string", 308 | }, 309 | ], 310 | "name": "addPrivateKey", 311 | "outputs": [], 312 | "stateMutability": "nonpayable", 313 | "type": "function", 314 | }, 315 | { 316 | "inputs": [ 317 | { 318 | "internalType": "address", 319 | "name": "to", 320 | "type": "address", 321 | }, 322 | { 323 | "internalType": "uint256", 324 | "name": "tokenId", 325 | "type": "uint256", 326 | }, 327 | ], 328 | "name": "approve", 329 | "outputs": [], 330 | "stateMutability": "nonpayable", 331 | "type": "function", 332 | }, 333 | { 334 | "inputs": [ 335 | { 336 | "internalType": "address", 337 | "name": "owner", 338 | "type": "address", 339 | }, 340 | ], 341 | "name": "balanceOf", 342 | "outputs": [ 343 | { 344 | "internalType": "uint256", 345 | "name": "", 346 | "type": "uint256", 347 | }, 348 | ], 349 | "stateMutability": "view", 350 | "type": "function", 351 | }, 352 | { 353 | "inputs": [ 354 | { 355 | "internalType": "uint256", 356 | "name": "tokenId", 357 | "type": "uint256", 358 | }, 359 | ], 360 | "name": "burn", 361 | "outputs": [], 362 | "stateMutability": "nonpayable", 363 | "type": "function", 364 | }, 365 | { 366 | "inputs": [ 367 | { 368 | "internalType": "uint256", 369 | "name": "tokenId", 370 | "type": "uint256", 371 | }, 372 | { 373 | "internalType": "string", 374 | "name": "label", 375 | "type": "string", 376 | }, 377 | ], 378 | "name": "childIdOf", 379 | "outputs": [ 380 | { 381 | "internalType": "uint256", 382 | "name": "", 383 | "type": "uint256", 384 | }, 385 | ], 386 | "stateMutability": "pure", 387 | "type": "function", 388 | }, 389 | { 390 | "inputs": [ 391 | { 392 | "components": [ 393 | { 394 | "internalType": "address", 395 | "name": "from", 396 | "type": "address", 397 | }, 398 | { 399 | "internalType": "uint256", 400 | "name": "nonce", 401 | "type": "uint256", 402 | }, 403 | { 404 | "internalType": "uint256", 405 | "name": "tokenId", 406 | "type": "uint256", 407 | }, 408 | { 409 | "internalType": "bytes", 410 | "name": "data", 411 | "type": "bytes", 412 | }, 413 | ], 414 | "internalType": "struct IForwarder.ForwardRequest", 415 | "name": "req", 416 | "type": "tuple", 417 | }, 418 | { 419 | "internalType": "bytes", 420 | "name": "signature", 421 | "type": "bytes", 422 | }, 423 | ], 424 | "name": "execute", 425 | "outputs": [ 426 | { 427 | "internalType": "bytes", 428 | "name": "", 429 | "type": "bytes", 430 | }, 431 | ], 432 | "stateMutability": "nonpayable", 433 | "type": "function", 434 | }, 435 | { 436 | "inputs": [ 437 | { 438 | "internalType": "uint256", 439 | "name": "tokenId", 440 | "type": "uint256", 441 | }, 442 | ], 443 | "name": "exists", 444 | "outputs": [ 445 | { 446 | "internalType": "bool", 447 | "name": "", 448 | "type": "bool", 449 | }, 450 | ], 451 | "stateMutability": "view", 452 | "type": "function", 453 | }, 454 | { 455 | "inputs": [ 456 | { 457 | "internalType": "uint256", 458 | "name": "tokenId", 459 | "type": "uint256", 460 | }, 461 | ], 462 | "name": "getAllKeys", 463 | "outputs": [ 464 | { 465 | "internalType": "string[]", 466 | "name": "keys", 467 | "type": "string[]", 468 | }, 469 | ], 470 | "stateMutability": "view", 471 | "type": "function", 472 | }, 473 | { 474 | "inputs": [ 475 | { 476 | "internalType": "uint256", 477 | "name": "tokenId", 478 | "type": "uint256", 479 | }, 480 | ], 481 | "name": "getApproved", 482 | "outputs": [ 483 | { 484 | "internalType": "address", 485 | "name": "", 486 | "type": "address", 487 | }, 488 | ], 489 | "stateMutability": "view", 490 | "type": "function", 491 | }, 492 | { 493 | "inputs": [ 494 | { 495 | "internalType": "uint256", 496 | "name": "keyHash", 497 | "type": "uint256", 498 | }, 499 | ], 500 | "name": "getKey", 501 | "outputs": [ 502 | { 503 | "internalType": "string", 504 | "name": "", 505 | "type": "string", 506 | }, 507 | ], 508 | "stateMutability": "view", 509 | "type": "function", 510 | }, 511 | { 512 | "inputs": [ 513 | { 514 | "internalType": "uint256[]", 515 | "name": "hashes", 516 | "type": "uint256[]", 517 | }, 518 | ], 519 | "name": "getKeys", 520 | "outputs": [ 521 | { 522 | "internalType": "string[]", 523 | "name": "values", 524 | "type": "string[]", 525 | }, 526 | ], 527 | "stateMutability": "view", 528 | "type": "function", 529 | }, 530 | { 531 | "inputs": [ 532 | { 533 | "internalType": "string[]", 534 | "name": "keys", 535 | "type": "string[]", 536 | }, 537 | { 538 | "internalType": "uint256", 539 | "name": "tokenId", 540 | "type": "uint256", 541 | }, 542 | ], 543 | "name": "getManyRecords", 544 | "outputs": [ 545 | { 546 | "internalType": "string[]", 547 | "name": "values", 548 | "type": "string[]", 549 | }, 550 | ], 551 | "stateMutability": "view", 552 | "type": "function", 553 | }, 554 | { 555 | "inputs": [ 556 | { 557 | "internalType": "uint256[]", 558 | "name": "keyHashes", 559 | "type": "uint256[]", 560 | }, 561 | { 562 | "internalType": "uint256", 563 | "name": "tokenId", 564 | "type": "uint256", 565 | }, 566 | ], 567 | "name": "getManyRecordsByHash", 568 | "outputs": [ 569 | { 570 | "internalType": "string[]", 571 | "name": "keys", 572 | "type": "string[]", 573 | }, 574 | { 575 | "internalType": "string[]", 576 | "name": "values", 577 | "type": "string[]", 578 | }, 579 | ], 580 | "stateMutability": "view", 581 | "type": "function", 582 | }, 583 | { 584 | "inputs": [ 585 | { 586 | "internalType": "string", 587 | "name": "key", 588 | "type": "string", 589 | }, 590 | { 591 | "internalType": "uint256", 592 | "name": "tokenId", 593 | "type": "uint256", 594 | }, 595 | ], 596 | "name": "getRecord", 597 | "outputs": [ 598 | { 599 | "internalType": "string", 600 | "name": "value", 601 | "type": "string", 602 | }, 603 | ], 604 | "stateMutability": "view", 605 | "type": "function", 606 | }, 607 | { 608 | "inputs": [ 609 | { 610 | "internalType": "uint256", 611 | "name": "keyHash", 612 | "type": "uint256", 613 | }, 614 | { 615 | "internalType": "uint256", 616 | "name": "tokenId", 617 | "type": "uint256", 618 | }, 619 | ], 620 | "name": "getRecordByHash", 621 | "outputs": [ 622 | { 623 | "internalType": "string", 624 | "name": "key", 625 | "type": "string", 626 | }, 627 | { 628 | "internalType": "string", 629 | "name": "value", 630 | "type": "string", 631 | }, 632 | ], 633 | "stateMutability": "view", 634 | "type": "function", 635 | }, 636 | { 637 | "inputs": [ 638 | { 639 | "internalType": "address", 640 | "name": "mintingManager", 641 | "type": "address", 642 | }, 643 | { 644 | "internalType": "address", 645 | "name": "keysManager", 646 | "type": "address", 647 | }, 648 | ], 649 | "name": "initialize", 650 | "outputs": [], 651 | "stateMutability": "nonpayable", 652 | "type": "function", 653 | }, 654 | { 655 | "inputs": [ 656 | { 657 | "internalType": "address", 658 | "name": "owner", 659 | "type": "address", 660 | }, 661 | { 662 | "internalType": "address", 663 | "name": "operator", 664 | "type": "address", 665 | }, 666 | ], 667 | "name": "isApprovedForAll", 668 | "outputs": [ 669 | { 670 | "internalType": "bool", 671 | "name": "", 672 | "type": "bool", 673 | }, 674 | ], 675 | "stateMutability": "view", 676 | "type": "function", 677 | }, 678 | { 679 | "inputs": [ 680 | { 681 | "internalType": "address", 682 | "name": "spender", 683 | "type": "address", 684 | }, 685 | { 686 | "internalType": "uint256", 687 | "name": "tokenId", 688 | "type": "uint256", 689 | }, 690 | ], 691 | "name": "isApprovedOrOwner", 692 | "outputs": [ 693 | { 694 | "internalType": "bool", 695 | "name": "", 696 | "type": "bool", 697 | }, 698 | ], 699 | "stateMutability": "view", 700 | "type": "function", 701 | }, 702 | { 703 | "inputs": [ 704 | { 705 | "internalType": "address", 706 | "name": "forwarder", 707 | "type": "address", 708 | }, 709 | ], 710 | "name": "isTrustedForwarder", 711 | "outputs": [ 712 | { 713 | "internalType": "bool", 714 | "name": "", 715 | "type": "bool", 716 | }, 717 | ], 718 | "stateMutability": "view", 719 | "type": "function", 720 | }, 721 | { 722 | "inputs": [ 723 | { 724 | "internalType": "address", 725 | "name": "user", 726 | "type": "address", 727 | }, 728 | { 729 | "internalType": "uint256", 730 | "name": "tokenId", 731 | "type": "uint256", 732 | }, 733 | ], 734 | "name": "mint", 735 | "outputs": [], 736 | "stateMutability": "nonpayable", 737 | "type": "function", 738 | }, 739 | { 740 | "inputs": [ 741 | { 742 | "internalType": "address", 743 | "name": "user", 744 | "type": "address", 745 | }, 746 | { 747 | "internalType": "uint256", 748 | "name": "tokenId", 749 | "type": "uint256", 750 | }, 751 | { 752 | "internalType": "bytes", 753 | "name": "", 754 | "type": "bytes", 755 | }, 756 | ], 757 | "name": "mint", 758 | "outputs": [], 759 | "stateMutability": "nonpayable", 760 | "type": "function", 761 | }, 762 | { 763 | "inputs": [ 764 | { 765 | "internalType": "address", 766 | "name": "to", 767 | "type": "address", 768 | }, 769 | { 770 | "internalType": "uint256", 771 | "name": "tokenId", 772 | "type": "uint256", 773 | }, 774 | { 775 | "internalType": "string", 776 | "name": "uri", 777 | "type": "string", 778 | }, 779 | ], 780 | "name": "mint", 781 | "outputs": [], 782 | "stateMutability": "nonpayable", 783 | "type": "function", 784 | }, 785 | { 786 | "inputs": [ 787 | { 788 | "internalType": "address", 789 | "name": "to", 790 | "type": "address", 791 | }, 792 | { 793 | "internalType": "uint256", 794 | "name": "tokenId", 795 | "type": "uint256", 796 | }, 797 | { 798 | "internalType": "string", 799 | "name": "uri", 800 | "type": "string", 801 | }, 802 | { 803 | "internalType": "string[]", 804 | "name": "keys", 805 | "type": "string[]", 806 | }, 807 | { 808 | "internalType": "string[]", 809 | "name": "values", 810 | "type": "string[]", 811 | }, 812 | ], 813 | "name": "mintWithRecords", 814 | "outputs": [], 815 | "stateMutability": "nonpayable", 816 | "type": "function", 817 | }, 818 | { 819 | "inputs": [], 820 | "name": "name", 821 | "outputs": [ 822 | { 823 | "internalType": "string", 824 | "name": "", 825 | "type": "string", 826 | }, 827 | ], 828 | "stateMutability": "view", 829 | "type": "function", 830 | }, 831 | { 832 | "inputs": [ 833 | { 834 | "internalType": "uint256", 835 | "name": "tokenId", 836 | "type": "uint256", 837 | }, 838 | ], 839 | "name": "nonceOf", 840 | "outputs": [ 841 | { 842 | "internalType": "uint256", 843 | "name": "", 844 | "type": "uint256", 845 | }, 846 | ], 847 | "stateMutability": "view", 848 | "type": "function", 849 | }, 850 | { 851 | "inputs": [ 852 | { 853 | "internalType": "address", 854 | "name": "", 855 | "type": "address", 856 | }, 857 | { 858 | "internalType": "address", 859 | "name": "from", 860 | "type": "address", 861 | }, 862 | { 863 | "internalType": "uint256", 864 | "name": "tokenId", 865 | "type": "uint256", 866 | }, 867 | { 868 | "internalType": "bytes", 869 | "name": "data", 870 | "type": "bytes", 871 | }, 872 | ], 873 | "name": "onERC721Received", 874 | "outputs": [ 875 | { 876 | "internalType": "bytes4", 877 | "name": "", 878 | "type": "bytes4", 879 | }, 880 | ], 881 | "stateMutability": "pure", 882 | "type": "function", 883 | }, 884 | { 885 | "inputs": [ 886 | { 887 | "internalType": "uint256", 888 | "name": "tokenId", 889 | "type": "uint256", 890 | }, 891 | ], 892 | "name": "ownerOf", 893 | "outputs": [ 894 | { 895 | "internalType": "address", 896 | "name": "", 897 | "type": "address", 898 | }, 899 | ], 900 | "stateMutability": "view", 901 | "type": "function", 902 | }, 903 | { 904 | "inputs": [ 905 | { 906 | "internalType": "string[]", 907 | "name": "keys", 908 | "type": "string[]", 909 | }, 910 | { 911 | "internalType": "string[]", 912 | "name": "values", 913 | "type": "string[]", 914 | }, 915 | { 916 | "internalType": "uint256", 917 | "name": "tokenId", 918 | "type": "uint256", 919 | }, 920 | ], 921 | "name": "reconfigure", 922 | "outputs": [], 923 | "stateMutability": "nonpayable", 924 | "type": "function", 925 | }, 926 | { 927 | "inputs": [], 928 | "name": "removeReverse", 929 | "outputs": [], 930 | "stateMutability": "nonpayable", 931 | "type": "function", 932 | }, 933 | { 934 | "inputs": [ 935 | { 936 | "internalType": "uint256", 937 | "name": "tokenId", 938 | "type": "uint256", 939 | }, 940 | ], 941 | "name": "reset", 942 | "outputs": [], 943 | "stateMutability": "nonpayable", 944 | "type": "function", 945 | }, 946 | { 947 | "inputs": [ 948 | { 949 | "internalType": "uint256", 950 | "name": "tokenId", 951 | "type": "uint256", 952 | }, 953 | ], 954 | "name": "resolverOf", 955 | "outputs": [ 956 | { 957 | "internalType": "address", 958 | "name": "", 959 | "type": "address", 960 | }, 961 | ], 962 | "stateMutability": "view", 963 | "type": "function", 964 | }, 965 | { 966 | "inputs": [ 967 | { 968 | "internalType": "address", 969 | "name": "addr", 970 | "type": "address", 971 | }, 972 | ], 973 | "name": "reverseOf", 974 | "outputs": [ 975 | { 976 | "internalType": "uint256", 977 | "name": "", 978 | "type": "uint256", 979 | }, 980 | ], 981 | "stateMutability": "view", 982 | "type": "function", 983 | }, 984 | { 985 | "inputs": [], 986 | "name": "root", 987 | "outputs": [ 988 | { 989 | "internalType": "uint256", 990 | "name": "", 991 | "type": "uint256", 992 | }, 993 | ], 994 | "stateMutability": "pure", 995 | "type": "function", 996 | }, 997 | { 998 | "inputs": [ 999 | { 1000 | "internalType": "address", 1001 | "name": "to", 1002 | "type": "address", 1003 | }, 1004 | { 1005 | "internalType": "uint256", 1006 | "name": "tokenId", 1007 | "type": "uint256", 1008 | }, 1009 | { 1010 | "internalType": "string", 1011 | "name": "uri", 1012 | "type": "string", 1013 | }, 1014 | { 1015 | "internalType": "bytes", 1016 | "name": "data", 1017 | "type": "bytes", 1018 | }, 1019 | ], 1020 | "name": "safeMint", 1021 | "outputs": [], 1022 | "stateMutability": "nonpayable", 1023 | "type": "function", 1024 | }, 1025 | { 1026 | "inputs": [ 1027 | { 1028 | "internalType": "address", 1029 | "name": "to", 1030 | "type": "address", 1031 | }, 1032 | { 1033 | "internalType": "uint256", 1034 | "name": "tokenId", 1035 | "type": "uint256", 1036 | }, 1037 | { 1038 | "internalType": "string", 1039 | "name": "uri", 1040 | "type": "string", 1041 | }, 1042 | ], 1043 | "name": "safeMint", 1044 | "outputs": [], 1045 | "stateMutability": "nonpayable", 1046 | "type": "function", 1047 | }, 1048 | { 1049 | "inputs": [ 1050 | { 1051 | "internalType": "address", 1052 | "name": "to", 1053 | "type": "address", 1054 | }, 1055 | { 1056 | "internalType": "uint256", 1057 | "name": "tokenId", 1058 | "type": "uint256", 1059 | }, 1060 | { 1061 | "internalType": "string", 1062 | "name": "uri", 1063 | "type": "string", 1064 | }, 1065 | { 1066 | "internalType": "string[]", 1067 | "name": "keys", 1068 | "type": "string[]", 1069 | }, 1070 | { 1071 | "internalType": "string[]", 1072 | "name": "values", 1073 | "type": "string[]", 1074 | }, 1075 | ], 1076 | "name": "safeMintWithRecords", 1077 | "outputs": [], 1078 | "stateMutability": "nonpayable", 1079 | "type": "function", 1080 | }, 1081 | { 1082 | "inputs": [ 1083 | { 1084 | "internalType": "address", 1085 | "name": "to", 1086 | "type": "address", 1087 | }, 1088 | { 1089 | "internalType": "uint256", 1090 | "name": "tokenId", 1091 | "type": "uint256", 1092 | }, 1093 | { 1094 | "internalType": "string", 1095 | "name": "uri", 1096 | "type": "string", 1097 | }, 1098 | { 1099 | "internalType": "string[]", 1100 | "name": "keys", 1101 | "type": "string[]", 1102 | }, 1103 | { 1104 | "internalType": "string[]", 1105 | "name": "values", 1106 | "type": "string[]", 1107 | }, 1108 | { 1109 | "internalType": "bytes", 1110 | "name": "data", 1111 | "type": "bytes", 1112 | }, 1113 | ], 1114 | "name": "safeMintWithRecords", 1115 | "outputs": [], 1116 | "stateMutability": "nonpayable", 1117 | "type": "function", 1118 | }, 1119 | { 1120 | "inputs": [ 1121 | { 1122 | "internalType": "address", 1123 | "name": "from", 1124 | "type": "address", 1125 | }, 1126 | { 1127 | "internalType": "address", 1128 | "name": "to", 1129 | "type": "address", 1130 | }, 1131 | { 1132 | "internalType": "uint256", 1133 | "name": "tokenId", 1134 | "type": "uint256", 1135 | }, 1136 | ], 1137 | "name": "safeTransferFrom", 1138 | "outputs": [], 1139 | "stateMutability": "nonpayable", 1140 | "type": "function", 1141 | }, 1142 | { 1143 | "inputs": [ 1144 | { 1145 | "internalType": "address", 1146 | "name": "from", 1147 | "type": "address", 1148 | }, 1149 | { 1150 | "internalType": "address", 1151 | "name": "to", 1152 | "type": "address", 1153 | }, 1154 | { 1155 | "internalType": "uint256", 1156 | "name": "tokenId", 1157 | "type": "uint256", 1158 | }, 1159 | { 1160 | "internalType": "bytes", 1161 | "name": "data", 1162 | "type": "bytes", 1163 | }, 1164 | ], 1165 | "name": "safeTransferFrom", 1166 | "outputs": [], 1167 | "stateMutability": "nonpayable", 1168 | "type": "function", 1169 | }, 1170 | { 1171 | "inputs": [ 1172 | { 1173 | "internalType": "address", 1174 | "name": "operator", 1175 | "type": "address", 1176 | }, 1177 | { 1178 | "internalType": "bool", 1179 | "name": "approved", 1180 | "type": "bool", 1181 | }, 1182 | ], 1183 | "name": "setApprovalForAll", 1184 | "outputs": [], 1185 | "stateMutability": "nonpayable", 1186 | "type": "function", 1187 | }, 1188 | { 1189 | "inputs": [ 1190 | { 1191 | "internalType": "string[]", 1192 | "name": "keys", 1193 | "type": "string[]", 1194 | }, 1195 | { 1196 | "internalType": "string[]", 1197 | "name": "values", 1198 | "type": "string[]", 1199 | }, 1200 | { 1201 | "internalType": "uint256", 1202 | "name": "tokenId", 1203 | "type": "uint256", 1204 | }, 1205 | ], 1206 | "name": "setManyRecords", 1207 | "outputs": [], 1208 | "stateMutability": "nonpayable", 1209 | "type": "function", 1210 | }, 1211 | { 1212 | "inputs": [ 1213 | { 1214 | "internalType": "uint256[]", 1215 | "name": "keyHashes", 1216 | "type": "uint256[]", 1217 | }, 1218 | { 1219 | "internalType": "string[]", 1220 | "name": "values", 1221 | "type": "string[]", 1222 | }, 1223 | { 1224 | "internalType": "uint256", 1225 | "name": "tokenId", 1226 | "type": "uint256", 1227 | }, 1228 | ], 1229 | "name": "setManyRecordsByHash", 1230 | "outputs": [], 1231 | "stateMutability": "nonpayable", 1232 | "type": "function", 1233 | }, 1234 | { 1235 | "inputs": [ 1236 | { 1237 | "internalType": "address", 1238 | "name": "to", 1239 | "type": "address", 1240 | }, 1241 | { 1242 | "internalType": "uint256", 1243 | "name": "tokenId", 1244 | "type": "uint256", 1245 | }, 1246 | ], 1247 | "name": "setOwner", 1248 | "outputs": [], 1249 | "stateMutability": "nonpayable", 1250 | "type": "function", 1251 | }, 1252 | { 1253 | "inputs": [ 1254 | { 1255 | "internalType": "string", 1256 | "name": "key", 1257 | "type": "string", 1258 | }, 1259 | { 1260 | "internalType": "string", 1261 | "name": "value", 1262 | "type": "string", 1263 | }, 1264 | { 1265 | "internalType": "uint256", 1266 | "name": "tokenId", 1267 | "type": "uint256", 1268 | }, 1269 | ], 1270 | "name": "setRecord", 1271 | "outputs": [], 1272 | "stateMutability": "nonpayable", 1273 | "type": "function", 1274 | }, 1275 | { 1276 | "inputs": [ 1277 | { 1278 | "internalType": "uint256", 1279 | "name": "keyHash", 1280 | "type": "uint256", 1281 | }, 1282 | { 1283 | "internalType": "string", 1284 | "name": "value", 1285 | "type": "string", 1286 | }, 1287 | { 1288 | "internalType": "uint256", 1289 | "name": "tokenId", 1290 | "type": "uint256", 1291 | }, 1292 | ], 1293 | "name": "setRecordByHash", 1294 | "outputs": [], 1295 | "stateMutability": "nonpayable", 1296 | "type": "function", 1297 | }, 1298 | { 1299 | "inputs": [ 1300 | { 1301 | "internalType": "uint256", 1302 | "name": "tokenId", 1303 | "type": "uint256", 1304 | }, 1305 | ], 1306 | "name": "setReverse", 1307 | "outputs": [], 1308 | "stateMutability": "nonpayable", 1309 | "type": "function", 1310 | }, 1311 | { 1312 | "inputs": [ 1313 | { 1314 | "internalType": "string", 1315 | "name": "prefix", 1316 | "type": "string", 1317 | }, 1318 | ], 1319 | "name": "setTokenURIPrefix", 1320 | "outputs": [], 1321 | "stateMutability": "nonpayable", 1322 | "type": "function", 1323 | }, 1324 | { 1325 | "inputs": [ 1326 | { 1327 | "internalType": "uint256", 1328 | "name": "tokenId", 1329 | "type": "uint256", 1330 | }, 1331 | ], 1332 | "name": "size", 1333 | "outputs": [ 1334 | { 1335 | "internalType": "uint256", 1336 | "name": "", 1337 | "type": "uint256", 1338 | }, 1339 | ], 1340 | "stateMutability": "view", 1341 | "type": "function", 1342 | }, 1343 | { 1344 | "inputs": [ 1345 | { 1346 | "internalType": "bytes4", 1347 | "name": "interfaceId", 1348 | "type": "bytes4", 1349 | }, 1350 | ], 1351 | "name": "supportsInterface", 1352 | "outputs": [ 1353 | { 1354 | "internalType": "bool", 1355 | "name": "", 1356 | "type": "bool", 1357 | }, 1358 | ], 1359 | "stateMutability": "view", 1360 | "type": "function", 1361 | }, 1362 | { 1363 | "inputs": [], 1364 | "name": "symbol", 1365 | "outputs": [ 1366 | { 1367 | "internalType": "string", 1368 | "name": "", 1369 | "type": "string", 1370 | }, 1371 | ], 1372 | "stateMutability": "view", 1373 | "type": "function", 1374 | }, 1375 | { 1376 | "inputs": [ 1377 | { 1378 | "internalType": "uint256", 1379 | "name": "index", 1380 | "type": "uint256", 1381 | }, 1382 | ], 1383 | "name": "tokenByIndex", 1384 | "outputs": [ 1385 | { 1386 | "internalType": "uint256", 1387 | "name": "", 1388 | "type": "uint256", 1389 | }, 1390 | ], 1391 | "stateMutability": "view", 1392 | "type": "function", 1393 | }, 1394 | { 1395 | "inputs": [ 1396 | { 1397 | "internalType": "address", 1398 | "name": "owner", 1399 | "type": "address", 1400 | }, 1401 | { 1402 | "internalType": "uint256", 1403 | "name": "index", 1404 | "type": "uint256", 1405 | }, 1406 | ], 1407 | "name": "tokenOfOwnerByIndex", 1408 | "outputs": [ 1409 | { 1410 | "internalType": "uint256", 1411 | "name": "", 1412 | "type": "uint256", 1413 | }, 1414 | ], 1415 | "stateMutability": "view", 1416 | "type": "function", 1417 | }, 1418 | { 1419 | "inputs": [ 1420 | { 1421 | "internalType": "uint256", 1422 | "name": "tokenId", 1423 | "type": "uint256", 1424 | }, 1425 | ], 1426 | "name": "tokenURI", 1427 | "outputs": [ 1428 | { 1429 | "internalType": "string", 1430 | "name": "", 1431 | "type": "string", 1432 | }, 1433 | ], 1434 | "stateMutability": "view", 1435 | "type": "function", 1436 | }, 1437 | { 1438 | "inputs": [], 1439 | "name": "totalSupply", 1440 | "outputs": [ 1441 | { 1442 | "internalType": "uint256", 1443 | "name": "", 1444 | "type": "uint256", 1445 | }, 1446 | ], 1447 | "stateMutability": "view", 1448 | "type": "function", 1449 | }, 1450 | { 1451 | "inputs": [ 1452 | { 1453 | "internalType": "address", 1454 | "name": "from", 1455 | "type": "address", 1456 | }, 1457 | { 1458 | "internalType": "address", 1459 | "name": "to", 1460 | "type": "address", 1461 | }, 1462 | { 1463 | "internalType": "uint256", 1464 | "name": "tokenId", 1465 | "type": "uint256", 1466 | }, 1467 | ], 1468 | "name": "transferFrom", 1469 | "outputs": [], 1470 | "stateMutability": "nonpayable", 1471 | "type": "function", 1472 | }, 1473 | { 1474 | "inputs": [ 1475 | { 1476 | "components": [ 1477 | { 1478 | "internalType": "address", 1479 | "name": "from", 1480 | "type": "address", 1481 | }, 1482 | { 1483 | "internalType": "uint256", 1484 | "name": "nonce", 1485 | "type": "uint256", 1486 | }, 1487 | { 1488 | "internalType": "uint256", 1489 | "name": "tokenId", 1490 | "type": "uint256", 1491 | }, 1492 | { 1493 | "internalType": "bytes", 1494 | "name": "data", 1495 | "type": "bytes", 1496 | }, 1497 | ], 1498 | "internalType": "struct IForwarder.ForwardRequest", 1499 | "name": "req", 1500 | "type": "tuple", 1501 | }, 1502 | { 1503 | "internalType": "bytes", 1504 | "name": "signature", 1505 | "type": "bytes", 1506 | }, 1507 | ], 1508 | "name": "verify", 1509 | "outputs": [ 1510 | { 1511 | "internalType": "bool", 1512 | "name": "", 1513 | "type": "bool", 1514 | }, 1515 | ], 1516 | "stateMutability": "view", 1517 | "type": "function", 1518 | }, 1519 | ]; 1520 | 1521 | 1522 | export const FREENAME_CONTRACT_CONFS: { networkName: NetworkName, address: string, type: "read" | "write", test: boolean, abi: any }[] = [ 1523 | { 1524 | address: "0x6034C0d80e6d023FFd62Ba48e6B5c13afe72D143", 1525 | networkName: NetworkName.POLYGON_MUMBAI, 1526 | test: true, 1527 | type: "read", 1528 | abi: FNS_ABI, 1529 | }, 1530 | { 1531 | address: "0x6034C0d80e6d023FFd62Ba48e6B5c13afe72D143", 1532 | networkName: NetworkName.POLYGON_MUMBAI, 1533 | test: true, 1534 | type: "write", 1535 | abi: FNS_ABI, 1536 | }, 1537 | { 1538 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1539 | networkName: NetworkName.POLYGON, 1540 | test: false, 1541 | type: "read", 1542 | abi: FNS_ABI, 1543 | }, 1544 | { 1545 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1546 | networkName: NetworkName.POLYGON, 1547 | test: false, 1548 | type: "write", 1549 | abi: FNS_ABI, 1550 | }, 1551 | { 1552 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1553 | networkName: NetworkName.CRONOS, 1554 | test: false, 1555 | type: "read", 1556 | abi: FNS_ABI, 1557 | }, 1558 | { 1559 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1560 | networkName: NetworkName.CRONOS, 1561 | test: false, 1562 | type: "write", 1563 | abi: FNS_ABI, 1564 | }, 1565 | { 1566 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1567 | networkName: NetworkName.BSC, 1568 | test: false, 1569 | type: "read", 1570 | abi: FNS_ABI, 1571 | }, 1572 | { 1573 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1574 | networkName: NetworkName.BSC, 1575 | test: false, 1576 | type: "write", 1577 | abi: FNS_ABI, 1578 | }, 1579 | { 1580 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1581 | networkName: NetworkName.AURORA, 1582 | test: false, 1583 | type: "read", 1584 | abi: FNS_ABI, 1585 | }, 1586 | { 1587 | address: "0x465ea4967479A96D4490d575b5a6cC2B4A4BEE65", 1588 | networkName: NetworkName.AURORA, 1589 | test: false, 1590 | type: "write", 1591 | abi: FNS_ABI, 1592 | }, 1593 | ]; 1594 | -------------------------------------------------------------------------------- /src/resolver-providers/providers/freename/freename-resolver-provider.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import cloneDeep from "lodash.clonedeep"; 3 | import { DefaultTools } from "../../../defaults/default-connections"; 4 | import { ConnectionLibrary } from "../../../networks/connections/connection-library"; 5 | import { ContractConnection } from "../../../networks/connections/contract-connection"; 6 | import { NetworkName } from "../../../networks/connections/network-connection.types"; 7 | import { IResolvedResource } from "../../../resolvers/resolved-resource/resolved-resource.interface"; 8 | import { ProviderName } from "../../../resolvers/types/resolver-name"; 9 | import { MappedName } from "../../../tools/name-tools.types"; 10 | import { IResolverProvider } from "../../resolver-provider.interface"; 11 | import { BaseResolverProvider } from "../base-resolver-provider"; 12 | import { FREENAME_CONTRACT_CONFS } from "./freename-resolver-provider.consts"; 13 | import { FreenameMetadata } from "./freename-resolver-provider.types"; 14 | import { FreenameResolverTools } from "./freename-resolver-tools"; 15 | 16 | export class FreenameResolverProvider extends BaseResolverProvider implements IResolverProvider { 17 | constructor(options: { connectionLibrary?: ConnectionLibrary, testMode?: boolean } = {}) { 18 | 19 | const { connectionLibrary, testMode = false } = options; 20 | 21 | const readContractConnections: ContractConnection[] = []; 22 | const writeContractConnections: ContractConnection[] = []; 23 | const freenameContractConfs = cloneDeep(FREENAME_CONTRACT_CONFS); 24 | for (const contractConf of freenameContractConfs) { 25 | if (contractConf.test == testMode) { 26 | const connection = connectionLibrary?.getConnection(contractConf.networkName) || DefaultTools.getDefaultConnection(contractConf.networkName, { infuraIfAvailable: true }); 27 | if (contractConf.type == "read") { 28 | readContractConnections.push(new ContractConnection(connection, contractConf.address, contractConf.abi)); 29 | } else if (contractConf.type == "write") { 30 | writeContractConnections.push(new ContractConnection(connection, contractConf.address, contractConf.abi)); 31 | } 32 | } 33 | } 34 | 35 | super(ProviderName.FREENAME, ["*"], readContractConnections, writeContractConnections); 36 | } 37 | 38 | public override async getRecord(tokenId: string, key: string, network?: NetworkName | string | undefined): Promise { 39 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 40 | if (!readContractConnection) { 41 | return undefined; 42 | } 43 | 44 | try { 45 | return await readContractConnection.contract.getRecord(key, tokenId); 46 | } 47 | catch { 48 | return undefined; 49 | } 50 | } 51 | 52 | public override async getManyRecords(tokenId: string, keys: string[], network?: NetworkName | string | undefined): Promise { 53 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 54 | if (!readContractConnection) { 55 | return undefined; 56 | } 57 | 58 | try { 59 | return await readContractConnection.contract.getManyRecords(keys, tokenId); 60 | } catch { 61 | return undefined; 62 | } 63 | } 64 | 65 | public override async setRecord(resource: IResolvedResource, key: string, value: string, signer: ethers.Signer): Promise { 66 | const writeContract = this.getWriteContractWithSigner(resource.network, signer); 67 | if (!writeContract) { 68 | return false; 69 | } 70 | 71 | try { 72 | const tx = await writeContract.setRecord(key, value, resource.tokenId); 73 | const approveReceipt = await tx.wait(); 74 | if (approveReceipt) { 75 | return true; 76 | } 77 | return false; 78 | } catch (e) { 79 | return false; 80 | } 81 | } 82 | 83 | public override async setRecords(resource: IResolvedResource, keys: string[], values: string[], signer: ethers.Signer): Promise { 84 | const writeContract = this.getWriteContractWithSigner(resource.network, signer); 85 | if (!writeContract) { 86 | return false; 87 | } 88 | 89 | try { 90 | const tx = await writeContract.setManyRecords(keys, values, resource.tokenId); 91 | const approveReceipt = await tx.wait(); 92 | if (approveReceipt) { 93 | return true; 94 | } 95 | return false; 96 | } catch (e) { 97 | return false; 98 | } 99 | } 100 | 101 | public async generateTokenId(mappedName: MappedName): Promise { 102 | let fullnameKeccak: string; 103 | if (mappedName.domain) { 104 | const domainKeccak = ethers.utils.solidityKeccak256(["string"], [mappedName.domain]); 105 | fullnameKeccak = ethers.utils.solidityKeccak256(["string", "uint256"], [mappedName.tld, domainKeccak]); 106 | } else { 107 | fullnameKeccak = ethers.utils.solidityKeccak256(["string"], [mappedName.tld]); 108 | } 109 | if (fullnameKeccak) { 110 | const tokenId = ethers.BigNumber.from(fullnameKeccak).toString(); 111 | return tokenId; 112 | } 113 | return undefined; 114 | } 115 | 116 | public async getNetworkFromName(mappedName: MappedName): Promise { 117 | const tokenId = await this.generateTokenId(mappedName); 118 | if (!tokenId) { 119 | return undefined; 120 | } 121 | 122 | const metadata: FreenameMetadata = await this.getMetadata(tokenId); 123 | const network = FreenameResolverTools.networkNameFormFreenameNetwork(metadata?.network); 124 | return network; 125 | } 126 | 127 | public async getRecords(tokenId: string, network?: NetworkName | string | undefined): Promise<{ [key: string]: string; } | undefined> { 128 | const keys = await this.getAllRecordKeys(tokenId, network); 129 | if (!keys) { 130 | return undefined; 131 | } 132 | 133 | const values = await this.getManyRecords(tokenId, keys, network); 134 | if (!values) { 135 | return undefined; 136 | } 137 | 138 | if (keys.length !== values.length) { 139 | return undefined; 140 | } 141 | 142 | const records: { [key: string]: string } = {}; 143 | for (let i = 0; i < keys.length; i++) { 144 | const key = keys[i]; 145 | records[key] = values[i]; 146 | } 147 | 148 | return records; 149 | } 150 | 151 | public async getAllRecordKeys(tokenId: string, network?: NetworkName | string | undefined): Promise { 152 | const readContractConnection = await this.getReadContractConnectionFromToken(tokenId, network); 153 | if (!readContractConnection) { 154 | return undefined; 155 | } 156 | 157 | try { 158 | return await readContractConnection.contract.getAllKeys(tokenId); 159 | } catch { 160 | return undefined; 161 | } 162 | } 163 | 164 | public async getNameFromTokenId(tokenId: string, network?: NetworkName | undefined): Promise { 165 | const metadata: FreenameMetadata = await this.getMetadata(tokenId); 166 | return metadata?.name; 167 | } 168 | } -------------------------------------------------------------------------------- /src/resolver-providers/providers/freename/freename-resolver-provider.types.ts: -------------------------------------------------------------------------------- 1 | enum FreenameItemType { 2 | TLD = "TLD", 3 | SECOND_LEVEL_DOMAIN = "SECOND_LEVEL_DOMAIN", 4 | SUB_DOMAINED_DOMAIN = "SUB_DOMAINED_DOMAIN", 5 | } 6 | 7 | export enum FreenameNetwork { 8 | POLYGON = "polygon", 9 | POLYGON_MUMBAI = "polygon-mumbai", 10 | ETHEREUM = "ethereum", 11 | BSC = "bsc" 12 | } 13 | 14 | export type FreenameMetadata = { 15 | name: string, 16 | description: string, 17 | image: string, 18 | itemType: FreenameItemType 19 | external_url: string, 20 | image_url: string, 21 | network: FreenameNetwork, 22 | properties: { 23 | [key: string]: any 24 | } 25 | } -------------------------------------------------------------------------------- /src/resolver-providers/providers/freename/freename-resolver-tools.ts: -------------------------------------------------------------------------------- 1 | import { NetworkName } from "../../../networks/connections/network-connection.types"; 2 | import { FreenameNetwork } from "./freename-resolver-provider.types"; 3 | 4 | export class FreenameResolverTools { 5 | 6 | public static networkNameFormFreenameNetwork(freenameNetwork: FreenameNetwork): NetworkName { 7 | switch (freenameNetwork) { 8 | case FreenameNetwork.BSC: return NetworkName.BSC; 9 | case FreenameNetwork.ETHEREUM: return NetworkName.ETHEREUM; 10 | case FreenameNetwork.POLYGON: return NetworkName.POLYGON; 11 | case FreenameNetwork.POLYGON_MUMBAI: return NetworkName.POLYGON_MUMBAI; 12 | } 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/resolver-providers/providers/ud/ud-resolver-provider.consts.ts: -------------------------------------------------------------------------------- 1 | export const UD_SUPPORTED_TLDS: string[] = [ 2 | "crypto", 3 | "wallet", 4 | "coin", 5 | "x", 6 | "nft", 7 | "blockchain", 8 | "bitcoin", 9 | "888", 10 | "dao", 11 | "zil", 12 | "polygon", 13 | "unstoppable", 14 | ]; 15 | 16 | export const UD_ZIL_TLDS: string[] = [ 17 | "zil", 18 | ]; 19 | 20 | export const UNS_ETH_CONTRACT_ADDRESS = "0xc3C2BAB5e3e52DBF311b2aAcEf2e40344f19494E"; 21 | export const UNS_POLYGON_CONTRACT_ADDRESS = "0xa9a6A3626993D487d2Dbda3173cf58cA1a9D9e9f"; 22 | export const UD_METADATA_URL = "https://metadata.unstoppabledomains.com/metadata/"; -------------------------------------------------------------------------------- /src/resolver-providers/providers/ud/ud-resolver-provider.ts: -------------------------------------------------------------------------------- 1 | import { default as Resolution, Locations, NamingServiceName } from "@unstoppabledomains/resolution"; 2 | import { ethers } from "ethers"; 3 | import { DefaultTools } from "../../../defaults/default-connections"; 4 | import { ERC721_UD_PROXY_ABI } from "../../../defaults/erc721.ud.proxy.abi"; 5 | import { ConnectionLibrary } from "../../../networks/connections/connection-library"; 6 | import { ContractConnection } from "../../../networks/connections/contract-connection"; 7 | import { NetworkName } from "../../../networks/connections/network-connection.types"; 8 | import { ProviderName } from "../../../resolvers/types/resolver-name"; 9 | import { MappedName } from "../../../tools/name-tools.types"; 10 | import { IResolverProvider } from "../../resolver-provider.interface"; 11 | import { BaseResolverProvider } from "../base-resolver-provider"; 12 | import { UD_SUPPORTED_TLDS, UD_ZIL_TLDS, UNS_ETH_CONTRACT_ADDRESS, UNS_POLYGON_CONTRACT_ADDRESS } from "./ud-resolver-provider.consts"; 13 | import { UDResolverTools } from "./ud-resolver-tools"; 14 | 15 | export class UDResolverProvider extends BaseResolverProvider implements IResolverProvider { 16 | 17 | constructor(options: { connectionLibrary?: ConnectionLibrary } = {}) { 18 | const ethereumConnection = options.connectionLibrary?.getConnection(NetworkName.ETHEREUM) || DefaultTools.getDefaultConnection(NetworkName.ETHEREUM); 19 | const ethReadContractAddress = new ContractConnection(ethereumConnection, UNS_ETH_CONTRACT_ADDRESS, ERC721_UD_PROXY_ABI); 20 | 21 | const polygonConnection = options.connectionLibrary?.getConnection(NetworkName.POLYGON) || DefaultTools.getDefaultConnection(NetworkName.POLYGON); 22 | const polygonReadContractAddress = new ContractConnection(polygonConnection, UNS_POLYGON_CONTRACT_ADDRESS, ERC721_UD_PROXY_ABI); 23 | 24 | super(ProviderName.UD, UD_SUPPORTED_TLDS, [polygonReadContractAddress, ethReadContractAddress], [polygonReadContractAddress, ethReadContractAddress]); 25 | this._resolution = new Resolution({ 26 | sourceConfig: { 27 | uns: { 28 | locations: { 29 | Layer1: { 30 | url: ethereumConnection.rpcUrl, 31 | network: 'mainnet', 32 | }, 33 | Layer2: { 34 | url: polygonConnection.rpcUrl, 35 | network: 'polygon-mainnet', 36 | }, 37 | }, 38 | }, 39 | zns: { 40 | url: 'https://api.zilliqa.com', 41 | network: 'mainnet', 42 | }, 43 | ens: { 44 | url: ethereumConnection.rpcUrl, 45 | network: 'mainnet', 46 | }, 47 | }, 48 | }); 49 | } 50 | 51 | private _resolution; 52 | 53 | public override async generateTokenId(mappedName: MappedName): Promise { 54 | try { 55 | let namingService: NamingServiceName; 56 | if (UD_ZIL_TLDS.includes(mappedName.tld)) { 57 | namingService = NamingServiceName.ZNS; 58 | } else { 59 | namingService = NamingServiceName.UNS; 60 | } 61 | const nameHash = this._resolution.namehash(mappedName.fullname, namingService); 62 | const tokenId = ethers.BigNumber.from(nameHash).toString(); 63 | return tokenId; 64 | } 65 | catch { 66 | return undefined; 67 | } 68 | } 69 | 70 | public override async getNetworkFromName(mappedName: MappedName): Promise { 71 | try { 72 | const network: Locations = await this._resolution.locations([mappedName.fullname]); 73 | const udNetwork = network[mappedName.fullname]?.blockchain; 74 | if (!udNetwork) { 75 | return undefined; 76 | } 77 | 78 | return UDResolverTools.networkNameFormUdNetwork(udNetwork); 79 | } catch { 80 | return undefined; 81 | } 82 | } 83 | 84 | public override async getRecords(tokenId: string): Promise<{ [key: string]: string; } | undefined> { 85 | const metadata = await this.getMetadata(tokenId); 86 | return metadata?.properties?.records; 87 | } 88 | 89 | public override async getNameFromTokenId(tokenId: string, network?: NetworkName | undefined): Promise { 90 | const hash = ethers.BigNumber.from(tokenId).toHexString(); 91 | let unhash: string | undefined; 92 | 93 | try { 94 | unhash = await this._resolution.unhash(hash, NamingServiceName.UNS); 95 | } catch(e) { 96 | console.error(e); 97 | } 98 | 99 | if (!unhash) { 100 | try { 101 | unhash = await this._resolution.unhash(hash, NamingServiceName.ZNS); 102 | } 103 | catch(e){ 104 | console.error(e); 105 | } 106 | } 107 | 108 | return unhash; 109 | } 110 | } -------------------------------------------------------------------------------- /src/resolver-providers/providers/ud/ud-resolver-tools.ts: -------------------------------------------------------------------------------- 1 | import { BlockchainType } from "@unstoppabledomains/resolution"; 2 | import { NetworkName } from "../../../networks/connections/network-connection.types"; 3 | 4 | export class UDResolverTools { 5 | public static networkNameFormUdNetwork(udNetwork: BlockchainType): NetworkName { 6 | switch (udNetwork) { 7 | case BlockchainType.ETH: return NetworkName.ETHEREUM; 8 | case BlockchainType.MATIC: return NetworkName.POLYGON; 9 | case BlockchainType.ZIL: return NetworkName.ZILLIQA; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/resolver-providers/resolver-provider-router.ts: -------------------------------------------------------------------------------- 1 | import sortBy from "lodash.sortby"; 2 | import indexOf from "lodash.indexof"; 3 | import { ProviderName } from "../resolvers/types/resolver-name"; 4 | import { NameTools } from "../tools/name-tools"; 5 | import { IResolverProvider } from "./resolver-provider.interface"; 6 | 7 | export class ResolverProviderRouter { 8 | constructor(resolverProviders: IResolverProvider[]) { 9 | this._resolverProviders = resolverProviders || []; 10 | } 11 | 12 | private _resolverProviders: IResolverProvider[]; 13 | public get resolverProviders(): IResolverProvider[] { 14 | return this._resolverProviders; 15 | } 16 | protected set resolverProviders(value: IResolverProvider[]) { 17 | this._resolverProviders = value; 18 | } 19 | 20 | public addResolverProviders(resolverProvider: IResolverProvider | IResolverProvider[]) { 21 | if (Array.isArray(resolverProvider)) { 22 | this._resolverProviders.push(...resolverProvider); 23 | } 24 | else { 25 | this._resolverProviders.push(resolverProvider); 26 | } 27 | } 28 | 29 | public getResolverProviderByDomainOrTld(domainOrTld: string): IResolverProvider | undefined { 30 | const mappedName = NameTools.mapName(domainOrTld); 31 | if (!mappedName) { 32 | return undefined; 33 | } 34 | 35 | for (const resolverProvider of this.resolverProviders) { 36 | if (resolverProvider.supportedTlds.includes(mappedName.tld)) { 37 | return resolverProvider; 38 | } 39 | } 40 | for (const resolverProvider of this.resolverProviders) { 41 | if (resolverProvider.supportedTlds.includes("*")) { 42 | return resolverProvider; 43 | } 44 | } 45 | return undefined; 46 | } 47 | 48 | public async findTokenIdResolverProvider(tokenId: string): Promise { 49 | for (const resolverProvider of this._resolverProviders) { 50 | try { 51 | const exists = await resolverProvider.exists(tokenId); 52 | if (exists) { 53 | return resolverProvider; 54 | } 55 | } catch { 56 | continue; 57 | } 58 | } 59 | return undefined; 60 | } 61 | 62 | public getResolverProvider(name: ProviderName | string): IResolverProvider | undefined { 63 | const resolverProvider = this._resolverProviders.find(x => x.name == name); 64 | return resolverProvider; 65 | } 66 | 67 | public setResolverProvidersPriority(priority: (ProviderName | string)[]) { 68 | 69 | const missing: (ProviderName | string)[] = []; 70 | for (const resolverProvider of this._resolverProviders) { 71 | if (!priority.includes(resolverProvider.name)) { 72 | missing.push(resolverProvider.name); 73 | } 74 | } 75 | 76 | const fullPriority = [...priority, ...missing]; 77 | 78 | const sortedResolverProviders = sortBy(this._resolverProviders, function (resolverProvider: IResolverProvider) { 79 | return indexOf(fullPriority, resolverProvider.name); 80 | }); 81 | 82 | this.resolverProviders = sortedResolverProviders; 83 | } 84 | } -------------------------------------------------------------------------------- /src/resolver-providers/resolver-provider.interface.ts: -------------------------------------------------------------------------------- 1 | import { NetworkName } from "../networks/connections/network-connection.types"; 2 | import { IResolvedResource } from "../resolvers/resolved-resource/resolved-resource.interface"; 3 | import { ConnectionLibrary } from "../networks/connections/connection-library"; 4 | import { ethers } from "ethers"; 5 | import { ProviderName } from "../resolvers/types/resolver-name"; 6 | 7 | export interface IResolverProvider { 8 | 9 | /** 10 | * The name of the provider. Eg. 'Freename' 11 | */ 12 | name: ProviderName | string; 13 | 14 | /** 15 | * The list of the provider's supported tlds, is `"*"` in case all the tlds are supported. 16 | */ 17 | supportedTlds: string[]; 18 | 19 | /** 20 | * The connection library containing the specific connections to the netowrks. If none is provided the default connection library is used. 21 | */ 22 | connectionLibrary?: ConnectionLibrary | undefined; 23 | 24 | /** 25 | * The supported networks of the current `IResolverProvider` 26 | */ 27 | supportedNetworks: (NetworkName | string)[]; 28 | 29 | /** 30 | * Resolves the given domain fullname or tld. 31 | * If the domain is valid, exists on the blockchain and can be resolved a `IResolvedResource` is given, otherwise the result is `undefined`. 32 | * To obtain the resolved resource a series of calls to the blockchain are made, depending on the chain traffic the `resolve` call can take a couple of seconds to be completed. 33 | * @param domainOrTld the domain to resolve. Eg. `"test.web3domain"` 34 | * @returns an `IResolvedResource` instance or undefined. 35 | */ 36 | resolve(domainOrTld: string): Promise; 37 | 38 | /** 39 | * Resolves the given tokenId. 40 | * If the tokenId is valid, exists on the blockchain and can be resolved a `IResolvedResource` is given, otherwise the result is `undefined`. 41 | * To obtain the resolved resource a series of calls to the blockchain are made, depending on the chain traffic the `resolveFromTokenId` call can take a couple of seconds to be completed. 42 | * @param tokenId the NFT tokenId uint256 string rappresentation to resolve. 43 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 44 | * @returns an `IResolvedResource` instance or undefined. 45 | */ 46 | resolveFromTokenId(tokenId: string, network?: NetworkName | string | undefined): Promise; 47 | 48 | /** 49 | * Given a wallet address returns the NFT tokenId uint256 string rappresentation. 50 | * @param address the wallet address set as the reverse resolve of a domain. 51 | */ 52 | reverseResolve(address: string): Promise; 53 | 54 | /** 55 | * Checks on the blockchain registry if the given address is the owner or an approved address for the resolved resource NFT. 56 | * @param tokenId the NFT tokenId uint256 string rappresentation to resolve. 57 | * @param addressToCheck the address to check 58 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 59 | */ 60 | isApprovedOrOwner(tokenId: string, addressToCheck: string, network?: NetworkName | string | undefined): Promise; 61 | 62 | /** 63 | * Calls the blockchain registry to change the owner of the `resource` NFT. Usually the `signer` must be the owner or an approved address of the resolved resource NFT. 64 | * @param resource a `IResolvedResource` instance 65 | * @param addressTo the address to transfer the resolved resource NFT to 66 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 67 | */ 68 | transfer(resource: IResolvedResource, addressTo: string, signer: ethers.Signer): Promise; 69 | 70 | /** 71 | * Calls the blockchain registry to approve the `addessToApprove` to the `resource` NFT eg. to give the address permission to transfer. Usually the `signer` must be the owner of the resolved resource NFT. 72 | * @param resource an `IResolvedResource` instance. 73 | * @param addessToApprove the address to approve. 74 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 75 | */ 76 | setApproved(resource: IResolvedResource, addessToApprove: string, signer: ethers.Signer): Promise; 77 | 78 | /** 79 | * Calls the blockchain registry to set the specified key-value pair as a record on the resolved resource NFT. 80 | * @param resource an `IResolvedResource` instance. 81 | * @param key the key of the record 82 | * @param value the value of the record 83 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 84 | */ 85 | setRecord(resource: IResolvedResource, key: string, value: string, signer: ethers.Signer): Promise; 86 | 87 | /** 88 | * Calls the blockchain registry to set the specified key-value pairs as records on the resolved resource NFT. 89 | * The `keys` and `values` array must be in order: the first key-value pair will be the first string of the `keys` array as key and the first string of the `values` array as value, and so on. 90 | * @param resource an `IResolvedResource` instance. 91 | * @param keys the keys of the records 92 | * @param values the values of the records 93 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 94 | */ 95 | setRecords(resource: IResolvedResource, keys: string[], values: string[], signer: ethers.Signer): Promise; 96 | 97 | /** 98 | * Set the `signer` address as a reverse resolution record of the resolved resource NFT. 99 | * @param resource an `IResolvedResource` instance. 100 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 101 | */ 102 | setReverse(resource: IResolvedResource, signer: ethers.Signer): Promise; 103 | 104 | /** 105 | * Gets the uri of the `tokenId` NFT. 106 | * @param tokenId the NFT tokenId uint256 string rappresentation. 107 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 108 | * @returns the uri string or undefined 109 | */ 110 | getTokenUri(tokenId: string, network?: NetworkName | string | undefined): Promise 111 | 112 | /** 113 | * Calls the `uri` of the `tokenId` NFT and returns the resulting metadata. 114 | * @param tokenId the NFT tokenId uint256 string rappresentation. 115 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 116 | */ 117 | getMetadata(tokenId: string, network?: NetworkName | string | undefined): Promise 118 | 119 | /** 120 | * Gets the image url portion of the NFT metadata, usually the content of `metadata.image_url`. 121 | * @param tokenId the NFT tokenId uint256 string rappresentation. 122 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 123 | */ 124 | getImageUrl(tokenId: string, network?: NetworkName | string | undefined): Promise; 125 | 126 | /** 127 | * Check if the `tokenId` exists on the blockchain. 128 | * @param tokenId the NFT tokenId uint256 string rappresentation. 129 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 130 | */ 131 | exists(tokenId: string, network?: NetworkName | string | undefined): Promise; 132 | 133 | /** 134 | * Get the current owner address of the `tokenId`. 135 | * @param tokenId the NFT tokenId uint256 string rappresentation. 136 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 137 | */ 138 | getOwnerAddress(tokenId: string, network?: NetworkName | string | undefined): Promise; 139 | 140 | /** 141 | * Gets the record portion of the NFT metadata, usually the content of `metadata.properties`. 142 | * @param tokenId the NFT tokenId uint256 string rappresentation. 143 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 144 | */ 145 | getRecords(tokenId: string, network?: NetworkName | string | undefined): Promise<{ [key: string]: string } | undefined>; 146 | 147 | /** 148 | * Get the value of the record with the specified key from the blockchain. 149 | * @param tokenId the NFT tokenId uint256 string rappresentation. 150 | * @param key the key of the record 151 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 152 | */ 153 | getRecord(tokenId: string, key: string, network?: NetworkName | string | undefined): Promise; 154 | 155 | /** 156 | * Get the value of the record with the specified key from the blockchain registry. 157 | * @param tokenId the NFT tokenId uint256 string rappresentation. 158 | * @param keys the keys of the records 159 | * @param network the network where the NFT is located. If not available every network is scanned to search the `tokenId` NFT. 160 | */ 161 | getManyRecords(tokenId: string, keys: string[], network?: NetworkName | string | undefined): Promise; 162 | } -------------------------------------------------------------------------------- /src/resolvers/resolved-resource/resolved-resource.interface.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { NetworkName } from "../../networks/connections/network-connection.types"; 3 | import { IResolverProvider } from "../../resolver-providers/resolver-provider.interface"; 4 | import { ResolvedResourceType } from "../types/resolved-resource-type"; 5 | import { ProviderName } from "../types/resolver-name"; 6 | 7 | export interface IResolvedResource { 8 | /** 9 | * The full name of the domain or the tld. 10 | */ 11 | fullname: string; 12 | 13 | /** 14 | * The second level domain portion of the full name, without trailing ".". Can be `undefined` in case of a tld. 15 | */ 16 | domain?: string | undefined; 17 | 18 | /** 19 | * The top level domain portion of the full name, without starting ".". 20 | */ 21 | tld: string; 22 | 23 | /** 24 | * Indicates if the resolved resource is a `tld` or a `domain`. 25 | */ 26 | type: ResolvedResourceType; 27 | 28 | /** 29 | * The uint256 string rappresentation of the resolved resource NFT. 30 | */ 31 | tokenId: string; 32 | 33 | /** 34 | * The name of the provider used to resolve the resource. Eg. "freename" or "ens". 35 | */ 36 | providerName: ProviderName | string; 37 | 38 | /** 39 | * The resolver provider instance used to resolve the resource. 40 | */ 41 | resolverProvider: IResolverProvider; 42 | 43 | /** 44 | * The network where the resolved resource NFT is located. 45 | */ 46 | network: NetworkName | string; 47 | 48 | /** 49 | * The address of the smart contract from where the data was read. 50 | */ 51 | proxyReaderAddress: string; 52 | 53 | /** 54 | * The address of the smart contract where the data can be written. 55 | */ 56 | proxyWriterAddress: string; 57 | 58 | /** 59 | * The address of current owner of the resolved resource NFT. 60 | */ 61 | ownerAddress: string; 62 | 63 | /** 64 | * The uri of the resolved resource NFT. 65 | */ 66 | uri: string | undefined; 67 | 68 | /** 69 | * The image url portion of the `metadata`, usually the content of `metadata.image_url`. 70 | */ 71 | imageUrl: string | undefined; 72 | 73 | /** 74 | * The record portion of the `metadata`, usually the content of `metadata.properties`. 75 | */ 76 | records: { [key: string]: string } | undefined; 77 | 78 | /** 79 | * The metadata of the resolved resource, usually the result of a http GET call from the `uri`. 80 | */ 81 | metadata: any | undefined 82 | 83 | /** 84 | * Get the value of the record with the specified key from the blockchain. 85 | * @param key the key of the record 86 | */ 87 | getRecord(key: string): Promise; 88 | 89 | /** 90 | * Get the value of the record with the specified key from the blockchain registry. 91 | * @param keys the keys of the records 92 | */ 93 | getManyRecords(keys: string[]): Promise; 94 | 95 | /** 96 | * Checks on the blockchain registry if the given address is the owner or an approved address for the resolved resource NFT. 97 | * @param address the address to check 98 | */ 99 | isApprovedOrOwner(address: string): Promise 100 | 101 | /** 102 | * Calls the blockchain registry to change the owner of the resolved resource NFT. Usually the `signer` must be the owner or an approved address of the resolved resource NFT. 103 | * @param addressTo the address to transfer the resolved resource NFT to 104 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 105 | */ 106 | transfer(addressTo: string, signer: ethers.Signer): Promise; 107 | 108 | /** 109 | * Calls the blockchain registry to approve the `addessToApprove` to the resolved resource NFT eg. to give the address permission to transfer. Usually the `signer` must be the owner of the resolved resource NFT. 110 | * @param addessToApprove the address to approve. 111 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 112 | */ 113 | setApproved(addessToApprove: string, signer: ethers.Signer): Promise; 114 | 115 | /** 116 | * Calls the blockchain registry to set the specified key-value pair as a record on the resolved resource NFT. 117 | * @param key the key of the record 118 | * @param value the value of the record 119 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 120 | */ 121 | setRecord(key: string, value: string, signer: ethers.Signer): Promise; 122 | 123 | /** 124 | * Calls the blockchain registry to set the specified key-value pairs as records on the resolved resource NFT. 125 | * The `keys` and `values` array must be in order: the first key-value pair will be the first string of the `keys` array as key and the first string of the `values` array as value, and so on. 126 | * @param keys the keys of the records 127 | * @param values the values of the records 128 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 129 | */ 130 | setRecords(keys: string[], values: string[], signer: ethers.Signer): Promise; 131 | 132 | /** 133 | * Set the `signer` address as a reverse resolution record of the resolved resource NFT. 134 | * @param signer a `ether.Signer` wallet. If `signer.provider` is `undefined` the `provider` of the resolved resource's `resolverProvider` is used. 135 | */ 136 | setReverse(signer: ethers.Signer): Promise; 137 | 138 | /** 139 | * Updates all the fields of the current resolved resource. 140 | */ 141 | refresh(): Promise 142 | } -------------------------------------------------------------------------------- /src/resolvers/resolved-resource/resolved-resource.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from "ethers"; 2 | import cloneDeep from "lodash.clonedeep"; 3 | import { NetworkName } from "../../networks/connections/network-connection.types"; 4 | import { IResolverProvider } from "../../resolver-providers/resolver-provider.interface"; 5 | import { ResolvedResourceType } from "../types/resolved-resource-type"; 6 | import { ProviderName } from "../types/resolver-name"; 7 | import { IResolvedResource } from "./resolved-resource.interface"; 8 | 9 | export class ResolvedResource implements IResolvedResource { 10 | 11 | constructor(input: { 12 | fullname: string, 13 | tld: string, 14 | type: ResolvedResourceType, 15 | tokenId: string, 16 | resolverName: ProviderName | string, 17 | resolverProvider: IResolverProvider, 18 | network: NetworkName | string, 19 | proxyReaderAddress: string, 20 | proxyWriterAddress: string, 21 | ownerAddress: string, 22 | metadataUri: string | undefined, 23 | imageUrl: string | undefined, 24 | metadata: any | undefined 25 | records: { [key: string]: string } | undefined, 26 | domain?: string | undefined, 27 | }, 28 | ) { 29 | this._fullname = input.fullname; 30 | this._tld = input.tld; 31 | this._type = input.type; 32 | this._tokenId = input.tokenId; 33 | this._resolverName = input.resolverName; 34 | this._resolverProvider = input.resolverProvider; 35 | this._network = input.network; 36 | this._proxyReaderAddress = input.proxyReaderAddress; 37 | this._proxyWriterAddress = input.proxyWriterAddress; 38 | this._ownerAddress = input.ownerAddress; 39 | this._metadataUri = input.metadataUri; 40 | this._imageUrl = input.imageUrl; 41 | this._metadata = input.metadata; 42 | this._records = input.records; 43 | } 44 | 45 | private _metadata: any | undefined; 46 | public get metadata(): any | undefined { 47 | return this._metadata; 48 | } 49 | public set metadata(value: any | undefined) { 50 | this._metadata = value; 51 | } 52 | 53 | private _fullname: string; 54 | public get fullname(): string { 55 | return this._fullname; 56 | } 57 | public set fullname(value: string) { 58 | this._fullname = value; 59 | } 60 | 61 | private _tld: string; 62 | public get tld(): string { 63 | return this._tld; 64 | } 65 | public set tld(value: string) { 66 | this._tld = value; 67 | } 68 | 69 | private _domain?: string | undefined; 70 | public get domain(): string | undefined { 71 | return this._domain; 72 | } 73 | public set domain(value: string | undefined) { 74 | this._domain = value; 75 | } 76 | 77 | private _type: ResolvedResourceType; 78 | public get type(): ResolvedResourceType { 79 | return this._type; 80 | } 81 | public set type(value: ResolvedResourceType) { 82 | this._type = value; 83 | } 84 | 85 | private _tokenId: string; 86 | public get tokenId(): string { 87 | return this._tokenId; 88 | } 89 | public set tokenId(value: string) { 90 | this._tokenId = value; 91 | } 92 | 93 | private _resolverName: ProviderName | string; 94 | public get providerName(): ProviderName | string { 95 | return this._resolverName; 96 | } 97 | public set providerName(value: ProviderName | string) { 98 | this._resolverName = value; 99 | } 100 | 101 | private _resolverProvider: IResolverProvider; 102 | public get resolverProvider(): IResolverProvider { 103 | return this._resolverProvider; 104 | } 105 | public set resolverProvider(value: IResolverProvider) { 106 | this._resolverProvider = value; 107 | } 108 | 109 | private _network: NetworkName | string; 110 | public get network(): NetworkName | string { 111 | return this._network; 112 | } 113 | public set network(value: NetworkName | string) { 114 | this._network = value; 115 | } 116 | 117 | private _proxyReaderAddress: string; 118 | public get proxyReaderAddress(): string { 119 | return this._proxyReaderAddress; 120 | } 121 | public set proxyReaderAddress(value: string) { 122 | this._proxyReaderAddress = value; 123 | } 124 | 125 | private _proxyWriterAddress: string; 126 | public get proxyWriterAddress(): string { 127 | return this._proxyWriterAddress; 128 | } 129 | public set proxyWriterAddress(value: string) { 130 | this._proxyWriterAddress = value; 131 | } 132 | 133 | private _ownerAddress: string; 134 | public get ownerAddress(): string { 135 | return this._ownerAddress; 136 | } 137 | public set ownerAddress(value: string) { 138 | this._ownerAddress = value; 139 | } 140 | 141 | private _metadataUri: string | undefined; 142 | public get uri(): string | undefined { 143 | return this._metadataUri; 144 | } 145 | public set uri(value: string | undefined) { 146 | this._metadataUri = value; 147 | } 148 | 149 | private _imageUrl: string | undefined; 150 | public get imageUrl(): string | undefined { 151 | return this._imageUrl; 152 | } 153 | public set imageUrl(value: string | undefined) { 154 | this._imageUrl = value; 155 | } 156 | 157 | private _records: { [key: string]: string; } | undefined; 158 | public get records(): { [key: string]: string; } | undefined { 159 | return this._records; 160 | } 161 | 162 | public async getRecord(key: string): Promise { 163 | return await this.resolverProvider.getRecord(this._tokenId, key, this._network); 164 | } 165 | 166 | public async getManyRecords(keys: string[]): Promise { 167 | return await this.resolverProvider.getManyRecords(this._tokenId, keys, this._network); 168 | } 169 | 170 | public async isApprovedOrOwner(address: string): Promise { 171 | return await this.resolverProvider.isApprovedOrOwner(this._tokenId, address, this._network); 172 | } 173 | 174 | public async transfer(addressTo: string, signer: Signer): Promise { 175 | return await this._resolverProvider.transfer(this, addressTo, signer); 176 | } 177 | 178 | public async setApproved(addessToApprove: string, signer: Signer): Promise { 179 | return await this._resolverProvider.setApproved(this, addessToApprove, signer); 180 | } 181 | 182 | public async setRecord(key: string, value: string, signer: Signer): Promise { 183 | return await this._resolverProvider.setRecord(this, key, value, signer); 184 | } 185 | 186 | public async setRecords(keys: string[], values: string[], signer: Signer): Promise { 187 | return await this._resolverProvider.setRecords(this, keys, values, signer); 188 | } 189 | 190 | public async setReverse(signer: Signer): Promise { 191 | return await this._resolverProvider.setReverse(this, signer); 192 | } 193 | 194 | public async refresh(): Promise { 195 | let resolvedResource = await this.resolverProvider.resolve(this._fullname); 196 | if (resolvedResource) { 197 | this._fullname = resolvedResource.fullname; 198 | this._tld = resolvedResource.tld; 199 | this._type = resolvedResource.type; 200 | this._tokenId = resolvedResource.tokenId; 201 | this._resolverName = resolvedResource.providerName; 202 | this._network = resolvedResource.network; 203 | this._proxyReaderAddress = resolvedResource.proxyReaderAddress; 204 | this._proxyWriterAddress = resolvedResource.proxyWriterAddress; 205 | this._ownerAddress = resolvedResource.ownerAddress; 206 | this._metadataUri = resolvedResource.uri; 207 | this._imageUrl = resolvedResource.imageUrl; 208 | this._metadata = cloneDeep(resolvedResource.metadata); 209 | this._records = cloneDeep(resolvedResource.records); 210 | resolvedResource = undefined; 211 | } else { 212 | return undefined; 213 | } 214 | return this; 215 | } 216 | } -------------------------------------------------------------------------------- /src/resolvers/resolver.ts: -------------------------------------------------------------------------------- 1 | import { ResolverProviderRouter } from "../resolver-providers/resolver-provider-router"; 2 | import { IResolverProvider } from "../resolver-providers/resolver-provider.interface"; 3 | import { IResolvedResource } from "./resolved-resource/resolved-resource.interface"; 4 | import { ProviderName } from "./types/resolver-name"; 5 | 6 | export class Resolver { 7 | 8 | constructor(resolverProviders: IResolverProvider[]) { 9 | this._resolverProviderRouter = new ResolverProviderRouter(resolverProviders); 10 | } 11 | 12 | private _resolverProviderRouter: ResolverProviderRouter; 13 | 14 | /** 15 | * Set the order in which the resolver providers are interrogated to resolve a name or tokenId. 16 | * @param priority the new priority of the providers. 17 | */ 18 | public setResolversPriority(priority: Array) { 19 | this._resolverProviderRouter.setResolverProvidersPriority(priority); 20 | } 21 | 22 | /** 23 | * Adds resolver providers to the Resolver. 24 | * @param resolverProviders the resolver providers to add. 25 | */ 26 | public addResolverProviders(resolverProviders: IResolverProvider | IResolverProvider[]) { 27 | this._resolverProviderRouter.addResolverProviders(resolverProviders); 28 | } 29 | 30 | /** 31 | * Resolve the given domain fullname or tld. 32 | * If the domain is valid, exists on the blockchain and can be resolved a `IResolvedResource` is given, otherwise the result is `undefined`. 33 | * To obtain the resolved resource a series of calls to the blockchain are made, depending on the chain traffic the `resolve` call can take a couple of seconds to be completed. 34 | * @param domainOrTld the domain to resolve. Eg. `"test.web3domain"` 35 | * @returns an `IResolvedResource` instance or `undefined`. 36 | */ 37 | public async resolve(domainOrTld: string): Promise { 38 | const resolverProvider = this._resolverProviderRouter.getResolverProviderByDomainOrTld(domainOrTld); 39 | if (resolverProvider) { 40 | return await resolverProvider.resolve(domainOrTld); 41 | } 42 | return undefined; 43 | } 44 | 45 | /** 46 | * Resolves the given tokenId. 47 | * If the tokenId is valid, exists on the blockchain and can be resolved a `IResolvedResource` is given, otherwise the result is `undefined`. 48 | * To obtain the resolved resource a series of calls to the blockchain are made, depending on the chain traffic the `resolveFromTokenId` call can take a couple of seconds to be completed. 49 | * To speed up the resolution a `ResolverName` can be provided, in this case only the given provider is checked. 50 | * @param tokenId the NFT tokenId uint256 string rappresentation to resolve 51 | * @param resolverProviderName the provider of the tokenId to resolve 52 | * @returns an `IResolvedResource` instance or `undefined`. 53 | */ 54 | public async resolveFromTokenId(tokenId: string, resolverProviderName?: ProviderName | string): Promise { 55 | let resolverProvider; 56 | if (resolverProviderName) { 57 | resolverProvider = this._resolverProviderRouter.getResolverProvider(resolverProviderName); 58 | } else { 59 | resolverProvider = await this._resolverProviderRouter.findTokenIdResolverProvider(tokenId); 60 | } 61 | 62 | if (resolverProvider) { 63 | return resolverProvider.resolveFromTokenId(tokenId); 64 | } 65 | return undefined; 66 | } 67 | 68 | /** 69 | * 70 | * @param address 71 | * @param resolverProviderName 72 | * @returns 73 | */ 74 | public async reverseResolve(address: string, resolverProviderName?: ProviderName | string): Promise { 75 | if (resolverProviderName) { 76 | const resolverProvider = this._resolverProviderRouter.getResolverProvider(resolverProviderName); 77 | if (!resolverProvider) { 78 | return undefined; 79 | } 80 | 81 | const tokenId = await resolverProvider.reverseResolve(address); 82 | if (tokenId) { 83 | return await this.resolveFromTokenId(tokenId, resolverProvider.name); 84 | } 85 | 86 | } else { 87 | for (const resolverProvider of this._resolverProviderRouter.resolverProviders) { 88 | const tokenId = await resolverProvider.reverseResolve(address); 89 | if (tokenId) { 90 | return await this.resolveFromTokenId(tokenId, resolverProvider.name); 91 | } 92 | } 93 | } 94 | return undefined; 95 | } 96 | } -------------------------------------------------------------------------------- /src/resolvers/types/resolved-resource-type.ts: -------------------------------------------------------------------------------- 1 | export enum ResolvedResourceType { 2 | TLD = "tld", 3 | SECOND_LEVEL_DOMAIN = "domain", 4 | UNTYPED = "UNTYPED" 5 | } -------------------------------------------------------------------------------- /src/resolvers/types/resolver-name.ts: -------------------------------------------------------------------------------- 1 | export enum ProviderName { 2 | FREENAME = "freename", 3 | UD = "unstoppable", 4 | ENS = "ens" 5 | } -------------------------------------------------------------------------------- /src/tools/api-caller.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export class ApiCaller{ 4 | static async getHttpsCall(url: string): Promise { 5 | try { 6 | const response = await axios.get(url); 7 | if (response) { 8 | return response.data; 9 | } 10 | } catch { 11 | return undefined; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/tools/name-tools.ts: -------------------------------------------------------------------------------- 1 | import { ResolvedResourceType } from "../resolvers/types/resolved-resource-type"; 2 | import { MappedName, NameType } from "./name-tools.types"; 3 | const punycode = require("punycode"); 4 | 5 | export class NameTools { 6 | static mapName(domainOrTld: string): MappedName | undefined { 7 | if (!domainOrTld) { 8 | return undefined; 9 | } 10 | const detailedName = this.getDetailedName(domainOrTld); 11 | if (!detailedName) { 12 | return undefined; 13 | } 14 | 15 | //portions 16 | let tld = null; 17 | let tldAsciiName = null; 18 | let secondLevelDomain = null; 19 | let secondLevelDomainAsciiName = null; 20 | 21 | if (detailedName.type === NameType.TLD) { 22 | tld = domainOrTld; 23 | tldAsciiName = detailedName.asciiName; 24 | } 25 | else if (detailedName.type === NameType.SECOND_LEVEL_DOMAIN || detailedName.type === NameType.SUB_DOMAINED_DOMAIN) { 26 | const nameSplitted = detailedName.name.split("."); 27 | const asciiNameSplitted = detailedName.asciiName.split("."); 28 | if (nameSplitted.length === 2 && asciiNameSplitted.length === 2) { 29 | tld = nameSplitted[nameSplitted.length - 1]; 30 | secondLevelDomain = nameSplitted[nameSplitted.length - 2]; 31 | 32 | tldAsciiName = asciiNameSplitted[asciiNameSplitted.length - 1]; 33 | secondLevelDomainAsciiName = asciiNameSplitted[asciiNameSplitted.length - 2]; 34 | } 35 | } 36 | 37 | return { 38 | domain: secondLevelDomainAsciiName, 39 | fullname: domainOrTld, 40 | tld: tldAsciiName, 41 | type: detailedName.type, 42 | }; 43 | } 44 | 45 | private static getItemTypeFromString(name: string): NameType { 46 | if (name.includes(".")) { 47 | const splitted = name.split("."); 48 | const cleanSplitted = splitted.filter(s => s !== ""); 49 | if (cleanSplitted.length === 2) { 50 | return NameType.SECOND_LEVEL_DOMAIN; 51 | } else if (cleanSplitted.length === 1) { 52 | return NameType.TLD; 53 | } else if (cleanSplitted.length > 2) { 54 | return NameType.SUB_DOMAINED_DOMAIN; 55 | } 56 | } 57 | 58 | return NameType.TLD; 59 | } 60 | 61 | private static getDetailedName(name: string) { 62 | let foundError = false; 63 | const errors: string[] = []; 64 | 65 | //We need the ascii string to avoid detecting emoji and other unicode characters as invalid 66 | const asciiString = punycode.toASCII(name); 67 | 68 | //Check if the search input contains forbidden characters 69 | const isSearchStringAllowable = this.isNameAllowed(asciiString); 70 | if (!isSearchStringAllowable?.isAllowable) { 71 | errors.push("INVALID_NAME"); 72 | foundError = true; 73 | } 74 | 75 | //Check if the search input name has a repeated dot (es. app..cryptotld) 76 | const dotCount = asciiString.split(".").length - 1; 77 | if (dotCount > 1) { 78 | errors.push("REPETED_DOT"); 79 | foundError = true; 80 | } 81 | 82 | if (!foundError) { 83 | let asciiName = asciiString; 84 | const itemType = this.getItemTypeFromString(asciiString); 85 | 86 | if (itemType == NameType.SUB_DOMAINED_DOMAIN) { 87 | //The search input name is a sub domained domain (es. freename.app.cryptotld) 88 | errors.push("SUB_DOMAINS_NOT_ALLOWED"); 89 | } else { 90 | if (itemType == NameType.TLD) { 91 | //remove the "." from the start of the tld (es .freename becomes freename) 92 | if (name.includes(".")) { 93 | name = name.replace(/\./g, ""); 94 | asciiName = asciiName.replace(/\./g, ""); 95 | } 96 | } 97 | return { 98 | asciiName, 99 | name, 100 | type: itemType, 101 | }; 102 | } 103 | } 104 | } 105 | 106 | static isNameAllowed(name: string) { 107 | //Check if search string contains only allowed characters 108 | const allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-."; 109 | let invalidCharacters = []; 110 | let isAllowable = true; 111 | let dotsOnly = true; 112 | for (let i = 0; i < name.length; i++) { 113 | const char = name.charAt(i); 114 | if (allowedCharacters.indexOf(char) === -1) { 115 | invalidCharacters.push(char); 116 | isAllowable = false; 117 | } 118 | if (char !== ".") { 119 | dotsOnly = false; 120 | } 121 | } 122 | 123 | if (dotsOnly) { 124 | invalidCharacters = ["."]; 125 | isAllowable = false; 126 | } 127 | 128 | return { 129 | isAllowable, 130 | invalidCharacters, 131 | }; 132 | } 133 | 134 | static getResolvedResourceType(type: NameType): ResolvedResourceType { 135 | switch (type) { 136 | case NameType.TLD: 137 | return ResolvedResourceType.TLD; 138 | case NameType.SECOND_LEVEL_DOMAIN: 139 | return ResolvedResourceType.SECOND_LEVEL_DOMAIN; 140 | default: 141 | return ResolvedResourceType.UNTYPED; 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /src/tools/name-tools.types.ts: -------------------------------------------------------------------------------- 1 | export enum NameType { 2 | TLD = "tld", 3 | SECOND_LEVEL_DOMAIN = "domain", 4 | SUB_DOMAINED_DOMAIN = "sub-domain" 5 | } 6 | 7 | export type MappedName = { 8 | fullname: string, 9 | domain?: string | undefined, 10 | tld: string, 11 | type: NameType, 12 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "skipLibCheck": true, 10 | "moduleResolution": "node", 11 | "outDir": "./lib", 12 | "allowJs": true, 13 | "declaration": true 14 | }, 15 | "include": [ 16 | "src" 17 | ], 18 | "exclude": [ 19 | "node_modules", 20 | "**/__tests__/*" 21 | ] 22 | } --------------------------------------------------------------------------------