├── .github └── workflows │ ├── golangci-lint.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── auctionregistrar.go ├── baseregistrar.go ├── contenthash.go ├── contenthash_test.go ├── contracts ├── auctionregistrar │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── baseregistrar │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── deed │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── dnsregistrar │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── dnsresolver │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── dnssecoracle │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── ethcontroller │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── registry │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── resolver │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── reverseregistrar │ ├── contract.abi │ ├── contract.go │ └── generate.go └── reverseresolver │ ├── contract.abi │ ├── contract.go │ └── generate.go ├── deed.go ├── dnsregistrar.go ├── dnsresolver.go ├── dnssecoracle.go ├── ethcontroller.go ├── go.mod ├── go.sum ├── misc.go ├── misc_test.go ├── name.go ├── name_test.go ├── namehash.go ├── namehash_test.go ├── registrar.go ├── registry.go ├── resolver.go ├── resolver_test.go ├── reverseregistrar.go ├── reverseresolver.go ├── reverseresolver_test.go ├── tokenid.go ├── tokenid_test.go └── util └── signer.go /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - uses: actions/setup-go@v3 17 | with: 18 | go-version: '1.20' 19 | - uses: actions/checkout@v3 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v3 22 | with: 23 | args: --timeout=60m 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | test: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/setup-go@v3 12 | with: 13 | go-version: '1.20' 14 | - uses: actions/checkout@v3 15 | - uses: n8maninger/action-golang-test@v1 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | coverage.html 13 | 14 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 15 | .glide/ 16 | 17 | # Vim 18 | *.sw? 19 | 20 | # Local TODO 21 | TODO.md 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This file contains all available configuration options 2 | # with their default values (in comments). 3 | # 4 | # This file is not a configuration example, 5 | # it contains the exhaustive configuration with explanations of the options. 6 | 7 | issues: 8 | # Which files to exclude: they will be analyzed, but issues from them won't be reported. 9 | # There is no need to include all autogenerated files, 10 | # we confidently recognize autogenerated files. 11 | # If it's not, please let us know. 12 | # "/" will be replaced by current OS file path separator to properly work on Windows. 13 | # Default: [] 14 | exclude-files: 15 | - ".*_ssz\\.go$" 16 | 17 | include: 18 | - 'EXC0002' 19 | - 'EXC0005' 20 | - 'EXC0009' 21 | - 'EXC0011' 22 | - 'EXC0012' 23 | - 'EXC0014' 24 | 25 | # Options for analysis running. 26 | run: 27 | # The default concurrency value is the number of available CPU. 28 | # concurrency: 4 29 | 30 | # Timeout for analysis, e.g. 30s, 5m. 31 | # Default: 1m 32 | timeout: 10m 33 | 34 | # Exit code when at least one issue was found. 35 | # Default: 1 36 | # issues-exit-code: 2 37 | 38 | # Include test files or not. 39 | # Default: true 40 | tests: false 41 | 42 | # List of build tags, all linters use it. 43 | # Default: []. 44 | # build-tags: 45 | # - mytag 46 | 47 | # Which dirs to skip: issues from them won't be reported. 48 | # Can use regexp here: `generated.*`, regexp is applied on full path. 49 | # Default value is empty list, 50 | # but default dirs are skipped independently of this option's value (see skip-dirs-use-default). 51 | # "/" will be replaced by current OS file path separator to properly work on Windows. 52 | # skip-dirs: 53 | # - autogenerated_by_my_lib 54 | 55 | # Enables skipping of directories: 56 | # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 57 | # Default: true 58 | # skip-dirs-use-default: false 59 | 60 | # If set we pass it to "go list -mod={option}". From "go help modules": 61 | # If invoked with -mod=readonly, the go command is disallowed from the implicit 62 | # automatic updating of go.mod described above. Instead, it fails when any changes 63 | # to go.mod are needed. This setting is most useful to check that go.mod does 64 | # not need updates, such as in a continuous integration and testing system. 65 | # If invoked with -mod=vendor, the go command assumes that the vendor 66 | # directory holds the correct copies of dependencies and ignores 67 | # the dependency descriptions in go.mod. 68 | # 69 | # Allowed values: readonly|vendor|mod 70 | # By default, it isn't set. 71 | modules-download-mode: readonly 72 | 73 | # Allow multiple parallel golangci-lint instances running. 74 | # If false (default) - golangci-lint acquires file lock on start. 75 | allow-parallel-runners: true 76 | 77 | # Define the Go version limit. 78 | # Mainly related to generics support since go1.18. 79 | # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.18 80 | # go: '1.19' 81 | 82 | 83 | # output configuration options 84 | output: 85 | # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions 86 | # 87 | # Multiple can be specified by separating them by comma, output can be provided 88 | # for each of them by separating format name and path by colon symbol. 89 | # Output path can be either `stdout`, `stderr` or path to the file to write to. 90 | # Example: "checkstyle:report.json,colored-line-number" 91 | # 92 | # Default: colored-line-number 93 | # format: json 94 | 95 | # Print lines of code with issue. 96 | # Default: true 97 | # print-issued-lines: false 98 | 99 | # Print linter name in the end of issue text. 100 | # Default: true 101 | # print-linter-name: false 102 | 103 | # Make issues output unique by line. 104 | # Default: true 105 | # uniq-by-line: false 106 | 107 | # Add a prefix to the output file references. 108 | # Default is no prefix. 109 | # path-prefix: "" 110 | 111 | # Sort results by: filepath, line and column. 112 | # sort-results: true 113 | 114 | 115 | # All available settings of specific linters. 116 | linters-settings: 117 | gosec: 118 | excludes: 119 | - G115 # This generates a lot of false positives, recheck once https://github.com/securego/gosec/issues/1212 is closed 120 | 121 | lll: 122 | line-length: 132 123 | 124 | nlreturn: 125 | # Allow two-line blocks without requiring a newline 126 | block-size: 3 127 | 128 | stylecheck: 129 | checks: [ "all", "-ST1000" ] 130 | 131 | tagliatelle: 132 | case: 133 | # use-field-name: true 134 | rules: 135 | json: snake 136 | yaml: snake 137 | 138 | linters: 139 | # Enable all available linters. 140 | # Default: false 141 | enable-all: true 142 | # Disable specific linter 143 | # https://golangci-lint.run/usage/linters/#disabled-by-default 144 | disable: 145 | - cyclop 146 | - depguard 147 | - dupl 148 | - err113 149 | - exhaustruct 150 | - exportloopref 151 | - forbidigo 152 | - forcetypeassert 153 | - funlen 154 | - gci 155 | - gochecknoglobals 156 | - gocognit 157 | - goconst 158 | - goheader 159 | - ireturn 160 | - lll 161 | - mnd 162 | - musttag 163 | - nestif 164 | - nilnil 165 | - nlreturn 166 | - perfsprint 167 | - varnamelen 168 | - wrapcheck 169 | - wsl 170 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.x" 5 | - tip 6 | 7 | before_install: 8 | - go get -t -v ./... 9 | 10 | script: 11 | - go test -race -coverprofile=coverage.txt -covermode=atomic 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-ens 2 | 3 | [![Tag](https://img.shields.io/github/tag/wealdtech/go-ens.svg)](https://github.com/wealdtech/go-ens/releases/) 4 | [![License](https://img.shields.io/github/license/wealdtech/go-ens.svg)](LICENSE) 5 | [![GoDoc](https://godoc.org/github.com/wealdtech/go-ens?status.svg)](https://godoc.org/github.com/wealdtech/go-ens) 6 | [![Travis CI](https://img.shields.io/travis/wealdtech/go-ens.svg)](https://travis-ci.org/wealdtech/go-ens) 7 | [![codecov.io](https://img.shields.io/codecov/c/github/wealdtech/go-ens.svg)](https://codecov.io/github/wealdtech/go-ens) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/wealdtech/go-ens)](https://goreportcard.com/report/github.com/wealdtech/go-ens) 9 | 10 | Go module to simplify interacting with the [Ethereum Name Service](https://ens.domains/) contracts. 11 | 12 | 13 | ## Table of Contents 14 | 15 | - [Install](#install) 16 | - [Usage](#usage) 17 | - [Maintainers](#maintainers) 18 | - [Contribute](#contribute) 19 | - [License](#license) 20 | 21 | ## Install 22 | 23 | `go-ens` is a standard Go module which can be installed with: 24 | 25 | ```sh 26 | go get github.com/wealdtech/go-ens/v3 27 | ``` 28 | 29 | ## Usage 30 | 31 | `go-ens` provides simple access to the [Ethereum Name Service](https://ens.domains/) (ENS) contracts. 32 | 33 | ### Resolution 34 | 35 | The most commonly-used feature of ENS is resolution: converting an ENS name to an Ethereum address. `go-ens` provides a simple call to allow this: 36 | 37 | ```go 38 | address, err := ens.Resolve(client, domain) 39 | ``` 40 | 41 | where `client` is a connection to an Ethereum client and `domain` is the fully-qualified name you wish to resolve (e.g. `foo.mydomain.eth`) (full examples for using this are given in the [Example](#Example) section below). 42 | 43 | The reverse process, converting an address to an ENS name, is just as simple: 44 | 45 | ```go 46 | domain, err := ens.ReverseResolve(client, address) 47 | ``` 48 | 49 | Note that if the address does not have a reverse resolution this will return "". If you just want a string version of an address for on-screen display then you can use `ens.Format()`, for example: 50 | 51 | ```go 52 | fmt.Printf("The address is %s\n", ens.Format(client, address)) 53 | ``` 54 | 55 | This will carry out reverse resolution of the address and print the name if present; if not it will print a formatted version of the address. 56 | 57 | 58 | ### Management of names 59 | 60 | A top-level name is one that sits directly underneath `.eth`, for example `mydomain.eth`. Lower-level names, such as `foo.mydomain.eth` are covered in the following section. `go-ens` provides a simplified `Name` interface to manage top-level, removing the requirement to understand registrars, controllers, _etc._ 61 | 62 | Starting out with names in `go-ens` is easy: 63 | 64 | ```go 65 | client, err := ethclient.Dial("https://infura.io/v3/SECRET") 66 | name, err := ens.NewName(client, "mydomain.eth") 67 | ``` 68 | 69 | Addresses can be set and obtained using the address functions, for example to get an address: 70 | 71 | ```go 72 | COIN_TYPE_ETHEREUM := uint64(60) 73 | address, err := name.Address(COIN_TYPE_ETHEREUM) 74 | ``` 75 | 76 | ENS supports addresses for multiple coin types; values of coin types can be found at https://github.com/satoshilabs/slips/blob/master/slip-0044.md 77 | 78 | ### Registering and extending names 79 | 80 | Most operations on a domain will involve setting resolvers and resolver information. 81 | 82 | 83 | ### Management of subdomains 84 | 85 | Because subdomains have their own registrars they do not work with the `Name` interface. 86 | 87 | ### Example 88 | 89 | ```go 90 | package main 91 | 92 | import ( 93 | "fmt" 94 | 95 | "github.com/ethereum/go-ethereum/ethclient" 96 | ens "github.com/wealdtech/go-ens/v3" 97 | ) 98 | 99 | func main() { 100 | // Replace SECRET with your own access token for this example to work. 101 | client, err := ethclient.Dial("https://mainnet.infura.io/v3/SECRET") 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | // Resolve a name to an address. 107 | domain := "ethereum.eth" 108 | address, err := ens.Resolve(client, domain) 109 | if err != nil { 110 | panic(err) 111 | } 112 | fmt.Printf("Address of %s is %s\n", domain, address.Hex()) 113 | 114 | // Reverse resolve an address to a name. 115 | reverse, err := ens.ReverseResolve(client, address) 116 | if err != nil { 117 | panic(err) 118 | } 119 | if reverse == "" { 120 | fmt.Printf("%s has no reverse lookup\n", address.Hex()) 121 | } else { 122 | fmt.Printf("Name of %s is %s\n", address.Hex(), reverse) 123 | } 124 | } 125 | ``` 126 | 127 | ## Maintainers 128 | 129 | Jim McDonald: [@mcdee](https://github.com/mcdee). 130 | 131 | ## Contribute 132 | 133 | Contributions welcome. Please check out [the issues](https://github.com/wealdtech/go-ens/issues). 134 | 135 | ## License 136 | 137 | [Apache-2.0](LICENSE) © 2019 Weald Technology Trading Ltd 138 | -------------------------------------------------------------------------------- /auctionregistrar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "fmt" 19 | "math/big" 20 | "time" 21 | 22 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 23 | "github.com/ethereum/go-ethereum/common" 24 | "github.com/ethereum/go-ethereum/core/types" 25 | "github.com/wealdtech/go-ens/v3/contracts/auctionregistrar" 26 | ) 27 | 28 | // AuctionRegistrar is the structure for the auction registrar contract. 29 | type AuctionRegistrar struct { 30 | backend bind.ContractBackend 31 | domain string 32 | Contract *auctionregistrar.Contract 33 | ContractAddr common.Address 34 | } 35 | 36 | // AuctionEntry is an auction entry. 37 | type AuctionEntry struct { 38 | State string 39 | Deed common.Address 40 | Registration time.Time 41 | Value *big.Int 42 | HighestBid *big.Int 43 | } 44 | 45 | // NewAuctionRegistrar creates a new auction registrar for a given domain. 46 | func NewAuctionRegistrar(backend bind.ContractBackend, domain string) (*AuctionRegistrar, error) { 47 | address, err := RegistrarContractAddress(backend, domain) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return NewAuctionRegistrarAt(backend, domain, address) 53 | } 54 | 55 | // NewAuctionRegistrarAt creates an auction registrar for a given domain at a given address. 56 | func NewAuctionRegistrarAt(backend bind.ContractBackend, domain string, address common.Address) (*AuctionRegistrar, error) { 57 | contract, err := auctionregistrar.NewContract(address, backend) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return &AuctionRegistrar{ 62 | backend: backend, 63 | domain: domain, 64 | Contract: contract, 65 | ContractAddr: address, 66 | }, nil 67 | } 68 | 69 | // State returns the state of a name. 70 | func (r *AuctionRegistrar) State(name string) (string, error) { 71 | entry, err := r.Entry(name) 72 | if err != nil { 73 | return "", err 74 | } 75 | if entry == nil { 76 | return "", fmt.Errorf("no entry for %s", name) 77 | } 78 | return entry.State, nil 79 | } 80 | 81 | // Entry obtains a registrar entry for a name. 82 | func (r *AuctionRegistrar) Entry(domain string) (*AuctionEntry, error) { 83 | name, err := UnqualifiedName(domain, r.domain) 84 | if err != nil { 85 | return nil, fmt.Errorf("invalid name %s", domain) 86 | } 87 | 88 | labelHash, err := LabelHash(name) 89 | if err != nil { 90 | return nil, err 91 | } 92 | status, deedAddress, registration, value, highestBid, err := r.Contract.Entries(nil, labelHash) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | entry := &AuctionEntry{ 98 | Deed: deedAddress, 99 | Value: value, 100 | HighestBid: highestBid, 101 | } 102 | entry.Registration = time.Unix(registration.Int64(), 0) 103 | switch status { 104 | case 0: 105 | entry.State = "Available" 106 | case 1: 107 | entry.State = "Bidding" 108 | case 2: 109 | // Might be won or owned. 110 | registry, err := NewRegistry(r.backend) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | owner, err := registry.Owner(domain) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | if owner == UnknownAddress { 121 | entry.State = "Won" 122 | } else { 123 | entry.State = "Owned" 124 | } 125 | case 3: 126 | entry.State = "Forbidden" 127 | case 4: 128 | entry.State = "Revealing" 129 | case 5: 130 | entry.State = "Unavailable" 131 | default: 132 | entry.State = "Unknown" 133 | } 134 | 135 | return entry, nil 136 | } 137 | 138 | // Migrate migrates a domain to the permanent registrar. 139 | func (r *AuctionRegistrar) Migrate(opts *bind.TransactOpts, domain string) (*types.Transaction, error) { 140 | name, err := UnqualifiedName(domain, r.domain) 141 | if err != nil { 142 | return nil, fmt.Errorf("invalid name %s", domain) 143 | } 144 | 145 | labelHash, err := LabelHash(name) 146 | if err != nil { 147 | return nil, err 148 | } 149 | return r.Contract.TransferRegistrars(opts, labelHash) 150 | } 151 | 152 | // Release releases a domain. 153 | func (r *AuctionRegistrar) Release(opts *bind.TransactOpts, domain string) (*types.Transaction, error) { 154 | name, err := UnqualifiedName(domain, r.domain) 155 | if err != nil { 156 | return nil, fmt.Errorf("invalid name %s", domain) 157 | } 158 | 159 | labelHash, err := LabelHash(name) 160 | if err != nil { 161 | return nil, err 162 | } 163 | return r.Contract.ReleaseDeed(opts, labelHash) 164 | } 165 | 166 | // Owner obtains the owner of the deed that represents the name. 167 | func (r *AuctionRegistrar) Owner(domain string) (common.Address, error) { 168 | name, err := UnqualifiedName(domain, r.domain) 169 | if err != nil { 170 | return UnknownAddress, err 171 | } 172 | 173 | entry, err := r.Entry(name) 174 | if err != nil { 175 | return UnknownAddress, err 176 | } 177 | 178 | deed, err := NewDeedAt(r.backend, entry.Deed) 179 | if err != nil { 180 | return UnknownAddress, err 181 | } 182 | return deed.Owner() 183 | } 184 | 185 | // SetOwner sets the owner of the deed that represents the name. 186 | func (r *AuctionRegistrar) SetOwner(opts *bind.TransactOpts, domain string, address common.Address) (*types.Transaction, error) { 187 | name, err := UnqualifiedName(domain, r.domain) 188 | if err != nil { 189 | return nil, fmt.Errorf("invalid name %s", domain) 190 | } 191 | 192 | labelHash, err := LabelHash(name) 193 | if err != nil { 194 | return nil, err 195 | } 196 | return r.Contract.Transfer(opts, labelHash, address) 197 | } 198 | 199 | // ShaBid calculates the hash for a bid. 200 | func (r *AuctionRegistrar) ShaBid(hash [32]byte, address common.Address, value *big.Int, salt [32]byte) ([32]byte, error) { 201 | return r.Contract.ShaBid(nil, hash, address, value, salt) 202 | } 203 | -------------------------------------------------------------------------------- /baseregistrar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 - 2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "math/big" 22 | 23 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 24 | "github.com/ethereum/go-ethereum/common" 25 | "github.com/ethereum/go-ethereum/core/types" 26 | "github.com/wealdtech/go-ens/v3/contracts/baseregistrar" 27 | "golang.org/x/crypto/sha3" 28 | ) 29 | 30 | // BaseRegistrar is the structure for the registrar. 31 | type BaseRegistrar struct { 32 | backend bind.ContractBackend 33 | domain string 34 | Contract *baseregistrar.Contract 35 | ContractAddr common.Address 36 | } 37 | 38 | // NewBaseRegistrar obtains the registrar contract for a given domain. 39 | func NewBaseRegistrar(backend bind.ContractBackend, domain string) (*BaseRegistrar, error) { 40 | address, err := RegistrarContractAddress(backend, domain) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | if address == UnknownAddress { 46 | return nil, fmt.Errorf("no registrar for domain %s", domain) 47 | } 48 | 49 | contract, err := baseregistrar.NewContract(address, backend) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | supported, err := contract.SupportsInterface(nil, [4]byte{0x28, 0xed, 0x4f, 0x6c}) 55 | if err != nil { 56 | return nil, err 57 | } 58 | if !supported { 59 | return nil, fmt.Errorf("purported registrar for domain %s does not support reclaim functionality", domain) 60 | } 61 | 62 | return &BaseRegistrar{ 63 | backend: backend, 64 | domain: domain, 65 | Contract: contract, 66 | ContractAddr: address, 67 | }, nil 68 | } 69 | 70 | // PriorAuctionContract obtains the previous (auction) registrar contract. 71 | func (r *BaseRegistrar) PriorAuctionContract() (*AuctionRegistrar, error) { 72 | address, err := r.Contract.PreviousRegistrar(nil) 73 | if err != nil { 74 | // Means there is no prior registrar. 75 | //nolint:nilerr 76 | return nil, nil 77 | } 78 | auctionContract, err := NewAuctionRegistrarAt(r.backend, r.domain, address) 79 | if err != nil { 80 | return nil, errors.New("failed to instantiate prior auction contract") 81 | } 82 | 83 | // Confirm ths really is an auction contract by trying to create (but not submit) a bid. 84 | var shaBid [32]byte 85 | var emptyHash [32]byte 86 | sha := sha3.NewLegacyKeccak256() 87 | if _, err := sha.Write(emptyHash[:]); err != nil { 88 | return nil, err 89 | } 90 | if _, err := sha.Write(UnknownAddress.Bytes()); err != nil { 91 | return nil, err 92 | } 93 | var amountBytes [32]byte 94 | if _, err := sha.Write(amountBytes[:]); err != nil { 95 | return nil, err 96 | } 97 | if _, err := sha.Write(emptyHash[:]); err != nil { 98 | return nil, err 99 | } 100 | sha.Sum(shaBid[:0]) 101 | 102 | contractShaBid, err := auctionContract.ShaBid(emptyHash, UnknownAddress, big.NewInt(0), emptyHash) 103 | if err != nil { 104 | return nil, errors.New("failed to confirm auction contract") 105 | } 106 | if !bytes.Equal(contractShaBid[:], shaBid[:]) { 107 | return nil, errors.New("failed to confirm auction contract") 108 | } 109 | 110 | return auctionContract, nil 111 | } 112 | 113 | // RegisteredWith returns one of "temporary", "permanent" or "none" for the 114 | // registrar on which this name is registered. 115 | func (r *BaseRegistrar) RegisteredWith(domain string) (string, error) { 116 | name, err := UnqualifiedName(domain, r.domain) 117 | if err != nil { 118 | return "", err 119 | } 120 | // See if we're registered at all - fetch the owner to find out. 121 | registry, err := NewRegistry(r.backend) 122 | if err != nil { 123 | return "", err 124 | } 125 | owner, err := registry.Owner(domain) 126 | if err != nil { 127 | return "", err 128 | } 129 | 130 | // Fetch the temporary registrar and see if we're registered there. 131 | auctionRegistrar, err := r.PriorAuctionContract() 132 | if err != nil { 133 | return "", err 134 | } 135 | if auctionRegistrar != nil { 136 | state, err := auctionRegistrar.State(name) 137 | if err != nil { 138 | return "", err 139 | } 140 | if state == "Won" || state == "Owned" { 141 | return "temporary", nil 142 | } 143 | } 144 | 145 | // No temporary registrar or no entry in same. 146 | if owner == UnknownAddress { 147 | return "none", nil 148 | } 149 | return "permanent", nil 150 | } 151 | 152 | // Owner obtains the owner of the underlying token that represents the name. 153 | func (r *BaseRegistrar) Owner(domain string) (common.Address, error) { 154 | name, err := UnqualifiedName(domain, r.domain) 155 | if err != nil { 156 | return UnknownAddress, err 157 | } 158 | labelHash, err := LabelHash(name) 159 | if err != nil { 160 | return UnknownAddress, err 161 | } 162 | owner, err := r.Contract.OwnerOf(nil, new(big.Int).SetBytes(labelHash[:])) 163 | // Registrar reverts rather than provide a 0 owner, so... 164 | if err != nil && err.Error() == "execution reverted" { 165 | return UnknownAddress, nil 166 | } 167 | return owner, err 168 | } 169 | 170 | // SetOwner sets the owner of the token holding the name. 171 | func (r *BaseRegistrar) SetOwner(opts *bind.TransactOpts, domain string, newOwner common.Address) (*types.Transaction, error) { 172 | name, err := UnqualifiedName(domain, r.domain) 173 | if err != nil { 174 | return nil, err 175 | } 176 | owner, err := r.Owner(name) 177 | if err != nil { 178 | return nil, err 179 | } 180 | labelHash, err := LabelHash(name) 181 | if err != nil { 182 | return nil, err 183 | } 184 | id := new(big.Int).SetBytes(labelHash[:]) 185 | return r.Contract.TransferFrom(opts, owner, newOwner, id) 186 | } 187 | 188 | // Expiry obtains the unix timestamp at which the registration expires. 189 | func (r *BaseRegistrar) Expiry(domain string) (*big.Int, error) { 190 | name, err := UnqualifiedName(domain, r.domain) 191 | if err != nil { 192 | return nil, err 193 | } 194 | labelHash, err := LabelHash(name) 195 | if err != nil { 196 | return nil, err 197 | } 198 | id := new(big.Int).SetBytes(labelHash[:]) 199 | return r.Contract.NameExpires(nil, id) 200 | } 201 | 202 | // Reclaim reclaims a domain by the owner. 203 | func (r *BaseRegistrar) Reclaim(opts *bind.TransactOpts, domain string, newOwner common.Address) (*types.Transaction, error) { 204 | name, err := UnqualifiedName(domain, r.domain) 205 | if err != nil { 206 | return nil, err 207 | } 208 | labelHash, err := LabelHash(name) 209 | if err != nil { 210 | return nil, err 211 | } 212 | id := new(big.Int).SetBytes(labelHash[:]) 213 | return r.Contract.Reclaim(opts, id, newOwner) 214 | } 215 | -------------------------------------------------------------------------------- /contenthash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "encoding/base32" 19 | "encoding/base64" 20 | "encoding/binary" 21 | "encoding/hex" 22 | "fmt" 23 | "strings" 24 | 25 | "github.com/ipfs/go-cid" 26 | "github.com/multiformats/go-multibase" 27 | "github.com/multiformats/go-multihash" 28 | "github.com/pkg/errors" 29 | "github.com/wealdtech/go-multicodec" 30 | ) 31 | 32 | // StringToContenthash turns EIP-1577 text format in to EIP-1577 binary format. 33 | func StringToContenthash(text string) ([]byte, error) { 34 | if text == "" { 35 | return nil, errors.New("no content hash") 36 | } 37 | 38 | var codec string 39 | var data string 40 | if strings.Contains(text, "://") { 41 | // URL style. 42 | bits := strings.Split(text, "://") 43 | if len(bits) != 2 { 44 | return nil, fmt.Errorf("invalid content hash") 45 | } 46 | codec = bits[0] 47 | data = bits[1] 48 | } else { 49 | // Path style. 50 | bits := strings.Split(text, "/") 51 | if len(bits) != 3 { 52 | return nil, errors.New("invalid content hash") 53 | } 54 | codec = bits[1] 55 | data = bits[2] 56 | } 57 | if codec == "" { 58 | return nil, errors.New("codec missing") 59 | } 60 | if data == "" { 61 | return nil, errors.New("data missing") 62 | } 63 | 64 | res := make([]byte, 0) 65 | switch codec { 66 | case "ipfs": 67 | content, err := cid.Parse(data) 68 | if err != nil { 69 | return nil, errors.Wrap(err, "invalid IPFS data") 70 | } 71 | // Namespace. 72 | buf := make([]byte, binary.MaxVarintLen64) 73 | size := binary.PutUvarint(buf, multicodec.MustID("ipfs-ns")) 74 | res = append(res, buf[0:size]...) 75 | if data[0:2] == "Qm" { 76 | // CID v0 needs additional headers. 77 | size = binary.PutUvarint(buf, 1) 78 | res = append(res, buf[0:size]...) 79 | size = binary.PutUvarint(buf, multicodec.MustID("dag-pb")) 80 | res = append(res, buf[0:size]...) 81 | res = append(res, content.Bytes()...) 82 | } else { 83 | res = append(res, content.Bytes()...) 84 | } 85 | case "ipns": 86 | content, err := cid.Parse(data) 87 | if err != nil { 88 | return nil, errors.Wrap(err, "invalid IPNS data") 89 | } 90 | // Namespace. 91 | buf := make([]byte, binary.MaxVarintLen64) 92 | size := binary.PutUvarint(buf, multicodec.MustID("ipns-ns")) 93 | res = append(res, buf[0:size]...) 94 | if data[0:2] == "Qm" { 95 | // CID v0 needs additional headers. 96 | size = binary.PutUvarint(buf, 1) 97 | res = append(res, buf[0:size]...) 98 | size = binary.PutUvarint(buf, multicodec.MustID("dag-pb")) 99 | res = append(res, buf[0:size]...) 100 | res = append(res, content.Bytes()...) 101 | } else { 102 | res = append(res, content.Bytes()...) 103 | } 104 | case "swarm", "bzz": 105 | // Namespace. 106 | buf := make([]byte, binary.MaxVarintLen64) 107 | size := binary.PutUvarint(buf, multicodec.MustID("swarm-ns")) 108 | res = append(res, buf[0:size]...) 109 | size = binary.PutUvarint(buf, 1) 110 | res = append(res, buf[0:size]...) 111 | size = binary.PutUvarint(buf, multicodec.MustID("swarm-manifest")) 112 | res = append(res, buf[0:size]...) 113 | // Hash. 114 | hashData, err := hex.DecodeString(data) 115 | if err != nil { 116 | return nil, errors.Wrap(err, "invalid hex") 117 | } 118 | hash, err := multihash.Encode(hashData, multihash.KECCAK_256) 119 | if err != nil { 120 | return nil, errors.Wrap(err, "failed to hash") 121 | } 122 | res = append(res, hash...) 123 | case "onion": 124 | // Codec. 125 | buf := make([]byte, binary.MaxVarintLen64) 126 | size := binary.PutUvarint(buf, multicodec.MustID("onion")) 127 | res = append(res, buf[0:size]...) 128 | 129 | // Address. 130 | if len(data) != 16 { 131 | return nil, errors.New("onion address should be 16 characters") 132 | } 133 | res = append(res, []byte(data)...) 134 | case "onion3": 135 | // Codec. 136 | buf := make([]byte, binary.MaxVarintLen64) 137 | size := binary.PutUvarint(buf, multicodec.MustID("onion3")) 138 | res = append(res, buf[0:size]...) 139 | 140 | // Address. 141 | if len(data) != 56 { 142 | return nil, errors.New("onion address should be 56 characters") 143 | } 144 | res = append(res, []byte(data)...) 145 | case "sia": 146 | // Codec. 147 | buf := make([]byte, binary.MaxVarintLen64) 148 | size := binary.PutUvarint(buf, multicodec.MustID("skynet-ns")) 149 | res = append(res, buf[0:size]...) 150 | 151 | // Skylink. 152 | var err error 153 | var decoded []byte 154 | switch len(data) { 155 | case 46: 156 | decoded, err = base64.RawURLEncoding.DecodeString(data) 157 | if err != nil { 158 | return nil, errors.New("skylink not correctly encoded") 159 | } 160 | case 55: 161 | decoded, err = base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(data)) 162 | if err != nil { 163 | return nil, errors.New("skylink not correctly encoded") 164 | } 165 | default: 166 | return nil, errors.New("skylinks should be either 46 or 55 characters, depending on whether it is base64 or base32 encoded") 167 | } 168 | res = append(res, decoded...) 169 | default: 170 | return nil, fmt.Errorf("unknown codec %s", codec) 171 | } 172 | 173 | return res, nil 174 | } 175 | 176 | // ContenthashToString turns EIP-1577 binary format in to EIP-1577 text format. 177 | func ContenthashToString(bytes []byte) (string, error) { 178 | data, codec, err := multicodec.RemoveCodec(bytes) 179 | if err != nil { 180 | return "", err 181 | } 182 | codecName, err := multicodec.Name(codec) 183 | if err != nil { 184 | return "", err 185 | } 186 | 187 | switch codecName { 188 | case "ipfs-ns": 189 | thisCID, err := cid.Parse(data) 190 | if err != nil { 191 | return "", errors.Wrap(err, "failed to parse CID") 192 | } 193 | str, err := thisCID.StringOfBase(multibase.Base36) 194 | if err != nil { 195 | return "", errors.Wrap(err, "failed to obtain base36 representation") 196 | } 197 | return fmt.Sprintf("/ipfs/%s", str), nil 198 | case "ipns-ns": 199 | thisCID, err := cid.Parse(data) 200 | if err != nil { 201 | return "", errors.Wrap(err, "failed to parse CID") 202 | } 203 | res, err := multibase.Encode(multibase.Base36, thisCID.Bytes()) 204 | if err != nil { 205 | return "", errors.Wrap(err, "unknown multibase") 206 | } 207 | return fmt.Sprintf("/ipns/%s", res), nil 208 | case "swarm-ns": 209 | id, offset := binary.Uvarint(data) 210 | if id == 0 { 211 | return "", fmt.Errorf("unknown CID") 212 | } 213 | data, subCodec, err := multicodec.RemoveCodec(data[offset:]) 214 | if err != nil { 215 | return "", err 216 | } 217 | _, err = multicodec.Name(subCodec) 218 | if err != nil { 219 | return "", err 220 | } 221 | decodedMHash, err := multihash.Decode(data) 222 | if err != nil { 223 | return "", err 224 | } 225 | return fmt.Sprintf("bzz://%x", decodedMHash.Digest), nil 226 | case "onion": 227 | return fmt.Sprintf("onion://%s", string(data)), nil 228 | case "onion3": 229 | return fmt.Sprintf("onion3://%s", string(data)), nil 230 | case "skynet-ns": 231 | skylink := base64.RawURLEncoding.EncodeToString(data) 232 | return fmt.Sprintf("sia://%s", skylink), nil 233 | default: 234 | return "", fmt.Errorf("unknown codec name %s", codecName) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /contenthash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-201 Weald Technology Trading 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "encoding/hex" 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func _hexStr(input string) []byte { 27 | res, err := hex.DecodeString(input) 28 | if err != nil { 29 | panic(err) 30 | } 31 | return res 32 | } 33 | 34 | func TestContenthash(t *testing.T) { 35 | tests := []struct { 36 | name string 37 | repr string 38 | bin []byte 39 | res string 40 | err error 41 | }{ 42 | { 43 | name: "Nil", 44 | repr: "", 45 | err: errors.New("no content hash"), 46 | }, 47 | { 48 | name: "CodecMissing", 49 | repr: "://QmayQq2DWCkY3d4x3xKh4suohuRPEXe2fBqMBam5xtDj3t", 50 | err: errors.New("codec missing"), 51 | }, 52 | { 53 | name: "CodecMissing2", 54 | repr: "//QmayQq2DWCkY3d4x3xKh4suohuRPEXe2fBqMBam5xtDj3t", 55 | err: errors.New("codec missing"), 56 | }, 57 | { 58 | name: "Bad", 59 | repr: "/bad/QmayQq2DWCkY3d4x3xKh4suohuRPEXe2fBqMBam5xtDj3t", 60 | err: errors.New("unknown codec bad"), 61 | }, 62 | { 63 | name: "MissingData", 64 | repr: "/ipfs/", 65 | err: errors.New("data missing"), 66 | }, 67 | { 68 | name: "BadHash", 69 | repr: "/ipfs/invalid", 70 | err: errors.New("invalid IPFS data: invalid cid: selected encoding not supported"), 71 | }, 72 | { 73 | name: "IPFSCIDv0", 74 | repr: "/ipfs/QmRAQB6YaCyidP37UdDnjFY5vQuiBrcqdyoW1CuDgwxkD4", 75 | bin: _hexStr("e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"), 76 | res: "/ipfs/k2jmtxseqz46solsx2rmxavgbzp6ij1t1kiq1or8a00c2g9bx1for0gv", 77 | }, 78 | { 79 | name: "IPFSCIDv1", 80 | repr: "/ipfs/bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4", 81 | bin: _hexStr("e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"), 82 | res: "/ipfs/k2jmtxseqz46solsx2rmxavgbzp6ij1t1kiq1or8a00c2g9bx1for0gv", 83 | }, 84 | { 85 | name: "Onion", 86 | repr: "/onion/zqktlwi4fecvo6ri", 87 | bin: _hexStr("bc037a716b746c776934666563766f367269"), 88 | res: "onion://zqktlwi4fecvo6ri", 89 | }, 90 | { 91 | name: "Onion3", 92 | repr: "/onion3/p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd", 93 | bin: _hexStr("bd037035336c663537716f7679757677736336786e72707079706c79337674716d376c3670636f626b6d797173696f6679657a6e667535757164"), 94 | res: "onion3://p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd", 95 | }, 96 | { 97 | name: "IPFS", 98 | repr: "ipfs://QmRAQB6YaCyidP37UdDnjFY5vQuiBrcqdyoW1CuDgwxkD4", 99 | bin: _hexStr("e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"), 100 | res: "/ipfs/k2jmtxseqz46solsx2rmxavgbzp6ij1t1kiq1or8a00c2g9bx1for0gv", 101 | }, 102 | { 103 | name: "IPNSIdentity", 104 | repr: "/ipns/k51qzi5uqu5djwbl0zcd4g9onue26a8nq97c0m9wp6kir1gibuyjxpkqpoxwag", 105 | bin: _hexStr("e5010172002408011220950b8f62b925ecc50247cc8de1084b43f854fbc452894d9a1a97d7f27d0addb8"), 106 | res: "/ipns/k51qzi5uqu5djwbl0zcd4g9onue26a8nq97c0m9wp6kir1gibuyjxpkqpoxwag", 107 | }, 108 | { 109 | name: "IPNSEIP1577", 110 | repr: "ipns://k51qzi5uqu5djwbl0zcd4g9onue26a8nq97c0m9wp6kir1gibuyjxpkqpoxwag", 111 | bin: _hexStr("e5010172002408011220950b8f62b925ecc50247cc8de1084b43f854fbc452894d9a1a97d7f27d0addb8"), 112 | res: "/ipns/k51qzi5uqu5djwbl0zcd4g9onue26a8nq97c0m9wp6kir1gibuyjxpkqpoxwag", 113 | }, 114 | { 115 | name: "SwarmBad", 116 | repr: "/swarm/invalid", 117 | err: errors.New("invalid hex: encoding/hex: invalid byte: U+0069 'i'"), 118 | }, 119 | { 120 | name: "Swarm", 121 | repr: "/swarm/d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162", 122 | bin: _hexStr("e40101fa011b20d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162"), 123 | res: "bzz://d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162", 124 | }, 125 | { 126 | name: "bzz", 127 | repr: "bzz://d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162", 128 | bin: []byte{0xe4, 0x01, 0x01, 0xfa, 0x01, 0x1b, 0x20, 0xd1, 0xde, 0x99, 0x94, 0xb4, 0xd0, 0x39, 0xf6, 0x54, 0x8d, 0x19, 0x1e, 0xb2, 0x67, 0x86, 0x76, 0x9f, 0x58, 0x08, 0x09, 0x25, 0x6b, 0x46, 0x85, 0xef, 0x31, 0x68, 0x05, 0x26, 0x5e, 0xa1, 0x62}, 129 | res: "bzz://d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162", 130 | }, 131 | { 132 | name: "Skynet", 133 | repr: "sia://CABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTcg", 134 | bin: _hexStr("90b2c60508004007fd43b74149b31aacbbf2784e874d09b086bed15fd54cacff7120cce95372"), 135 | res: "sia://CABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTcg", 136 | }, 137 | { 138 | name: "SkynetBase32", 139 | repr: "sia://100401vt8erk2idj3ambnsjo9q3kq2dggqvd2nul9imfus90pjkl6sg", 140 | bin: _hexStr("90b2c60508004007fd43b74149b31aacbbf2784e874d09b086bed15fd54cacff7120cce95372"), 141 | res: "sia://CABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTcg", 142 | }, 143 | { 144 | name: "SkynetInvalidSize", 145 | repr: "sia://AAAACABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTcg", 146 | err: errors.New("skylinks should be either 46 or 55 characters, depending on whether it is base64 or base32 encoded"), 147 | }, 148 | { 149 | name: "SkynetInvalidEncoding", 150 | repr: "sia://%CABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTc", 151 | err: errors.New("skylink not correctly encoded"), 152 | }, 153 | } 154 | 155 | for _, test := range tests { 156 | t.Run(test.name, func(t *testing.T) { 157 | bin, err := StringToContenthash(test.repr) 158 | if test.err != nil { 159 | require.NotNil(t, err, "missing error") 160 | assert.Equal(t, test.err.Error(), err.Error(), "incorrect error") 161 | } else { 162 | require.Nil(t, err, "unexpected error creating content hash") 163 | require.Equal(t, test.bin, bin, "incorrect coding") 164 | res, err := ContenthashToString(bin) 165 | require.Nil(t, err, "unexpected error decoding content hash") 166 | assert.Equal(t, test.res, res, "incorrect decoding") 167 | } 168 | }) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /contracts/auctionregistrar/contract.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"releaseDeed","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"getAllowedTime","outputs":[{"name":"timestamp","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"unhashedName","type":"string"}],"name":"invalidateName","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"hash","type":"bytes32"},{"name":"owner","type":"address"},{"name":"value","type":"uint256"},{"name":"salt","type":"bytes32"}],"name":"shaBid","outputs":[{"name":"sealedBid","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"bidder","type":"address"},{"name":"seal","type":"bytes32"}],"name":"cancelBid","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"entries","outputs":[{"name":"","type":"uint8"},{"name":"","type":"address"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"ens","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"},{"name":"_value","type":"uint256"},{"name":"_salt","type":"bytes32"}],"name":"unsealBid","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"transferRegistrars","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"bytes32"}],"name":"sealedBids","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"state","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"},{"name":"newOwner","type":"address"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"},{"name":"_timestamp","type":"uint256"}],"name":"isAllowed","outputs":[{"name":"allowed","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"finalizeAuction","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"registryStarted","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"launchLength","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"sealedBid","type":"bytes32"}],"name":"newBid","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"labels","type":"bytes32[]"}],"name":"eraseNode","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hashes","type":"bytes32[]"}],"name":"startAuctions","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"hash","type":"bytes32"},{"name":"deed","type":"address"},{"name":"registrationDate","type":"uint256"}],"name":"acceptRegistrarTransfer","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"startAuction","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"rootNode","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"hashes","type":"bytes32[]"},{"name":"sealedBid","type":"bytes32"}],"name":"startAuctionsAndBid","outputs":[],"payable":true,"type":"function"},{"inputs":[{"name":"_ens","type":"address"},{"name":"_rootNode","type":"bytes32"},{"name":"_startDate","type":"uint256"}],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":false,"name":"registrationDate","type":"uint256"}],"name":"AuctionStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"bidder","type":"address"},{"indexed":false,"name":"deposit","type":"uint256"}],"name":"NewBid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"status","type":"uint8"}],"name":"BidRevealed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"registrationDate","type":"uint256"}],"name":"HashRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"}],"name":"HashReleased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"registrationDate","type":"uint256"}],"name":"HashInvalidated","type":"event"}] 2 | -------------------------------------------------------------------------------- /contracts/auctionregistrar/generate.go: -------------------------------------------------------------------------------- 1 | package auctionregistrar 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg auctionregistrar -type Contract 4 | -------------------------------------------------------------------------------- /contracts/baseregistrar/contract.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "interfaceID", 7 | "type": "bytes4" 8 | } 9 | ], 10 | "name": "supportsInterface", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "bool" 15 | } 16 | ], 17 | "payable": false, 18 | "stateMutability": "view", 19 | "type": "function" 20 | }, 21 | { 22 | "constant": true, 23 | "inputs": [ 24 | { 25 | "name": "tokenId", 26 | "type": "uint256" 27 | } 28 | ], 29 | "name": "getApproved", 30 | "outputs": [ 31 | { 32 | "name": "", 33 | "type": "address" 34 | } 35 | ], 36 | "payable": false, 37 | "stateMutability": "view", 38 | "type": "function" 39 | }, 40 | { 41 | "constant": false, 42 | "inputs": [ 43 | { 44 | "name": "to", 45 | "type": "address" 46 | }, 47 | { 48 | "name": "tokenId", 49 | "type": "uint256" 50 | } 51 | ], 52 | "name": "approve", 53 | "outputs": [], 54 | "payable": false, 55 | "stateMutability": "nonpayable", 56 | "type": "function" 57 | }, 58 | { 59 | "constant": false, 60 | "inputs": [ 61 | { 62 | "name": "from", 63 | "type": "address" 64 | }, 65 | { 66 | "name": "to", 67 | "type": "address" 68 | }, 69 | { 70 | "name": "tokenId", 71 | "type": "uint256" 72 | } 73 | ], 74 | "name": "transferFrom", 75 | "outputs": [], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": false, 82 | "inputs": [ 83 | { 84 | "name": "id", 85 | "type": "uint256" 86 | }, 87 | { 88 | "name": "owner", 89 | "type": "address" 90 | } 91 | ], 92 | "name": "reclaim", 93 | "outputs": [], 94 | "payable": false, 95 | "stateMutability": "nonpayable", 96 | "type": "function" 97 | }, 98 | { 99 | "constant": true, 100 | "inputs": [], 101 | "name": "ens", 102 | "outputs": [ 103 | { 104 | "name": "", 105 | "type": "address" 106 | } 107 | ], 108 | "payable": false, 109 | "stateMutability": "view", 110 | "type": "function" 111 | }, 112 | { 113 | "constant": false, 114 | "inputs": [ 115 | { 116 | "name": "from", 117 | "type": "address" 118 | }, 119 | { 120 | "name": "to", 121 | "type": "address" 122 | }, 123 | { 124 | "name": "tokenId", 125 | "type": "uint256" 126 | } 127 | ], 128 | "name": "safeTransferFrom", 129 | "outputs": [], 130 | "payable": false, 131 | "stateMutability": "nonpayable", 132 | "type": "function" 133 | }, 134 | { 135 | "constant": true, 136 | "inputs": [], 137 | "name": "transferPeriodEnds", 138 | "outputs": [ 139 | { 140 | "name": "", 141 | "type": "uint256" 142 | } 143 | ], 144 | "payable": false, 145 | "stateMutability": "view", 146 | "type": "function" 147 | }, 148 | { 149 | "constant": false, 150 | "inputs": [ 151 | { 152 | "name": "resolver", 153 | "type": "address" 154 | } 155 | ], 156 | "name": "setResolver", 157 | "outputs": [], 158 | "payable": false, 159 | "stateMutability": "nonpayable", 160 | "type": "function" 161 | }, 162 | { 163 | "constant": true, 164 | "inputs": [ 165 | { 166 | "name": "tokenId", 167 | "type": "uint256" 168 | } 169 | ], 170 | "name": "ownerOf", 171 | "outputs": [ 172 | { 173 | "name": "", 174 | "type": "address" 175 | } 176 | ], 177 | "payable": false, 178 | "stateMutability": "view", 179 | "type": "function" 180 | }, 181 | { 182 | "constant": true, 183 | "inputs": [], 184 | "name": "MIGRATION_LOCK_PERIOD", 185 | "outputs": [ 186 | { 187 | "name": "", 188 | "type": "uint256" 189 | } 190 | ], 191 | "payable": false, 192 | "stateMutability": "view", 193 | "type": "function" 194 | }, 195 | { 196 | "constant": true, 197 | "inputs": [ 198 | { 199 | "name": "owner", 200 | "type": "address" 201 | } 202 | ], 203 | "name": "balanceOf", 204 | "outputs": [ 205 | { 206 | "name": "", 207 | "type": "uint256" 208 | } 209 | ], 210 | "payable": false, 211 | "stateMutability": "view", 212 | "type": "function" 213 | }, 214 | { 215 | "constant": false, 216 | "inputs": [], 217 | "name": "renounceOwnership", 218 | "outputs": [], 219 | "payable": false, 220 | "stateMutability": "nonpayable", 221 | "type": "function" 222 | }, 223 | { 224 | "constant": true, 225 | "inputs": [], 226 | "name": "owner", 227 | "outputs": [ 228 | { 229 | "name": "", 230 | "type": "address" 231 | } 232 | ], 233 | "payable": false, 234 | "stateMutability": "view", 235 | "type": "function" 236 | }, 237 | { 238 | "constant": true, 239 | "inputs": [], 240 | "name": "isOwner", 241 | "outputs": [ 242 | { 243 | "name": "", 244 | "type": "bool" 245 | } 246 | ], 247 | "payable": false, 248 | "stateMutability": "view", 249 | "type": "function" 250 | }, 251 | { 252 | "constant": true, 253 | "inputs": [ 254 | { 255 | "name": "id", 256 | "type": "uint256" 257 | } 258 | ], 259 | "name": "available", 260 | "outputs": [ 261 | { 262 | "name": "", 263 | "type": "bool" 264 | } 265 | ], 266 | "payable": false, 267 | "stateMutability": "view", 268 | "type": "function" 269 | }, 270 | { 271 | "constant": false, 272 | "inputs": [ 273 | { 274 | "name": "to", 275 | "type": "address" 276 | }, 277 | { 278 | "name": "approved", 279 | "type": "bool" 280 | } 281 | ], 282 | "name": "setApprovalForAll", 283 | "outputs": [], 284 | "payable": false, 285 | "stateMutability": "nonpayable", 286 | "type": "function" 287 | }, 288 | { 289 | "constant": false, 290 | "inputs": [ 291 | { 292 | "name": "controller", 293 | "type": "address" 294 | } 295 | ], 296 | "name": "addController", 297 | "outputs": [], 298 | "payable": false, 299 | "stateMutability": "nonpayable", 300 | "type": "function" 301 | }, 302 | { 303 | "constant": true, 304 | "inputs": [], 305 | "name": "previousRegistrar", 306 | "outputs": [ 307 | { 308 | "name": "", 309 | "type": "address" 310 | } 311 | ], 312 | "payable": false, 313 | "stateMutability": "view", 314 | "type": "function" 315 | }, 316 | { 317 | "constant": false, 318 | "inputs": [ 319 | { 320 | "name": "from", 321 | "type": "address" 322 | }, 323 | { 324 | "name": "to", 325 | "type": "address" 326 | }, 327 | { 328 | "name": "tokenId", 329 | "type": "uint256" 330 | }, 331 | { 332 | "name": "_data", 333 | "type": "bytes" 334 | } 335 | ], 336 | "name": "safeTransferFrom", 337 | "outputs": [], 338 | "payable": false, 339 | "stateMutability": "nonpayable", 340 | "type": "function" 341 | }, 342 | { 343 | "constant": true, 344 | "inputs": [], 345 | "name": "GRACE_PERIOD", 346 | "outputs": [ 347 | { 348 | "name": "", 349 | "type": "uint256" 350 | } 351 | ], 352 | "payable": false, 353 | "stateMutability": "view", 354 | "type": "function" 355 | }, 356 | { 357 | "constant": false, 358 | "inputs": [ 359 | { 360 | "name": "id", 361 | "type": "uint256" 362 | }, 363 | { 364 | "name": "duration", 365 | "type": "uint256" 366 | } 367 | ], 368 | "name": "renew", 369 | "outputs": [ 370 | { 371 | "name": "", 372 | "type": "uint256" 373 | } 374 | ], 375 | "payable": false, 376 | "stateMutability": "nonpayable", 377 | "type": "function" 378 | }, 379 | { 380 | "constant": true, 381 | "inputs": [ 382 | { 383 | "name": "id", 384 | "type": "uint256" 385 | } 386 | ], 387 | "name": "nameExpires", 388 | "outputs": [ 389 | { 390 | "name": "", 391 | "type": "uint256" 392 | } 393 | ], 394 | "payable": false, 395 | "stateMutability": "view", 396 | "type": "function" 397 | }, 398 | { 399 | "constant": true, 400 | "inputs": [ 401 | { 402 | "name": "", 403 | "type": "address" 404 | } 405 | ], 406 | "name": "controllers", 407 | "outputs": [ 408 | { 409 | "name": "", 410 | "type": "bool" 411 | } 412 | ], 413 | "payable": false, 414 | "stateMutability": "view", 415 | "type": "function" 416 | }, 417 | { 418 | "constant": true, 419 | "inputs": [], 420 | "name": "baseNode", 421 | "outputs": [ 422 | { 423 | "name": "", 424 | "type": "bytes32" 425 | } 426 | ], 427 | "payable": false, 428 | "stateMutability": "view", 429 | "type": "function" 430 | }, 431 | { 432 | "constant": true, 433 | "inputs": [ 434 | { 435 | "name": "owner", 436 | "type": "address" 437 | }, 438 | { 439 | "name": "operator", 440 | "type": "address" 441 | } 442 | ], 443 | "name": "isApprovedForAll", 444 | "outputs": [ 445 | { 446 | "name": "", 447 | "type": "bool" 448 | } 449 | ], 450 | "payable": false, 451 | "stateMutability": "view", 452 | "type": "function" 453 | }, 454 | { 455 | "constant": false, 456 | "inputs": [ 457 | { 458 | "name": "label", 459 | "type": "bytes32" 460 | }, 461 | { 462 | "name": "deed", 463 | "type": "address" 464 | }, 465 | { 466 | "name": "", 467 | "type": "uint256" 468 | } 469 | ], 470 | "name": "acceptRegistrarTransfer", 471 | "outputs": [], 472 | "payable": false, 473 | "stateMutability": "nonpayable", 474 | "type": "function" 475 | }, 476 | { 477 | "constant": false, 478 | "inputs": [ 479 | { 480 | "name": "newOwner", 481 | "type": "address" 482 | } 483 | ], 484 | "name": "transferOwnership", 485 | "outputs": [], 486 | "payable": false, 487 | "stateMutability": "nonpayable", 488 | "type": "function" 489 | }, 490 | { 491 | "constant": false, 492 | "inputs": [ 493 | { 494 | "name": "controller", 495 | "type": "address" 496 | } 497 | ], 498 | "name": "removeController", 499 | "outputs": [], 500 | "payable": false, 501 | "stateMutability": "nonpayable", 502 | "type": "function" 503 | }, 504 | { 505 | "constant": false, 506 | "inputs": [ 507 | { 508 | "name": "id", 509 | "type": "uint256" 510 | }, 511 | { 512 | "name": "owner", 513 | "type": "address" 514 | }, 515 | { 516 | "name": "duration", 517 | "type": "uint256" 518 | } 519 | ], 520 | "name": "register", 521 | "outputs": [ 522 | { 523 | "name": "", 524 | "type": "uint256" 525 | } 526 | ], 527 | "payable": false, 528 | "stateMutability": "nonpayable", 529 | "type": "function" 530 | }, 531 | { 532 | "inputs": [ 533 | { 534 | "name": "_ens", 535 | "type": "address" 536 | }, 537 | { 538 | "name": "_previousRegistrar", 539 | "type": "address" 540 | }, 541 | { 542 | "name": "_baseNode", 543 | "type": "bytes32" 544 | }, 545 | { 546 | "name": "_transferPeriodEnds", 547 | "type": "uint256" 548 | } 549 | ], 550 | "payable": false, 551 | "stateMutability": "nonpayable", 552 | "type": "constructor" 553 | }, 554 | { 555 | "anonymous": false, 556 | "inputs": [ 557 | { 558 | "indexed": true, 559 | "name": "controller", 560 | "type": "address" 561 | } 562 | ], 563 | "name": "ControllerAdded", 564 | "type": "event" 565 | }, 566 | { 567 | "anonymous": false, 568 | "inputs": [ 569 | { 570 | "indexed": true, 571 | "name": "controller", 572 | "type": "address" 573 | } 574 | ], 575 | "name": "ControllerRemoved", 576 | "type": "event" 577 | }, 578 | { 579 | "anonymous": false, 580 | "inputs": [ 581 | { 582 | "indexed": true, 583 | "name": "id", 584 | "type": "uint256" 585 | }, 586 | { 587 | "indexed": true, 588 | "name": "owner", 589 | "type": "address" 590 | }, 591 | { 592 | "indexed": false, 593 | "name": "expires", 594 | "type": "uint256" 595 | } 596 | ], 597 | "name": "NameMigrated", 598 | "type": "event" 599 | }, 600 | { 601 | "anonymous": false, 602 | "inputs": [ 603 | { 604 | "indexed": true, 605 | "name": "id", 606 | "type": "uint256" 607 | }, 608 | { 609 | "indexed": true, 610 | "name": "owner", 611 | "type": "address" 612 | }, 613 | { 614 | "indexed": false, 615 | "name": "expires", 616 | "type": "uint256" 617 | } 618 | ], 619 | "name": "NameRegistered", 620 | "type": "event" 621 | }, 622 | { 623 | "anonymous": false, 624 | "inputs": [ 625 | { 626 | "indexed": true, 627 | "name": "id", 628 | "type": "uint256" 629 | }, 630 | { 631 | "indexed": false, 632 | "name": "expires", 633 | "type": "uint256" 634 | } 635 | ], 636 | "name": "NameRenewed", 637 | "type": "event" 638 | }, 639 | { 640 | "anonymous": false, 641 | "inputs": [ 642 | { 643 | "indexed": true, 644 | "name": "previousOwner", 645 | "type": "address" 646 | }, 647 | { 648 | "indexed": true, 649 | "name": "newOwner", 650 | "type": "address" 651 | } 652 | ], 653 | "name": "OwnershipTransferred", 654 | "type": "event" 655 | }, 656 | { 657 | "anonymous": false, 658 | "inputs": [ 659 | { 660 | "indexed": true, 661 | "name": "from", 662 | "type": "address" 663 | }, 664 | { 665 | "indexed": true, 666 | "name": "to", 667 | "type": "address" 668 | }, 669 | { 670 | "indexed": true, 671 | "name": "tokenId", 672 | "type": "uint256" 673 | } 674 | ], 675 | "name": "Transfer", 676 | "type": "event" 677 | }, 678 | { 679 | "anonymous": false, 680 | "inputs": [ 681 | { 682 | "indexed": true, 683 | "name": "owner", 684 | "type": "address" 685 | }, 686 | { 687 | "indexed": true, 688 | "name": "approved", 689 | "type": "address" 690 | }, 691 | { 692 | "indexed": true, 693 | "name": "tokenId", 694 | "type": "uint256" 695 | } 696 | ], 697 | "name": "Approval", 698 | "type": "event" 699 | }, 700 | { 701 | "anonymous": false, 702 | "inputs": [ 703 | { 704 | "indexed": true, 705 | "name": "owner", 706 | "type": "address" 707 | }, 708 | { 709 | "indexed": true, 710 | "name": "operator", 711 | "type": "address" 712 | }, 713 | { 714 | "indexed": false, 715 | "name": "approved", 716 | "type": "bool" 717 | } 718 | ], 719 | "name": "ApprovalForAll", 720 | "type": "event" 721 | } 722 | ] 723 | -------------------------------------------------------------------------------- /contracts/baseregistrar/generate.go: -------------------------------------------------------------------------------- 1 | package baseregistrar 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg baseregistrar -type Contract 4 | -------------------------------------------------------------------------------- /contracts/deed/contract.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"creationDate","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"destroyDeed","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"registrar","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"value","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"previousOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newValue","type":"uint256"},{"name":"throwOnFailure","type":"bool"}],"name":"setBalance","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"refundRatio","type":"uint256"}],"name":"closeDeed","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newRegistrar","type":"address"}],"name":"setRegistrar","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"_owner","type":"address"}],"payable":true,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"DeedClosed","type":"event"}] 2 | -------------------------------------------------------------------------------- /contracts/deed/generate.go: -------------------------------------------------------------------------------- 1 | package deed 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg deed -type Contract 4 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/contract.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "interfaceID", 7 | "type": "bytes4" 8 | } 9 | ], 10 | "name": "supportsInterface", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "bool" 15 | } 16 | ], 17 | "payable": false, 18 | "stateMutability": "pure", 19 | "type": "function" 20 | }, 21 | { 22 | "constant": true, 23 | "inputs": [], 24 | "name": "ens", 25 | "outputs": [ 26 | { 27 | "name": "", 28 | "type": "address" 29 | } 30 | ], 31 | "payable": false, 32 | "stateMutability": "view", 33 | "type": "function" 34 | }, 35 | { 36 | "constant": true, 37 | "inputs": [], 38 | "name": "oracle", 39 | "outputs": [ 40 | { 41 | "name": "", 42 | "type": "address" 43 | } 44 | ], 45 | "payable": false, 46 | "stateMutability": "view", 47 | "type": "function" 48 | }, 49 | { 50 | "constant": false, 51 | "inputs": [ 52 | { 53 | "name": "name", 54 | "type": "bytes" 55 | }, 56 | { 57 | "name": "proof", 58 | "type": "bytes" 59 | } 60 | ], 61 | "name": "claim", 62 | "outputs": [], 63 | "payable": false, 64 | "stateMutability": "nonpayable", 65 | "type": "function" 66 | }, 67 | { 68 | "constant": false, 69 | "inputs": [ 70 | { 71 | "name": "name", 72 | "type": "bytes" 73 | }, 74 | { 75 | "name": "input", 76 | "type": "bytes" 77 | }, 78 | { 79 | "name": "proof", 80 | "type": "bytes" 81 | } 82 | ], 83 | "name": "proveAndClaim", 84 | "outputs": [], 85 | "payable": false, 86 | "stateMutability": "nonpayable", 87 | "type": "function" 88 | }, 89 | { 90 | "inputs": [ 91 | { 92 | "name": "_dnssec", 93 | "type": "address" 94 | }, 95 | { 96 | "name": "_ens", 97 | "type": "address" 98 | } 99 | ], 100 | "payable": false, 101 | "stateMutability": "nonpayable", 102 | "type": "constructor" 103 | }, 104 | { 105 | "anonymous": false, 106 | "inputs": [ 107 | { 108 | "indexed": true, 109 | "name": "node", 110 | "type": "bytes32" 111 | }, 112 | { 113 | "indexed": true, 114 | "name": "owner", 115 | "type": "address" 116 | }, 117 | { 118 | "indexed": false, 119 | "name": "dnsname", 120 | "type": "bytes" 121 | } 122 | ], 123 | "name": "Claim", 124 | "type": "event" 125 | } 126 | ] 127 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/generate.go: -------------------------------------------------------------------------------- 1 | package dnsregistrar 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg dnsregistrar -type Contract 4 | -------------------------------------------------------------------------------- /contracts/dnsresolver/generate.go: -------------------------------------------------------------------------------- 1 | package dnsresolver 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg dnsresolver -type Contract 4 | -------------------------------------------------------------------------------- /contracts/dnssecoracle/contract.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "dnstype", 7 | "type": "uint16" 8 | }, 9 | { 10 | "name": "name", 11 | "type": "bytes" 12 | } 13 | ], 14 | "name": "rrdata", 15 | "outputs": [ 16 | { 17 | "name": "", 18 | "type": "uint32" 19 | }, 20 | { 21 | "name": "", 22 | "type": "uint64" 23 | }, 24 | { 25 | "name": "", 26 | "type": "bytes20" 27 | } 28 | ], 29 | "payable": false, 30 | "stateMutability": "view", 31 | "type": "function" 32 | }, 33 | { 34 | "constant": false, 35 | "inputs": [ 36 | { 37 | "name": "input", 38 | "type": "bytes" 39 | }, 40 | { 41 | "name": "sig", 42 | "type": "bytes" 43 | }, 44 | { 45 | "name": "proof", 46 | "type": "bytes" 47 | } 48 | ], 49 | "name": "submitRRSet", 50 | "outputs": [ 51 | { 52 | "name": "", 53 | "type": "bytes" 54 | } 55 | ], 56 | "payable": false, 57 | "stateMutability": "nonpayable", 58 | "type": "function" 59 | }, 60 | { 61 | "constant": false, 62 | "inputs": [ 63 | { 64 | "name": "data", 65 | "type": "bytes" 66 | }, 67 | { 68 | "name": "proof", 69 | "type": "bytes" 70 | } 71 | ], 72 | "name": "submitRRSets", 73 | "outputs": [ 74 | { 75 | "name": "", 76 | "type": "bytes" 77 | } 78 | ], 79 | "payable": false, 80 | "stateMutability": "nonpayable", 81 | "type": "function" 82 | }, 83 | { 84 | "constant": false, 85 | "inputs": [ 86 | { 87 | "name": "deleteType", 88 | "type": "uint16" 89 | }, 90 | { 91 | "name": "deleteName", 92 | "type": "bytes" 93 | }, 94 | { 95 | "name": "nsec", 96 | "type": "bytes" 97 | }, 98 | { 99 | "name": "sig", 100 | "type": "bytes" 101 | }, 102 | { 103 | "name": "proof", 104 | "type": "bytes" 105 | } 106 | ], 107 | "name": "deleteRRSet", 108 | "outputs": [], 109 | "payable": false, 110 | "stateMutability": "nonpayable", 111 | "type": "function" 112 | }, 113 | { 114 | "anonymous": false, 115 | "inputs": [ 116 | { 117 | "indexed": false, 118 | "name": "id", 119 | "type": "uint8" 120 | }, 121 | { 122 | "indexed": false, 123 | "name": "addr", 124 | "type": "address" 125 | } 126 | ], 127 | "name": "AlgorithmUpdated", 128 | "type": "event" 129 | }, 130 | { 131 | "anonymous": false, 132 | "inputs": [ 133 | { 134 | "indexed": false, 135 | "name": "id", 136 | "type": "uint8" 137 | }, 138 | { 139 | "indexed": false, 140 | "name": "addr", 141 | "type": "address" 142 | } 143 | ], 144 | "name": "DigestUpdated", 145 | "type": "event" 146 | }, 147 | { 148 | "anonymous": false, 149 | "inputs": [ 150 | { 151 | "indexed": false, 152 | "name": "id", 153 | "type": "uint8" 154 | }, 155 | { 156 | "indexed": false, 157 | "name": "addr", 158 | "type": "address" 159 | } 160 | ], 161 | "name": "NSEC3DigestUpdated", 162 | "type": "event" 163 | }, 164 | { 165 | "anonymous": false, 166 | "inputs": [ 167 | { 168 | "indexed": false, 169 | "name": "name", 170 | "type": "bytes" 171 | }, 172 | { 173 | "indexed": false, 174 | "name": "rrset", 175 | "type": "bytes" 176 | } 177 | ], 178 | "name": "RRSetUpdated", 179 | "type": "event" 180 | } 181 | ] 182 | -------------------------------------------------------------------------------- /contracts/dnssecoracle/generate.go: -------------------------------------------------------------------------------- 1 | package dnssecoracle 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg dnssecoracle -type Contract 4 | -------------------------------------------------------------------------------- /contracts/ethcontroller/contract.abi: -------------------------------------------------------------------------------- 1 | 2 | [{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_prices","type":"address"}],"name":"setPriceOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_minCommitmentAge","type":"uint256"},{"name":"_maxCommitmentAge","type":"uint256"}],"name":"setCommitmentAges","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"commitments","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"name","type":"string"},{"name":"duration","type":"uint256"}],"name":"rentPrice","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"name","type":"string"},{"name":"owner","type":"address"},{"name":"duration","type":"uint256"},{"name":"secret","type":"bytes32"}],"name":"register","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"MIN_REGISTRATION_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"minCommitmentAge","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"name","type":"string"}],"name":"valid","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"name","type":"string"},{"name":"duration","type":"uint256"}],"name":"renew","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"name","type":"string"}],"name":"available","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxCommitmentAge","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"commitment","type":"bytes32"}],"name":"commit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"name","type":"string"},{"name":"owner","type":"address"},{"name":"secret","type":"bytes32"}],"name":"makeCommitment","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"},{"inputs":[{"name":"_base","type":"address"},{"name":"_prices","type":"address"},{"name":"_minCommitmentAge","type":"uint256"},{"name":"_maxCommitmentAge","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"name","type":"string"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"cost","type":"uint256"},{"indexed":false,"name":"expires","type":"uint256"}],"name":"NameRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"name","type":"string"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"cost","type":"uint256"},{"indexed":false,"name":"expires","type":"uint256"}],"name":"NameRenewed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"oracle","type":"address"}],"name":"NewPriceOracle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}] 3 | -------------------------------------------------------------------------------- /contracts/ethcontroller/generate.go: -------------------------------------------------------------------------------- 1 | package ethcontroller 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg ethcontroller -type Contract 4 | -------------------------------------------------------------------------------- /contracts/registry/contract.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"label","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"name":"","type":"uint64"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"}] 2 | -------------------------------------------------------------------------------- /contracts/registry/generate.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg registry -type Contract 4 | -------------------------------------------------------------------------------- /contracts/resolver/generate.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg resolver -type Contract 4 | -------------------------------------------------------------------------------- /contracts/reverseregistrar/contract.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"owner","type":"address"},{"name":"resolver","type":"address"}],"name":"claimWithResolver","outputs":[{"name":"node","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"claim","outputs":[{"name":"node","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"ens","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"defaultResolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"node","outputs":[{"name":"ret","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"setName","outputs":[{"name":"node","type":"bytes32"}],"payable":false,"type":"function"},{"inputs":[{"name":"ensAddr","type":"address"},{"name":"resolverAddr","type":"address"}],"payable":false,"type":"constructor"}] 2 | -------------------------------------------------------------------------------- /contracts/reverseregistrar/contract.go: -------------------------------------------------------------------------------- 1 | // Code generated - DO NOT EDIT. 2 | // This file is a generated binding and any manual changes will be lost. 3 | 4 | package reverseregistrar 5 | 6 | import ( 7 | "math/big" 8 | "strings" 9 | 10 | ethereum "github.com/ethereum/go-ethereum" 11 | "github.com/ethereum/go-ethereum/accounts/abi" 12 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 13 | "github.com/ethereum/go-ethereum/common" 14 | "github.com/ethereum/go-ethereum/core/types" 15 | "github.com/ethereum/go-ethereum/event" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var ( 20 | _ = big.NewInt 21 | _ = strings.NewReader 22 | _ = ethereum.NotFound 23 | _ = bind.Bind 24 | _ = common.Big1 25 | _ = types.BloomLookup 26 | _ = event.NewSubscription 27 | ) 28 | 29 | // ContractABI is the input ABI used to generate the binding from. 30 | const ContractABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"resolver\",\"type\":\"address\"}],\"name\":\"claimWithResolver\",\"outputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"claim\",\"outputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"ens\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"defaultResolver\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"node\",\"outputs\":[{\"name\":\"ret\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"name\",\"type\":\"string\"}],\"name\":\"setName\",\"outputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[{\"name\":\"ensAddr\",\"type\":\"address\"},{\"name\":\"resolverAddr\",\"type\":\"address\"}],\"payable\":false,\"type\":\"constructor\"}]" 31 | 32 | // Contract is an auto generated Go binding around an Ethereum contract. 33 | type Contract struct { 34 | ContractCaller // Read-only binding to the contract 35 | ContractTransactor // Write-only binding to the contract 36 | ContractFilterer // Log filterer for contract events 37 | } 38 | 39 | // ContractCaller is an auto generated read-only Go binding around an Ethereum contract. 40 | type ContractCaller struct { 41 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 42 | } 43 | 44 | // ContractTransactor is an auto generated write-only Go binding around an Ethereum contract. 45 | type ContractTransactor struct { 46 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 47 | } 48 | 49 | // ContractFilterer is an auto generated log filtering Go binding around an Ethereum contract events. 50 | type ContractFilterer struct { 51 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 52 | } 53 | 54 | // ContractSession is an auto generated Go binding around an Ethereum contract, 55 | // with pre-set call and transact options. 56 | type ContractSession struct { 57 | Contract *Contract // Generic contract binding to set the session for 58 | CallOpts bind.CallOpts // Call options to use throughout this session 59 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 60 | } 61 | 62 | // ContractCallerSession is an auto generated read-only Go binding around an Ethereum contract, 63 | // with pre-set call options. 64 | type ContractCallerSession struct { 65 | Contract *ContractCaller // Generic contract caller binding to set the session for 66 | CallOpts bind.CallOpts // Call options to use throughout this session 67 | } 68 | 69 | // ContractTransactorSession is an auto generated write-only Go binding around an Ethereum contract, 70 | // with pre-set transact options. 71 | type ContractTransactorSession struct { 72 | Contract *ContractTransactor // Generic contract transactor binding to set the session for 73 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 74 | } 75 | 76 | // ContractRaw is an auto generated low-level Go binding around an Ethereum contract. 77 | type ContractRaw struct { 78 | Contract *Contract // Generic contract binding to access the raw methods on 79 | } 80 | 81 | // ContractCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. 82 | type ContractCallerRaw struct { 83 | Contract *ContractCaller // Generic read-only contract binding to access the raw methods on 84 | } 85 | 86 | // ContractTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. 87 | type ContractTransactorRaw struct { 88 | Contract *ContractTransactor // Generic write-only contract binding to access the raw methods on 89 | } 90 | 91 | // NewContract creates a new instance of Contract, bound to a specific deployed contract. 92 | func NewContract(address common.Address, backend bind.ContractBackend) (*Contract, error) { 93 | contract, err := bindContract(address, backend, backend, backend) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return &Contract{ContractCaller: ContractCaller{contract: contract}, ContractTransactor: ContractTransactor{contract: contract}, ContractFilterer: ContractFilterer{contract: contract}}, nil 98 | } 99 | 100 | // NewContractCaller creates a new read-only instance of Contract, bound to a specific deployed contract. 101 | func NewContractCaller(address common.Address, caller bind.ContractCaller) (*ContractCaller, error) { 102 | contract, err := bindContract(address, caller, nil, nil) 103 | if err != nil { 104 | return nil, err 105 | } 106 | return &ContractCaller{contract: contract}, nil 107 | } 108 | 109 | // NewContractTransactor creates a new write-only instance of Contract, bound to a specific deployed contract. 110 | func NewContractTransactor(address common.Address, transactor bind.ContractTransactor) (*ContractTransactor, error) { 111 | contract, err := bindContract(address, nil, transactor, nil) 112 | if err != nil { 113 | return nil, err 114 | } 115 | return &ContractTransactor{contract: contract}, nil 116 | } 117 | 118 | // NewContractFilterer creates a new log filterer instance of Contract, bound to a specific deployed contract. 119 | func NewContractFilterer(address common.Address, filterer bind.ContractFilterer) (*ContractFilterer, error) { 120 | contract, err := bindContract(address, nil, nil, filterer) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return &ContractFilterer{contract: contract}, nil 125 | } 126 | 127 | // bindContract binds a generic wrapper to an already deployed contract. 128 | func bindContract(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { 129 | parsed, err := abi.JSON(strings.NewReader(ContractABI)) 130 | if err != nil { 131 | return nil, err 132 | } 133 | return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil 134 | } 135 | 136 | // Call invokes the (constant) contract method with params as input values and 137 | // sets the output to result. The result type might be a single field for simple 138 | // returns, a slice of interfaces for anonymous returns and a struct for named 139 | // returns. 140 | func (_Contract *ContractRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 141 | return _Contract.Contract.ContractCaller.contract.Call(opts, result, method, params...) 142 | } 143 | 144 | // Transfer initiates a plain transaction to move funds to the contract, calling 145 | // its default method if one is available. 146 | func (_Contract *ContractRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 147 | return _Contract.Contract.ContractTransactor.contract.Transfer(opts) 148 | } 149 | 150 | // Transact invokes the (paid) contract method with params as input values. 151 | func (_Contract *ContractRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 152 | return _Contract.Contract.ContractTransactor.contract.Transact(opts, method, params...) 153 | } 154 | 155 | // Call invokes the (constant) contract method with params as input values and 156 | // sets the output to result. The result type might be a single field for simple 157 | // returns, a slice of interfaces for anonymous returns and a struct for named 158 | // returns. 159 | func (_Contract *ContractCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 160 | return _Contract.Contract.contract.Call(opts, result, method, params...) 161 | } 162 | 163 | // Transfer initiates a plain transaction to move funds to the contract, calling 164 | // its default method if one is available. 165 | func (_Contract *ContractTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 166 | return _Contract.Contract.contract.Transfer(opts) 167 | } 168 | 169 | // Transact invokes the (paid) contract method with params as input values. 170 | func (_Contract *ContractTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 171 | return _Contract.Contract.contract.Transact(opts, method, params...) 172 | } 173 | 174 | // DefaultResolver is a free data retrieval call binding the contract method 0x828eab0e. 175 | // 176 | // Solidity: function defaultResolver() returns(address) 177 | func (_Contract *ContractCaller) DefaultResolver(opts *bind.CallOpts) (common.Address, error) { 178 | var out []interface{} 179 | err := _Contract.contract.Call(opts, &out, "defaultResolver") 180 | 181 | if err != nil { 182 | return *new(common.Address), err 183 | } 184 | 185 | out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) 186 | 187 | return out0, err 188 | 189 | } 190 | 191 | // DefaultResolver is a free data retrieval call binding the contract method 0x828eab0e. 192 | // 193 | // Solidity: function defaultResolver() returns(address) 194 | func (_Contract *ContractSession) DefaultResolver() (common.Address, error) { 195 | return _Contract.Contract.DefaultResolver(&_Contract.CallOpts) 196 | } 197 | 198 | // DefaultResolver is a free data retrieval call binding the contract method 0x828eab0e. 199 | // 200 | // Solidity: function defaultResolver() returns(address) 201 | func (_Contract *ContractCallerSession) DefaultResolver() (common.Address, error) { 202 | return _Contract.Contract.DefaultResolver(&_Contract.CallOpts) 203 | } 204 | 205 | // Ens is a free data retrieval call binding the contract method 0x3f15457f. 206 | // 207 | // Solidity: function ens() returns(address) 208 | func (_Contract *ContractCaller) Ens(opts *bind.CallOpts) (common.Address, error) { 209 | var out []interface{} 210 | err := _Contract.contract.Call(opts, &out, "ens") 211 | 212 | if err != nil { 213 | return *new(common.Address), err 214 | } 215 | 216 | out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) 217 | 218 | return out0, err 219 | 220 | } 221 | 222 | // Ens is a free data retrieval call binding the contract method 0x3f15457f. 223 | // 224 | // Solidity: function ens() returns(address) 225 | func (_Contract *ContractSession) Ens() (common.Address, error) { 226 | return _Contract.Contract.Ens(&_Contract.CallOpts) 227 | } 228 | 229 | // Ens is a free data retrieval call binding the contract method 0x3f15457f. 230 | // 231 | // Solidity: function ens() returns(address) 232 | func (_Contract *ContractCallerSession) Ens() (common.Address, error) { 233 | return _Contract.Contract.Ens(&_Contract.CallOpts) 234 | } 235 | 236 | // Node is a free data retrieval call binding the contract method 0xbffbe61c. 237 | // 238 | // Solidity: function node(address addr) returns(bytes32 ret) 239 | func (_Contract *ContractCaller) Node(opts *bind.CallOpts, addr common.Address) ([32]byte, error) { 240 | var out []interface{} 241 | err := _Contract.contract.Call(opts, &out, "node", addr) 242 | 243 | if err != nil { 244 | return *new([32]byte), err 245 | } 246 | 247 | out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) 248 | 249 | return out0, err 250 | 251 | } 252 | 253 | // Node is a free data retrieval call binding the contract method 0xbffbe61c. 254 | // 255 | // Solidity: function node(address addr) returns(bytes32 ret) 256 | func (_Contract *ContractSession) Node(addr common.Address) ([32]byte, error) { 257 | return _Contract.Contract.Node(&_Contract.CallOpts, addr) 258 | } 259 | 260 | // Node is a free data retrieval call binding the contract method 0xbffbe61c. 261 | // 262 | // Solidity: function node(address addr) returns(bytes32 ret) 263 | func (_Contract *ContractCallerSession) Node(addr common.Address) ([32]byte, error) { 264 | return _Contract.Contract.Node(&_Contract.CallOpts, addr) 265 | } 266 | 267 | // Claim is a paid mutator transaction binding the contract method 0x1e83409a. 268 | // 269 | // Solidity: function claim(address owner) returns(bytes32 node) 270 | func (_Contract *ContractTransactor) Claim(opts *bind.TransactOpts, owner common.Address) (*types.Transaction, error) { 271 | return _Contract.contract.Transact(opts, "claim", owner) 272 | } 273 | 274 | // Claim is a paid mutator transaction binding the contract method 0x1e83409a. 275 | // 276 | // Solidity: function claim(address owner) returns(bytes32 node) 277 | func (_Contract *ContractSession) Claim(owner common.Address) (*types.Transaction, error) { 278 | return _Contract.Contract.Claim(&_Contract.TransactOpts, owner) 279 | } 280 | 281 | // Claim is a paid mutator transaction binding the contract method 0x1e83409a. 282 | // 283 | // Solidity: function claim(address owner) returns(bytes32 node) 284 | func (_Contract *ContractTransactorSession) Claim(owner common.Address) (*types.Transaction, error) { 285 | return _Contract.Contract.Claim(&_Contract.TransactOpts, owner) 286 | } 287 | 288 | // ClaimWithResolver is a paid mutator transaction binding the contract method 0x0f5a5466. 289 | // 290 | // Solidity: function claimWithResolver(address owner, address resolver) returns(bytes32 node) 291 | func (_Contract *ContractTransactor) ClaimWithResolver(opts *bind.TransactOpts, owner common.Address, resolver common.Address) (*types.Transaction, error) { 292 | return _Contract.contract.Transact(opts, "claimWithResolver", owner, resolver) 293 | } 294 | 295 | // ClaimWithResolver is a paid mutator transaction binding the contract method 0x0f5a5466. 296 | // 297 | // Solidity: function claimWithResolver(address owner, address resolver) returns(bytes32 node) 298 | func (_Contract *ContractSession) ClaimWithResolver(owner common.Address, resolver common.Address) (*types.Transaction, error) { 299 | return _Contract.Contract.ClaimWithResolver(&_Contract.TransactOpts, owner, resolver) 300 | } 301 | 302 | // ClaimWithResolver is a paid mutator transaction binding the contract method 0x0f5a5466. 303 | // 304 | // Solidity: function claimWithResolver(address owner, address resolver) returns(bytes32 node) 305 | func (_Contract *ContractTransactorSession) ClaimWithResolver(owner common.Address, resolver common.Address) (*types.Transaction, error) { 306 | return _Contract.Contract.ClaimWithResolver(&_Contract.TransactOpts, owner, resolver) 307 | } 308 | 309 | // SetName is a paid mutator transaction binding the contract method 0xc47f0027. 310 | // 311 | // Solidity: function setName(string name) returns(bytes32 node) 312 | func (_Contract *ContractTransactor) SetName(opts *bind.TransactOpts, name string) (*types.Transaction, error) { 313 | return _Contract.contract.Transact(opts, "setName", name) 314 | } 315 | 316 | // SetName is a paid mutator transaction binding the contract method 0xc47f0027. 317 | // 318 | // Solidity: function setName(string name) returns(bytes32 node) 319 | func (_Contract *ContractSession) SetName(name string) (*types.Transaction, error) { 320 | return _Contract.Contract.SetName(&_Contract.TransactOpts, name) 321 | } 322 | 323 | // SetName is a paid mutator transaction binding the contract method 0xc47f0027. 324 | // 325 | // Solidity: function setName(string name) returns(bytes32 node) 326 | func (_Contract *ContractTransactorSession) SetName(name string) (*types.Transaction, error) { 327 | return _Contract.Contract.SetName(&_Contract.TransactOpts, name) 328 | } 329 | -------------------------------------------------------------------------------- /contracts/reverseregistrar/generate.go: -------------------------------------------------------------------------------- 1 | package reverseregistrar 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg reverseregistrar -type Contract 4 | -------------------------------------------------------------------------------- /contracts/reverseresolver/contract.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"ens","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"_name","type":"string"}],"name":"setName","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"ensAddr","type":"address"}],"payable":false,"type":"constructor"}] 2 | -------------------------------------------------------------------------------- /contracts/reverseresolver/contract.go: -------------------------------------------------------------------------------- 1 | // Code generated - DO NOT EDIT. 2 | // This file is a generated binding and any manual changes will be lost. 3 | 4 | package reverseresolver 5 | 6 | import ( 7 | "errors" 8 | "math/big" 9 | "strings" 10 | 11 | ethereum "github.com/ethereum/go-ethereum" 12 | "github.com/ethereum/go-ethereum/accounts/abi" 13 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 14 | "github.com/ethereum/go-ethereum/common" 15 | "github.com/ethereum/go-ethereum/core/types" 16 | "github.com/ethereum/go-ethereum/event" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var ( 21 | _ = errors.New 22 | _ = big.NewInt 23 | _ = strings.NewReader 24 | _ = ethereum.NotFound 25 | _ = bind.Bind 26 | _ = common.Big1 27 | _ = types.BloomLookup 28 | _ = event.NewSubscription 29 | ) 30 | 31 | // ContractMetaData contains all meta data concerning the Contract contract. 32 | var ContractMetaData = &bind.MetaData{ 33 | ABI: "[{\"constant\":true,\"inputs\":[],\"name\":\"ens\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"_name\",\"type\":\"string\"}],\"name\":\"setName\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"inputs\":[{\"name\":\"ensAddr\",\"type\":\"address\"}],\"payable\":false,\"type\":\"constructor\"}]", 34 | } 35 | 36 | // ContractABI is the input ABI used to generate the binding from. 37 | // Deprecated: Use ContractMetaData.ABI instead. 38 | var ContractABI = ContractMetaData.ABI 39 | 40 | // Contract is an auto generated Go binding around an Ethereum contract. 41 | type Contract struct { 42 | ContractCaller // Read-only binding to the contract 43 | ContractTransactor // Write-only binding to the contract 44 | ContractFilterer // Log filterer for contract events 45 | } 46 | 47 | // ContractCaller is an auto generated read-only Go binding around an Ethereum contract. 48 | type ContractCaller struct { 49 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 50 | } 51 | 52 | // ContractTransactor is an auto generated write-only Go binding around an Ethereum contract. 53 | type ContractTransactor struct { 54 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 55 | } 56 | 57 | // ContractFilterer is an auto generated log filtering Go binding around an Ethereum contract events. 58 | type ContractFilterer struct { 59 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 60 | } 61 | 62 | // ContractSession is an auto generated Go binding around an Ethereum contract, 63 | // with pre-set call and transact options. 64 | type ContractSession struct { 65 | Contract *Contract // Generic contract binding to set the session for 66 | CallOpts bind.CallOpts // Call options to use throughout this session 67 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 68 | } 69 | 70 | // ContractCallerSession is an auto generated read-only Go binding around an Ethereum contract, 71 | // with pre-set call options. 72 | type ContractCallerSession struct { 73 | Contract *ContractCaller // Generic contract caller binding to set the session for 74 | CallOpts bind.CallOpts // Call options to use throughout this session 75 | } 76 | 77 | // ContractTransactorSession is an auto generated write-only Go binding around an Ethereum contract, 78 | // with pre-set transact options. 79 | type ContractTransactorSession struct { 80 | Contract *ContractTransactor // Generic contract transactor binding to set the session for 81 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 82 | } 83 | 84 | // ContractRaw is an auto generated low-level Go binding around an Ethereum contract. 85 | type ContractRaw struct { 86 | Contract *Contract // Generic contract binding to access the raw methods on 87 | } 88 | 89 | // ContractCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. 90 | type ContractCallerRaw struct { 91 | Contract *ContractCaller // Generic read-only contract binding to access the raw methods on 92 | } 93 | 94 | // ContractTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. 95 | type ContractTransactorRaw struct { 96 | Contract *ContractTransactor // Generic write-only contract binding to access the raw methods on 97 | } 98 | 99 | // NewContract creates a new instance of Contract, bound to a specific deployed contract. 100 | func NewContract(address common.Address, backend bind.ContractBackend) (*Contract, error) { 101 | contract, err := bindContract(address, backend, backend, backend) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return &Contract{ContractCaller: ContractCaller{contract: contract}, ContractTransactor: ContractTransactor{contract: contract}, ContractFilterer: ContractFilterer{contract: contract}}, nil 106 | } 107 | 108 | // NewContractCaller creates a new read-only instance of Contract, bound to a specific deployed contract. 109 | func NewContractCaller(address common.Address, caller bind.ContractCaller) (*ContractCaller, error) { 110 | contract, err := bindContract(address, caller, nil, nil) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return &ContractCaller{contract: contract}, nil 115 | } 116 | 117 | // NewContractTransactor creates a new write-only instance of Contract, bound to a specific deployed contract. 118 | func NewContractTransactor(address common.Address, transactor bind.ContractTransactor) (*ContractTransactor, error) { 119 | contract, err := bindContract(address, nil, transactor, nil) 120 | if err != nil { 121 | return nil, err 122 | } 123 | return &ContractTransactor{contract: contract}, nil 124 | } 125 | 126 | // NewContractFilterer creates a new log filterer instance of Contract, bound to a specific deployed contract. 127 | func NewContractFilterer(address common.Address, filterer bind.ContractFilterer) (*ContractFilterer, error) { 128 | contract, err := bindContract(address, nil, nil, filterer) 129 | if err != nil { 130 | return nil, err 131 | } 132 | return &ContractFilterer{contract: contract}, nil 133 | } 134 | 135 | // bindContract binds a generic wrapper to an already deployed contract. 136 | func bindContract(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { 137 | parsed, err := abi.JSON(strings.NewReader(ContractABI)) 138 | if err != nil { 139 | return nil, err 140 | } 141 | return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil 142 | } 143 | 144 | // Call invokes the (constant) contract method with params as input values and 145 | // sets the output to result. The result type might be a single field for simple 146 | // returns, a slice of interfaces for anonymous returns and a struct for named 147 | // returns. 148 | func (_Contract *ContractRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 149 | return _Contract.Contract.ContractCaller.contract.Call(opts, result, method, params...) 150 | } 151 | 152 | // Transfer initiates a plain transaction to move funds to the contract, calling 153 | // its default method if one is available. 154 | func (_Contract *ContractRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 155 | return _Contract.Contract.ContractTransactor.contract.Transfer(opts) 156 | } 157 | 158 | // Transact invokes the (paid) contract method with params as input values. 159 | func (_Contract *ContractRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 160 | return _Contract.Contract.ContractTransactor.contract.Transact(opts, method, params...) 161 | } 162 | 163 | // Call invokes the (constant) contract method with params as input values and 164 | // sets the output to result. The result type might be a single field for simple 165 | // returns, a slice of interfaces for anonymous returns and a struct for named 166 | // returns. 167 | func (_Contract *ContractCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 168 | return _Contract.Contract.contract.Call(opts, result, method, params...) 169 | } 170 | 171 | // Transfer initiates a plain transaction to move funds to the contract, calling 172 | // its default method if one is available. 173 | func (_Contract *ContractTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 174 | return _Contract.Contract.contract.Transfer(opts) 175 | } 176 | 177 | // Transact invokes the (paid) contract method with params as input values. 178 | func (_Contract *ContractTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 179 | return _Contract.Contract.contract.Transact(opts, method, params...) 180 | } 181 | 182 | // Ens is a free data retrieval call binding the contract method 0x3f15457f. 183 | // 184 | // Solidity: function ens() returns(address) 185 | func (_Contract *ContractCaller) Ens(opts *bind.CallOpts) (common.Address, error) { 186 | var out []interface{} 187 | err := _Contract.contract.Call(opts, &out, "ens") 188 | 189 | if err != nil { 190 | return *new(common.Address), err 191 | } 192 | 193 | out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) 194 | 195 | return out0, err 196 | 197 | } 198 | 199 | // Ens is a free data retrieval call binding the contract method 0x3f15457f. 200 | // 201 | // Solidity: function ens() returns(address) 202 | func (_Contract *ContractSession) Ens() (common.Address, error) { 203 | return _Contract.Contract.Ens(&_Contract.CallOpts) 204 | } 205 | 206 | // Ens is a free data retrieval call binding the contract method 0x3f15457f. 207 | // 208 | // Solidity: function ens() returns(address) 209 | func (_Contract *ContractCallerSession) Ens() (common.Address, error) { 210 | return _Contract.Contract.Ens(&_Contract.CallOpts) 211 | } 212 | 213 | // Name is a free data retrieval call binding the contract method 0x691f3431. 214 | // 215 | // Solidity: function name(bytes32 ) returns(string) 216 | func (_Contract *ContractCaller) Name(opts *bind.CallOpts, arg0 [32]byte) (string, error) { 217 | var out []interface{} 218 | err := _Contract.contract.Call(opts, &out, "name", arg0) 219 | 220 | if err != nil { 221 | return *new(string), err 222 | } 223 | 224 | out0 := *abi.ConvertType(out[0], new(string)).(*string) 225 | 226 | return out0, err 227 | 228 | } 229 | 230 | // Name is a free data retrieval call binding the contract method 0x691f3431. 231 | // 232 | // Solidity: function name(bytes32 ) returns(string) 233 | func (_Contract *ContractSession) Name(arg0 [32]byte) (string, error) { 234 | return _Contract.Contract.Name(&_Contract.CallOpts, arg0) 235 | } 236 | 237 | // Name is a free data retrieval call binding the contract method 0x691f3431. 238 | // 239 | // Solidity: function name(bytes32 ) returns(string) 240 | func (_Contract *ContractCallerSession) Name(arg0 [32]byte) (string, error) { 241 | return _Contract.Contract.Name(&_Contract.CallOpts, arg0) 242 | } 243 | 244 | // SetName is a paid mutator transaction binding the contract method 0x77372213. 245 | // 246 | // Solidity: function setName(bytes32 node, string _name) returns() 247 | func (_Contract *ContractTransactor) SetName(opts *bind.TransactOpts, node [32]byte, _name string) (*types.Transaction, error) { 248 | return _Contract.contract.Transact(opts, "setName", node, _name) 249 | } 250 | 251 | // SetName is a paid mutator transaction binding the contract method 0x77372213. 252 | // 253 | // Solidity: function setName(bytes32 node, string _name) returns() 254 | func (_Contract *ContractSession) SetName(node [32]byte, _name string) (*types.Transaction, error) { 255 | return _Contract.Contract.SetName(&_Contract.TransactOpts, node, _name) 256 | } 257 | 258 | // SetName is a paid mutator transaction binding the contract method 0x77372213. 259 | // 260 | // Solidity: function setName(bytes32 node, string _name) returns() 261 | func (_Contract *ContractTransactorSession) SetName(node [32]byte, _name string) (*types.Transaction, error) { 262 | return _Contract.Contract.SetName(&_Contract.TransactOpts, node, _name) 263 | } 264 | -------------------------------------------------------------------------------- /contracts/reverseresolver/generate.go: -------------------------------------------------------------------------------- 1 | package reverseresolver 2 | 3 | //go:generate abigen -abi contract.abi -out contract.go -pkg reverseresolver -type Contract 4 | -------------------------------------------------------------------------------- /deed.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 19 | "github.com/ethereum/go-ethereum/common" 20 | "github.com/ethereum/go-ethereum/core/types" 21 | "github.com/wealdtech/go-ens/v3/contracts/deed" 22 | ) 23 | 24 | // Deed is the structure for the deed. 25 | type Deed struct { 26 | Contract *deed.Contract 27 | } 28 | 29 | // NewDeed obtains the deed contract for a given domain. 30 | func NewDeed(backend bind.ContractBackend, domain string) (*Deed, error) { 31 | // Obtain the auction registrar for the deed. 32 | auctionRegistrar, err := NewAuctionRegistrar(backend, domain) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | entry, err := auctionRegistrar.Entry(domain) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return NewDeedAt(backend, entry.Deed) 43 | } 44 | 45 | // NewDeedAt creates a deed contract at a given address. 46 | func NewDeedAt(backend bind.ContractBackend, address common.Address) (*Deed, error) { 47 | contract, err := deed.NewContract(address, backend) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return &Deed{ 53 | Contract: contract, 54 | }, nil 55 | } 56 | 57 | // Owner obtains the owner of the deed. 58 | func (c *Deed) Owner() (common.Address, error) { 59 | return c.Contract.Owner(nil) 60 | } 61 | 62 | // PreviousOwner obtains the previous owner of the deed. 63 | func (c *Deed) PreviousOwner() (common.Address, error) { 64 | return c.Contract.PreviousOwner(nil) 65 | } 66 | 67 | // SetOwner sets the owner of the deed. 68 | func (c *Deed) SetOwner(opts *bind.TransactOpts, address common.Address) (*types.Transaction, error) { 69 | return c.Contract.SetOwner(opts, address) 70 | } 71 | -------------------------------------------------------------------------------- /dnsregistrar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 21 | "github.com/ethereum/go-ethereum/common" 22 | "github.com/wealdtech/go-ens/v3/contracts/dnsregistrar" 23 | ) 24 | 25 | // DNSRegistrar is the structure for the registrar. 26 | type DNSRegistrar struct { 27 | backend bind.ContractBackend 28 | domain string 29 | Contract *dnsregistrar.Contract 30 | ContractAddr common.Address 31 | } 32 | 33 | // NewDNSRegistrar obtains the registrar contract for a given domain. 34 | func NewDNSRegistrar(backend bind.ContractBackend, domain string) (*DNSRegistrar, error) { 35 | address, err := RegistrarContractAddress(backend, domain) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | if address == UnknownAddress { 41 | return nil, fmt.Errorf("no registrar for domain %s", domain) 42 | } 43 | 44 | contract, err := dnsregistrar.NewContract(address, backend) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | // Ensure this really is a DNS registrar. To do this confirm that it supports 50 | // the expected interface. 51 | supported, err := contract.SupportsInterface(nil, [4]byte{0x1a, 0xa2, 0xe6, 0x41}) 52 | if err != nil { 53 | return nil, err 54 | } 55 | if !supported { 56 | return nil, fmt.Errorf("purported registrar for domain %s does not support DNS registrar functionality", domain) 57 | } 58 | 59 | return &DNSRegistrar{ 60 | backend: backend, 61 | domain: domain, 62 | Contract: contract, 63 | ContractAddr: address, 64 | }, nil 65 | } 66 | -------------------------------------------------------------------------------- /dnsresolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 22 | "github.com/ethereum/go-ethereum/common" 23 | "github.com/ethereum/go-ethereum/core/types" 24 | "github.com/wealdtech/go-ens/v3/contracts/dnsresolver" 25 | "golang.org/x/crypto/sha3" 26 | ) 27 | 28 | // DNSResolver is the structure for the DNS resolver contract. 29 | type DNSResolver struct { 30 | backend bind.ContractBackend 31 | domain string 32 | Contract *dnsresolver.Contract 33 | ContractAddr common.Address 34 | } 35 | 36 | // NewDNSResolver creates a new DNS resolver for a given domain. 37 | func NewDNSResolver(backend bind.ContractBackend, domain string) (*DNSResolver, error) { 38 | registry, err := NewRegistry(backend) 39 | if err != nil { 40 | return nil, err 41 | } 42 | address, err := registry.ResolverAddress(domain) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | return NewDNSResolverAt(backend, domain, address) 48 | } 49 | 50 | // NewDNSResolverAt creates a new DNS resolver for a given domain at a given address. 51 | func NewDNSResolverAt(backend bind.ContractBackend, domain string, address common.Address) (*DNSResolver, error) { 52 | contract, err := dnsresolver.NewContract(address, backend) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | // Ensure that this is a DNS resolver. 58 | supported, err := contract.SupportsInterface(nil, [4]byte{0xa8, 0xfa, 0x56, 0x82}) 59 | if err != nil { 60 | return nil, err 61 | } 62 | if !supported { 63 | return nil, fmt.Errorf("%s is not a DNS resolver contract", address.Hex()) 64 | } 65 | 66 | return &DNSResolver{ 67 | backend: backend, 68 | domain: domain, 69 | Contract: contract, 70 | ContractAddr: address, 71 | }, nil 72 | } 73 | 74 | // Record obtains an RRSet for a name. 75 | func (r *DNSResolver) Record(name string, rrType uint16) ([]byte, error) { 76 | nameHash, err := NameHash(r.domain) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return r.Contract.DnsRecord(nil, nameHash, DNSWireFormatDomainHash(name), rrType) 81 | } 82 | 83 | // HasRecords returns true if the given name has any RRsets. 84 | func (r *DNSResolver) HasRecords(name string) (bool, error) { 85 | nameHash, err := NameHash(r.domain) 86 | if err != nil { 87 | return false, err 88 | } 89 | return r.Contract.HasDNSRecords(nil, nameHash, DNSWireFormatDomainHash(name)) 90 | } 91 | 92 | // SetRecords sets one or more RRSets. 93 | func (r *DNSResolver) SetRecords(opts *bind.TransactOpts, data []byte) (*types.Transaction, error) { 94 | nameHash, err := NameHash(r.domain) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return r.Contract.SetDNSRecords(opts, nameHash, data) 99 | } 100 | 101 | // ClearDNSZone clears all records in the zone. 102 | func (r *DNSResolver) ClearDNSZone(opts *bind.TransactOpts) (*types.Transaction, error) { 103 | nameHash, err := NameHash(r.domain) 104 | if err != nil { 105 | return nil, err 106 | } 107 | return r.Contract.ClearDNSZone(opts, nameHash) 108 | } 109 | 110 | // Zonehash returns the zone hash of the domain. 111 | func (r *DNSResolver) Zonehash() ([]byte, error) { 112 | nameHash, err := NameHash(r.domain) 113 | if err != nil { 114 | return nil, err 115 | } 116 | return r.Contract.Zonehash(nil, nameHash) 117 | } 118 | 119 | // SetZonehash sets the zone hash of the domain. 120 | func (r *DNSResolver) SetZonehash(opts *bind.TransactOpts, zonehash []byte) (*types.Transaction, error) { 121 | nameHash, err := NameHash(r.domain) 122 | if err != nil { 123 | return nil, err 124 | } 125 | return r.Contract.SetZonehash(opts, nameHash, zonehash) 126 | } 127 | 128 | // DNSWireFormatDomainHash hashes a domain name in wire format. 129 | func DNSWireFormatDomainHash(domain string) [32]byte { 130 | var hash [32]byte 131 | 132 | sha := sha3.NewLegacyKeccak256() 133 | // //nolint:golint,errcheck 134 | sha.Write(DNSWireFormat(domain)) 135 | sha.Sum(hash[:0]) 136 | 137 | return hash 138 | } 139 | 140 | // DNSWireFormat turns a domain name in to wire format. 141 | func DNSWireFormat(domain string) []byte { 142 | // Remove leading and trailing dots. 143 | domain = strings.TrimLeft(domain, ".") 144 | domain = strings.TrimRight(domain, ".") 145 | domain = strings.ToLower(domain) 146 | 147 | if domain == "" { 148 | return []byte{0x00} 149 | } 150 | 151 | bytes := make([]byte, len(domain)+2) 152 | pieces := strings.Split(domain, ".") 153 | offset := 0 154 | for _, piece := range pieces { 155 | bytes[offset] = byte(len(piece)) 156 | offset++ 157 | copy(bytes[offset:offset+len(piece)], piece) 158 | offset += len(piece) 159 | } 160 | return bytes 161 | } 162 | -------------------------------------------------------------------------------- /dnssecoracle.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 19 | "github.com/ethereum/go-ethereum/common" 20 | "github.com/wealdtech/go-ens/v3/contracts/dnssecoracle" 21 | ) 22 | 23 | // DNSSECOracle is the structure for the DNSSEC oracle. 24 | type DNSSECOracle struct { 25 | backend bind.ContractBackend 26 | domain string 27 | Contract *dnssecoracle.Contract 28 | ContractAddr common.Address 29 | } 30 | 31 | // NewDNSSECOracle obtains the DNSSEC oracle contract for a given domain. 32 | func NewDNSSECOracle(backend bind.ContractBackend, domain string) (*DNSSECOracle, error) { 33 | registrar, err := NewDNSRegistrar(backend, domain) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | address, err := registrar.Contract.Oracle(nil) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | contract, err := dnssecoracle.NewContract(address, backend) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return &DNSSECOracle{ 49 | backend: backend, 50 | domain: domain, 51 | Contract: contract, 52 | ContractAddr: address, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /ethcontroller.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "math/big" 21 | "time" 22 | 23 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 24 | "github.com/ethereum/go-ethereum/common" 25 | "github.com/ethereum/go-ethereum/core/types" 26 | "github.com/wealdtech/go-ens/v3/contracts/ethcontroller" 27 | ) 28 | 29 | // ETHController is the structure for the .eth controller contract. 30 | type ETHController struct { 31 | backend bind.ContractBackend 32 | Contract *ethcontroller.Contract 33 | ContractAddr common.Address 34 | domain string 35 | } 36 | 37 | // NewETHController creates a new controller for a given domain. 38 | func NewETHController(backend bind.ContractBackend, domain string) (*ETHController, error) { 39 | registry, err := NewRegistry(backend) 40 | if err != nil { 41 | return nil, err 42 | } 43 | resolver, err := registry.Resolver(domain) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | // Obtain the controller from the resolver. 49 | controllerAddress, err := resolver.InterfaceImplementer([4]byte{0x01, 0x8f, 0xac, 0x06}) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return NewETHControllerAt(backend, domain, controllerAddress) 55 | } 56 | 57 | // NewETHControllerAt creates a .eth controller at a given address. 58 | func NewETHControllerAt(backend bind.ContractBackend, domain string, address common.Address) (*ETHController, error) { 59 | contract, err := ethcontroller.NewContract(address, backend) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return ÐController{ 64 | backend: backend, 65 | Contract: contract, 66 | ContractAddr: address, 67 | domain: domain, 68 | }, nil 69 | } 70 | 71 | // IsValid returns true if the domain is considered valid by the controller. 72 | func (c *ETHController) IsValid(domain string) (bool, error) { 73 | name, err := UnqualifiedName(domain, c.domain) 74 | if err != nil { 75 | return false, fmt.Errorf("invalid name %s", domain) 76 | } 77 | return c.Contract.Valid(nil, name) 78 | } 79 | 80 | // IsAvailable returns true if the domain is available for registration. 81 | func (c *ETHController) IsAvailable(domain string) (bool, error) { 82 | name, err := UnqualifiedName(domain, c.domain) 83 | if err != nil { 84 | return false, fmt.Errorf("invalid name %s", domain) 85 | } 86 | return c.Contract.Available(nil, name) 87 | } 88 | 89 | // MinRegistrationDuration returns the minimum duration for which a name can be registered. 90 | func (c *ETHController) MinRegistrationDuration() (time.Duration, error) { 91 | tmp, err := c.Contract.MINREGISTRATIONDURATION(nil) 92 | if err != nil { 93 | return 0 * time.Second, err 94 | } 95 | 96 | return time.Duration(tmp.Int64()) * time.Second, nil 97 | } 98 | 99 | // RentCost returns the cost of rent in wei-per-second. 100 | func (c *ETHController) RentCost(domain string) (*big.Int, error) { 101 | name, err := UnqualifiedName(domain, c.domain) 102 | if err != nil { 103 | return nil, fmt.Errorf("invalid name %s", domain) 104 | } 105 | return c.Contract.RentPrice(nil, name, big.NewInt(1)) 106 | } 107 | 108 | // MinCommitmentInterval returns the minimum time that has to pass between a commit and reveal. 109 | func (c *ETHController) MinCommitmentInterval() (*big.Int, error) { 110 | return c.Contract.MinCommitmentAge(nil) 111 | } 112 | 113 | // MaxCommitmentInterval returns the maximum time that has to pass between a commit and reveal. 114 | func (c *ETHController) MaxCommitmentInterval() (*big.Int, error) { 115 | return c.Contract.MaxCommitmentAge(nil) 116 | } 117 | 118 | // CommitmentHash returns the commitment hash for a label/owner/secret tuple. 119 | func (c *ETHController) CommitmentHash(domain string, owner common.Address, secret [32]byte) (common.Hash, error) { 120 | name, err := UnqualifiedName(domain, c.domain) 121 | if err != nil { 122 | return common.BytesToHash([]byte{}), fmt.Errorf("invalid name %s", domain) 123 | } 124 | 125 | commitment, err := c.Contract.MakeCommitment(nil, name, owner, secret) 126 | if err != nil { 127 | return common.BytesToHash([]byte{}), err 128 | } 129 | hash := common.BytesToHash(commitment[:]) 130 | return hash, err 131 | } 132 | 133 | // CommitmentTime states the time at which a commitment was registered on the blockchain. 134 | func (c *ETHController) CommitmentTime(domain string, owner common.Address, secret [32]byte) (*big.Int, error) { 135 | hash, err := c.CommitmentHash(domain, owner, secret) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | return c.Contract.Commitments(nil, hash) 141 | } 142 | 143 | // Commit sends a commitment to register a domain. 144 | func (c *ETHController) Commit(opts *bind.TransactOpts, domain string, owner common.Address, secret [32]byte) (*types.Transaction, error) { 145 | name, err := UnqualifiedName(domain, c.domain) 146 | if err != nil { 147 | return nil, fmt.Errorf("invalid name %s", domain) 148 | } 149 | 150 | commitment, err := c.Contract.MakeCommitment(nil, name, owner, secret) 151 | if err != nil { 152 | return nil, errors.New("failed to create commitment") 153 | } 154 | 155 | if opts.Value != nil && opts.Value.Cmp(big.NewInt(0)) != 0 { 156 | return nil, errors.New("commitment should have 0 value") 157 | } 158 | 159 | return c.Contract.Commit(opts, commitment) 160 | } 161 | 162 | // Reveal reveals a commitment to register a domain. 163 | func (c *ETHController) Reveal(opts *bind.TransactOpts, domain string, owner common.Address, secret [32]byte) (*types.Transaction, error) { 164 | name, err := UnqualifiedName(domain, c.domain) 165 | if err != nil { 166 | return nil, fmt.Errorf("invalid name %s", domain) 167 | } 168 | 169 | if opts == nil { 170 | return nil, errors.New("transaction options required") 171 | } 172 | if opts.Value == nil { 173 | return nil, errors.New("no ether supplied with transaction") 174 | } 175 | 176 | commitTS, err := c.CommitmentTime(name, owner, secret) 177 | if err != nil { 178 | return nil, err 179 | } 180 | if commitTS.Cmp(big.NewInt(0)) == 0 { 181 | return nil, errors.New("no commitment present") 182 | } 183 | commit := time.Unix(commitTS.Int64(), 0) 184 | 185 | minCommitIntervalTS, err := c.MinCommitmentInterval() 186 | if err != nil { 187 | return nil, err 188 | } 189 | minCommitInterval := time.Duration(minCommitIntervalTS.Int64()) * time.Second 190 | minRevealTime := commit.Add(minCommitInterval) 191 | if time.Now().Before(minRevealTime) { 192 | return nil, errors.New("commitment too young to reveal") 193 | } 194 | 195 | maxCommitIntervalTS, err := c.MaxCommitmentInterval() 196 | if err != nil { 197 | return nil, err 198 | } 199 | maxCommitInterval := time.Duration(maxCommitIntervalTS.Int64()) * time.Second 200 | maxRevealTime := commit.Add(maxCommitInterval) 201 | if time.Now().After(maxRevealTime) { 202 | return nil, errors.New("commitment too old to reveal") 203 | } 204 | 205 | // Calculate the duration given the rent cost and the value. 206 | costPerSecond, err := c.RentCost(domain) 207 | if err != nil { 208 | return nil, errors.New("failed to obtain rent cost") 209 | } 210 | duration := new(big.Int).Div(opts.Value, costPerSecond) 211 | 212 | // Ensure duration is greater than minimum duration. 213 | minDuration, err := c.MinRegistrationDuration() 214 | if err != nil { 215 | return nil, err 216 | } 217 | if big.NewInt(int64(minDuration.Seconds())).Cmp(duration) >= 0 { 218 | return nil, fmt.Errorf("not enough funds to cover minimum duration of %v", minDuration) 219 | } 220 | 221 | return c.Contract.Register(opts, name, owner, duration, secret) 222 | } 223 | 224 | // Renew renews a registered domain. 225 | func (c *ETHController) Renew(opts *bind.TransactOpts, domain string) (*types.Transaction, error) { 226 | name, err := UnqualifiedName(domain, c.domain) 227 | if err != nil { 228 | return nil, fmt.Errorf("invalid name %s", domain) 229 | } 230 | 231 | // See if we're registered at all - fetch the owner to find out. 232 | registry, err := NewRegistry(c.backend) 233 | if err != nil { 234 | return nil, err 235 | } 236 | owner, err := registry.Owner(domain) 237 | if err != nil { 238 | return nil, err 239 | } 240 | if owner == UnknownAddress { 241 | return nil, fmt.Errorf("%s not registered", domain) 242 | } 243 | 244 | // Calculate the duration given the rent cost and the value. 245 | costPerSecond, err := c.RentCost(domain) 246 | if err != nil { 247 | return nil, errors.New("failed to obtain rent cost") 248 | } 249 | duration := new(big.Int).Div(opts.Value, costPerSecond) 250 | 251 | return c.Contract.Renew(opts, name, duration) 252 | } 253 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wealdtech/go-ens/v3 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.2 6 | 7 | require ( 8 | github.com/ethereum/go-ethereum v1.14.12 9 | github.com/ipfs/go-cid v0.4.1 10 | github.com/multiformats/go-multibase v0.2.0 11 | github.com/multiformats/go-multihash v0.2.3 12 | github.com/pkg/errors v0.9.1 13 | github.com/stretchr/testify v1.9.0 14 | github.com/wealdtech/go-multicodec v1.4.0 15 | github.com/wealdtech/go-string2eth v1.2.1 16 | golang.org/x/crypto v0.31.0 17 | golang.org/x/net v0.33.0 18 | ) 19 | 20 | require ( 21 | github.com/Microsoft/go-winio v0.6.2 // indirect 22 | github.com/bits-and-blooms/bitset v1.20.0 // indirect 23 | github.com/consensys/bavard v0.1.24 // indirect 24 | github.com/consensys/gnark-crypto v0.14.0 // indirect 25 | github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect 26 | github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect 27 | github.com/davecgh/go-spew v1.1.1 // indirect 28 | github.com/deckarep/golang-set/v2 v2.7.0 // indirect 29 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect 30 | github.com/ethereum/c-kzg-4844 v1.0.3 // indirect 31 | github.com/ethereum/go-verkle v0.2.2 // indirect 32 | github.com/fsnotify/fsnotify v1.8.0 // indirect 33 | github.com/go-ole/go-ole v1.3.0 // indirect 34 | github.com/google/uuid v1.6.0 // indirect 35 | github.com/gorilla/websocket v1.5.3 // indirect 36 | github.com/holiman/uint256 v1.3.2 // indirect 37 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect 38 | github.com/minio/sha256-simd v1.0.1 // indirect 39 | github.com/mmcloughlin/addchain v0.4.0 // indirect 40 | github.com/mr-tron/base58 v1.2.0 // indirect 41 | github.com/multiformats/go-base32 v0.1.0 // indirect 42 | github.com/multiformats/go-base36 v0.2.0 // indirect 43 | github.com/multiformats/go-varint v0.0.7 // indirect 44 | github.com/pmezard/go-difflib v1.0.0 // indirect 45 | github.com/shirou/gopsutil v3.21.11+incompatible // indirect 46 | github.com/spaolacci/murmur3 v1.1.0 // indirect 47 | github.com/supranational/blst v0.3.13 // indirect 48 | github.com/tklauser/go-sysconf v0.3.14 // indirect 49 | github.com/tklauser/numcpus v0.9.0 // indirect 50 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 51 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect 52 | golang.org/x/sync v0.10.0 // indirect 53 | golang.org/x/sys v0.28.0 // indirect 54 | golang.org/x/text v0.21.0 // indirect 55 | gopkg.in/yaml.v3 v3.0.1 // indirect 56 | lukechampine.com/blake3 v1.3.0 // indirect 57 | rsc.io/tmplfunc v0.0.3 // indirect 58 | ) 59 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= 2 | github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 3 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 4 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 5 | github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= 6 | github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= 7 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 8 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 9 | github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= 10 | github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 11 | github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= 12 | github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= 13 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 14 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 15 | github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= 16 | github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= 17 | github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= 18 | github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= 19 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= 20 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= 21 | github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= 22 | github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= 23 | github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= 24 | github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= 25 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= 26 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= 27 | github.com/consensys/bavard v0.1.24 h1:Lfe+bjYbpaoT7K5JTFoMi5wo9V4REGLvQQbHmatoN2I= 28 | github.com/consensys/bavard v0.1.24/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= 29 | github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= 30 | github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= 31 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 32 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 33 | github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= 34 | github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= 35 | github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= 36 | github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= 37 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 38 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 39 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/deckarep/golang-set/v2 v2.7.0 h1:gIloKvD7yH2oip4VLhsv3JyLLFnC0Y2mlusgcvJYW5k= 41 | github.com/deckarep/golang-set/v2 v2.7.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= 42 | github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= 43 | github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 44 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= 45 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 46 | github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs= 47 | github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= 48 | github.com/ethereum/go-ethereum v1.14.12 h1:8hl57x77HSUo+cXExrURjU/w1VhL+ShCTJrTwcCQSe4= 49 | github.com/ethereum/go-ethereum v1.14.12/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY= 50 | github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= 51 | github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= 52 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 53 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 54 | github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= 55 | github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= 56 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 57 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 58 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 59 | github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= 60 | github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= 61 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 62 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 63 | github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= 64 | github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 65 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 66 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 67 | github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= 68 | github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 69 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 70 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 71 | github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 72 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 73 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 74 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 75 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 76 | github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= 77 | github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= 78 | github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= 79 | github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= 80 | github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= 81 | github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= 82 | github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= 83 | github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= 84 | github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= 85 | github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= 86 | github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= 87 | github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 88 | github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= 89 | github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= 90 | github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= 91 | github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 92 | github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= 93 | github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= 94 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 95 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 96 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 97 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 98 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 99 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 100 | github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= 101 | github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= 102 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 103 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 104 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 105 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 106 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 107 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 108 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= 109 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 110 | github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 111 | github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 112 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= 113 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 114 | github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= 115 | github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= 116 | github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= 117 | github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= 118 | github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= 119 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 120 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 121 | github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 122 | github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 123 | github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= 124 | github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= 125 | github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= 126 | github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= 127 | github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= 128 | github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 129 | github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 130 | github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 131 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 132 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 133 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 134 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 135 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 136 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 137 | github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= 138 | github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 139 | github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= 140 | github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 141 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= 142 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 143 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= 144 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 145 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 146 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 147 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 148 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 149 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 150 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 151 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 152 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 153 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 154 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 155 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 156 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 157 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 158 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 159 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 160 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 161 | github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= 162 | github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= 163 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= 164 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 165 | github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= 166 | github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= 167 | github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= 168 | github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= 169 | github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= 170 | github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= 171 | github.com/wealdtech/go-multicodec v1.4.0 h1:iq5PgxwssxnXGGPTIK1srvt6U5bJwIp7k6kBrudIWxg= 172 | github.com/wealdtech/go-multicodec v1.4.0/go.mod h1:aedGMaTeYkIqi/KCPre1ho5rTb3hGpu/snBOS3GQLw4= 173 | github.com/wealdtech/go-string2eth v1.2.1 h1:u9sofvGFkp+uvTg4Nvsvy5xBaiw8AibGLLngfC4F76g= 174 | github.com/wealdtech/go-string2eth v1.2.1/go.mod h1:9uwxm18zKZfrReXrGIbdiRYJtbE91iGcj6TezKKEx80= 175 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 176 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 177 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 178 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 179 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 180 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 181 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= 182 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= 183 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 184 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 185 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 186 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 187 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 189 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 190 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 191 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 192 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 193 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 194 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 195 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 196 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 197 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 198 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 199 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 200 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 201 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 202 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 203 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 204 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 205 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 206 | lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= 207 | lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 208 | rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= 209 | rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= 210 | -------------------------------------------------------------------------------- /misc.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // DomainLevel calculates the level of the domain presented. 9 | // A top-level domain (e.g. 'eth') will be 0, a domain (e.g. 10 | // 'foo.eth') will be 1, a subdomain (e.g. 'bar.foo.eth' will 11 | // be 2, etc. 12 | func DomainLevel(name string) int { 13 | return len(strings.Split(name, ".")) - 1 14 | } 15 | 16 | // NormaliseDomain turns ENS domain in to normal form. 17 | func NormaliseDomain(domain string) (string, error) { 18 | wildcard := false 19 | if strings.HasPrefix(domain, "*.") { 20 | wildcard = true 21 | domain = domain[2:] 22 | } 23 | output, err := p.ToUnicode(domain) 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | // ToUnicode() removes leading periods. Replace them. 29 | if strings.HasPrefix(domain, ".") && !strings.HasPrefix(output, ".") { 30 | output = "." + output 31 | } 32 | 33 | // If we removed a wildcard then add it back. 34 | if wildcard { 35 | output = "*." + output 36 | } 37 | return output, nil 38 | } 39 | 40 | // NormaliseDomainStrict turns ENS domain in to normal form, using strict DNS 41 | // rules (e.g. no underscores). 42 | func NormaliseDomainStrict(domain string) (string, error) { 43 | wildcard := false 44 | if strings.HasPrefix(domain, "*.") { 45 | wildcard = true 46 | domain = domain[2:] 47 | } 48 | output, err := pStrict.ToUnicode(domain) 49 | if err != nil { 50 | return "", err 51 | } 52 | 53 | // ToUnicode() removes leading periods. Replace them. 54 | if strings.HasPrefix(domain, ".") && !strings.HasPrefix(output, ".") { 55 | output = "." + output 56 | } 57 | 58 | // If we removed a wildcard then add it back. 59 | if wildcard { 60 | output = "*." + output 61 | } 62 | return output, nil 63 | } 64 | 65 | // Tld obtains the top-level domain of an ENS name. 66 | func Tld(domain string) string { 67 | domain, err := NormaliseDomain(domain) 68 | if err != nil { 69 | return domain 70 | } 71 | tld, err := DomainPart(domain, -1) 72 | if err != nil { 73 | return domain 74 | } 75 | return tld 76 | } 77 | 78 | // Domain obtains the domain of an ENS name, including subdomains. It does this 79 | // by removing everything up to and including the first period. 80 | // For example, 'eth' will return ” 81 | // 82 | // 'foo.eth' will return 'eth' 83 | // 'bar.foo.eth' will return 'foo.eth' 84 | func Domain(domain string) string { 85 | if idx := strings.IndexByte(domain, '.'); idx >= 0 { 86 | return domain[idx+1:] 87 | } 88 | return "" 89 | } 90 | 91 | // DomainPart obtains a part of a name 92 | // Positive parts start at the lowest-level of the domain and work towards the 93 | // top-level domain. Negative parts start at the top-level domain and work 94 | // towards the lowest-level domain. 95 | // For example, with a domain bar.foo.com the following parts will be returned: 96 | // Number | part 97 | // 98 | // 1 | bar 99 | // 2 | foo 100 | // 3 | com 101 | // -1 | com 102 | // -2 | foo 103 | // -3 | bar 104 | func DomainPart(domain string, part int) (string, error) { 105 | if part == 0 { 106 | return "", fmt.Errorf("invalid part") 107 | } 108 | domain, err := NormaliseDomain(domain) 109 | if err != nil { 110 | return "", err 111 | } 112 | parts := strings.Split(domain, ".") 113 | if len(parts) < abs(part) { 114 | return "", fmt.Errorf("not enough parts") 115 | } 116 | if part < 0 { 117 | return parts[len(parts)+part], nil 118 | } 119 | return parts[part-1], nil 120 | } 121 | 122 | func abs(x int) int { 123 | if x < 0 { 124 | return -x 125 | } 126 | return x 127 | } 128 | 129 | // UnqualifiedName strips the root from the domain and ensures the result is 130 | // suitable as a name. 131 | func UnqualifiedName(domain string, root string) (string, error) { 132 | suffix := fmt.Sprintf(".%s", root) 133 | name := strings.TrimSuffix(domain, suffix) 134 | if strings.Contains(name, ".") { 135 | return "", fmt.Errorf("%s not a direct child of %s", domain, root) 136 | } 137 | return name, nil 138 | } 139 | -------------------------------------------------------------------------------- /misc_test.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestNormaliseDomain(t *testing.T) { 13 | tests := []struct { 14 | input string 15 | output string 16 | err error 17 | }{ 18 | {"", "", nil}, 19 | {".", ".", nil}, 20 | {"eth", "eth", nil}, 21 | {"ETH", "eth", nil}, 22 | {".eth", ".eth", nil}, 23 | {".eth.", ".eth.", nil}, 24 | {"wealdtech.eth", "wealdtech.eth", nil}, 25 | {".wealdtech.eth", ".wealdtech.eth", nil}, 26 | {"subdomain.wealdtech.eth", "subdomain.wealdtech.eth", nil}, 27 | {"*.wealdtech.eth", "*.wealdtech.eth", nil}, 28 | {"omg.thetoken.eth", "omg.thetoken.eth", nil}, 29 | {"_underscore.thetoken.eth", "_underscore.thetoken.eth", nil}, 30 | {"點看.eth", "點看.eth", nil}, 31 | } 32 | 33 | for _, tt := range tests { 34 | t.Run(tt.input, func(t *testing.T) { 35 | result, err := NormaliseDomain(tt.input) 36 | if tt.err != nil { 37 | if err == nil { 38 | t.Fatalf("missing expected error") 39 | } 40 | if tt.err.Error() != err.Error() { 41 | t.Errorf("unexpected error value %v", err) 42 | } 43 | } else { 44 | if err != nil { 45 | t.Fatalf("unexpected error %v", err) 46 | } 47 | if tt.output != result { 48 | t.Errorf("%v => %v (expected %v)", tt.input, result, tt.output) 49 | } 50 | } 51 | }) 52 | } 53 | } 54 | 55 | func TestNormaliseDomainStrict(t *testing.T) { 56 | tests := []struct { 57 | input string 58 | output string 59 | err error 60 | }{ 61 | {"", "", nil}, 62 | {".", ".", nil}, 63 | {"eth", "eth", nil}, 64 | {"ETH", "eth", nil}, 65 | {".eth", ".eth", nil}, 66 | {".eth.", ".eth.", nil}, 67 | {"wealdtech.eth", "wealdtech.eth", nil}, 68 | {".wealdtech.eth", ".wealdtech.eth", nil}, 69 | {"subdomain.wealdtech.eth", "subdomain.wealdtech.eth", nil}, 70 | {"*.wealdtech.eth", "*.wealdtech.eth", nil}, 71 | {"omg.thetoken.eth", "omg.thetoken.eth", nil}, 72 | {"_underscore.thetoken.eth", "", errors.New("idna: disallowed rune U+005F")}, 73 | {"點看.eth", "點看.eth", nil}, 74 | } 75 | 76 | for _, tt := range tests { 77 | t.Run(tt.input, func(t *testing.T) { 78 | result, err := NormaliseDomainStrict(tt.input) 79 | if tt.err != nil { 80 | if err == nil { 81 | t.Fatalf("missing expected error") 82 | } 83 | if tt.err.Error() != err.Error() { 84 | t.Errorf("unexpected error value %v", err) 85 | } 86 | } else { 87 | if err != nil { 88 | t.Fatalf("unexpected error %v", err) 89 | } 90 | if tt.output != result { 91 | t.Errorf("%v => %v (expected %v)", tt.input, result, tt.output) 92 | } 93 | } 94 | }) 95 | } 96 | } 97 | 98 | func TestTld(t *testing.T) { 99 | tests := []struct { 100 | input string 101 | output string 102 | }{ 103 | {"", ""}, 104 | {".", ""}, 105 | {"eth", "eth"}, 106 | {"ETH", "eth"}, 107 | {".eth", "eth"}, 108 | {"wealdtech.eth", "eth"}, 109 | {".wealdtech.eth", "eth"}, 110 | {"subdomain.wealdtech.eth", "eth"}, 111 | } 112 | 113 | for _, tt := range tests { 114 | result := Tld(tt.input) 115 | if tt.output != result { 116 | t.Errorf("Failure: %v => %v (expected %v)\n", tt.input, result, tt.output) 117 | } 118 | } 119 | } 120 | 121 | func TestDomainPart(t *testing.T) { 122 | tests := []struct { 123 | input string 124 | part int 125 | output string 126 | err bool 127 | }{ 128 | {"", 1, "", false}, 129 | {"", 2, "", true}, 130 | {"", -1, "", false}, 131 | {"", -2, "", true}, 132 | {".", 1, "", false}, 133 | {".", 2, "", false}, 134 | {".", 3, "", true}, 135 | {".", -1, "", false}, 136 | {".", -2, "", false}, 137 | {".", -3, "", true}, 138 | {"ETH", 1, "eth", false}, 139 | {"ETH", 2, "", true}, 140 | {"ETH", -1, "eth", false}, 141 | {"ETH", -2, "", true}, 142 | {".ETH", 1, "", false}, 143 | {".ETH", 2, "eth", false}, 144 | {".ETH", 3, "", true}, 145 | {".ETH", -1, "eth", false}, 146 | {".ETH", -2, "", false}, 147 | {".ETH", -3, "", true}, 148 | {"wealdtech.eth", 1, "wealdtech", false}, 149 | {"wealdtech.eth", 2, "eth", false}, 150 | {"wealdtech.eth", 3, "", true}, 151 | {"wealdtech.eth", -1, "eth", false}, 152 | {"wealdtech.eth", -2, "wealdtech", false}, 153 | {"wealdtech.eth", -3, "", true}, 154 | {".wealdtech.eth", 1, "", false}, 155 | {".wealdtech.eth", 2, "wealdtech", false}, 156 | {".wealdtech.eth", 3, "eth", false}, 157 | {".wealdtech.eth", 4, "", true}, 158 | {".wealdtech.eth", -1, "eth", false}, 159 | {".wealdtech.eth", -2, "wealdtech", false}, 160 | {".wealdtech.eth", -3, "", false}, 161 | {".wealdtech.eth", -4, "", true}, 162 | {"subdomain.wealdtech.eth", 1, "subdomain", false}, 163 | {"subdomain.wealdtech.eth", 2, "wealdtech", false}, 164 | {"subdomain.wealdtech.eth", 3, "eth", false}, 165 | {"subdomain.wealdtech.eth", 4, "", true}, 166 | {"subdomain.wealdtech.eth", -1, "eth", false}, 167 | {"subdomain.wealdtech.eth", -2, "wealdtech", false}, 168 | {"subdomain.wealdtech.eth", -3, "subdomain", false}, 169 | {"subdomain.wealdtech.eth", -4, "", true}, 170 | {"a.b.c", 1, "a", false}, 171 | {"a.b.c", 2, "b", false}, 172 | {"a.b.c", 3, "c", false}, 173 | {"a.b.c", 4, "", true}, 174 | {"a.b.c", -1, "c", false}, 175 | {"a.b.c", -2, "b", false}, 176 | {"a.b.c", -3, "a", false}, 177 | {"a.b.c", -4, "", true}, 178 | } 179 | 180 | for _, tt := range tests { 181 | result, err := DomainPart(tt.input, tt.part) 182 | if err != nil && !tt.err { 183 | t.Errorf("Failure: %v, %v => error (unexpected)\n", tt.input, tt.part) 184 | } 185 | if err == nil && tt.err { 186 | t.Errorf("Failure: %v, %v => no error (unexpected)\n", tt.input, tt.part) 187 | } 188 | if tt.output != result { 189 | t.Errorf("Failure: %v, %v => %v (expected %v)\n", tt.input, tt.part, result, tt.output) 190 | } 191 | } 192 | } 193 | 194 | func TestUnqualifiedName(t *testing.T) { 195 | tests := []struct { 196 | domain string 197 | root string 198 | name string 199 | err error 200 | }{ 201 | { 202 | domain: "", 203 | root: "", 204 | name: "", 205 | }, 206 | { 207 | domain: "wealdtech.eth", 208 | root: "eth", 209 | name: "wealdtech", 210 | }, 211 | } 212 | 213 | for i, test := range tests { 214 | name, err := UnqualifiedName(test.domain, test.root) 215 | if test.err != nil { 216 | assert.Equal(t, test.err, err, fmt.Sprintf("Incorrect error at test %d", i)) 217 | } else { 218 | require.Nil(t, err, fmt.Sprintf("Unexpected error at test %d", i)) 219 | assert.Equal(t, test.name, name, fmt.Sprintf("Incorrect result at test %d", i)) 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /name.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "crypto/rand" 19 | "errors" 20 | "fmt" 21 | "math/big" 22 | "time" 23 | 24 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 25 | "github.com/ethereum/go-ethereum/common" 26 | "github.com/ethereum/go-ethereum/core/types" 27 | ) 28 | 29 | // Name represents an ENS name, for example 'foo.bar.eth'. 30 | type Name struct { 31 | backend bind.ContractBackend 32 | // Name is the fully-qualified name of an ENS domain e.g. foo.bar.eth 33 | Name string 34 | // Domain is the domain of an ENS domain e.g. bar.eth 35 | Domain string 36 | // Label is the name part of an ENS domain e.g. foo 37 | Label string 38 | // Contracts 39 | registry *Registry 40 | registrar *BaseRegistrar 41 | controller *ETHController 42 | } 43 | 44 | // NewName creates an ENS name structure. 45 | // Note that this does not create the name on-chain. 46 | func NewName(backend bind.ContractBackend, name string) (*Name, error) { 47 | name, err := NormaliseDomain(name) 48 | if err != nil { 49 | return nil, err 50 | } 51 | domain := Domain(name) 52 | label, err := DomainPart(name, 1) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | registry, err := NewRegistry(backend) 58 | if err != nil { 59 | return nil, err 60 | } 61 | registrar, err := NewBaseRegistrar(backend, domain) 62 | if err != nil { 63 | return nil, err 64 | } 65 | controller, err := NewETHController(backend, domain) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | isValid, err := controller.IsValid(name) 71 | if err != nil { 72 | return nil, err 73 | } 74 | if !isValid { 75 | return nil, errors.New("name is not valid according to the rules of the registrar (too short, invalid characters, etc.)") 76 | } 77 | 78 | return &Name{ 79 | backend: backend, 80 | Name: name, 81 | Domain: domain, 82 | Label: label, 83 | registry: registry, 84 | registrar: registrar, 85 | controller: controller, 86 | }, nil 87 | } 88 | 89 | // IsRegistered returns true if the name is registered in the registrar. 90 | func (n *Name) IsRegistered() (bool, error) { 91 | registrant, err := n.Registrant() 92 | if err != nil { 93 | return false, err 94 | } 95 | return registrant != UnknownAddress, nil 96 | } 97 | 98 | // ExtendRegistration sends a transaction that extends the registration of the name. 99 | func (n *Name) ExtendRegistration(opts *bind.TransactOpts) (*types.Transaction, error) { 100 | isRegistered, err := n.IsRegistered() 101 | if err != nil { 102 | return nil, err 103 | } 104 | if !isRegistered { 105 | return nil, errors.New("name is not registered") 106 | } 107 | 108 | rentCost, err := n.RentCost() 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | if opts.Value == nil || opts.Value.Cmp(rentCost) < 0 { 114 | return nil, errors.New("not enough funds to extend the registration") 115 | } 116 | 117 | return n.controller.Renew(opts, n.Name) 118 | } 119 | 120 | // RegistrationInterval obtains the minimum interval between commit and reveal 121 | // when registering this name. 122 | func (n *Name) RegistrationInterval() (time.Duration, error) { 123 | interval, err := n.controller.MinCommitmentInterval() 124 | if err != nil { 125 | return time.Duration(0), err 126 | } 127 | return time.Duration(interval.Int64()) * time.Second, nil 128 | } 129 | 130 | // RegisterStageOne sends a transaction that starts the registration process. 131 | func (n *Name) RegisterStageOne(registrant common.Address, opts *bind.TransactOpts) (*types.Transaction, [32]byte, error) { 132 | var secret [32]byte 133 | _, err := rand.Read(secret[:]) 134 | if err != nil { 135 | return nil, secret, err 136 | } 137 | 138 | isRegistered, err := n.IsRegistered() 139 | if err != nil { 140 | return nil, secret, err 141 | } 142 | if isRegistered { 143 | return nil, secret, errors.New("name is already registered") 144 | } 145 | 146 | signedTx, err := n.controller.Commit(opts, n.Label, registrant, secret) 147 | return signedTx, secret, err 148 | } 149 | 150 | // RegisterStageTwo sends a transaction that completes the registration process. 151 | // The registrant must be the same as supplied in RegisterStageOne. 152 | // The secret is that returned by RegisterStageOne. 153 | // At least RegistrationInterval() time must have passed since the stage one 154 | // transaction was mined for this to work. 155 | func (n *Name) RegisterStageTwo(registrant common.Address, secret [32]byte, opts *bind.TransactOpts) (*types.Transaction, error) { 156 | commitTS, err := n.controller.CommitmentTime(n.Label, registrant, secret) 157 | if err != nil { 158 | return nil, err 159 | } 160 | if commitTS.Cmp(big.NewInt(0)) == 0 { 161 | return nil, errors.New("stage 2 attempted prior to successful stage 1 transaction") 162 | } 163 | commit := time.Unix(commitTS.Int64(), 0) 164 | 165 | minCommitIntervalTS, err := n.controller.MinCommitmentInterval() 166 | if err != nil { 167 | return nil, err 168 | } 169 | minCommitInterval := time.Duration(minCommitIntervalTS.Int64()) * time.Second 170 | minStageTwoTime := commit.Add(minCommitInterval) 171 | if time.Now().Before(minStageTwoTime) { 172 | return nil, errors.New("too early to send second transaction") 173 | } 174 | 175 | maxCommitIntervalTS, err := n.controller.MaxCommitmentInterval() 176 | if err != nil { 177 | return nil, err 178 | } 179 | maxCommitInterval := time.Duration(maxCommitIntervalTS.Int64()) * time.Second 180 | maxStageTwoTime := commit.Add(maxCommitInterval) 181 | if time.Now().After(maxStageTwoTime) { 182 | return nil, errors.New("too late to send second transaction") 183 | } 184 | 185 | return n.controller.Reveal(opts, n.Label, registrant, secret) 186 | } 187 | 188 | // Expires obtain the time at which the registration for this name expires. 189 | func (n *Name) Expires() (time.Time, error) { 190 | expiryTS, err := n.registrar.Expiry(n.Label) 191 | if err != nil { 192 | return time.Unix(0, 0), err 193 | } 194 | 195 | if expiryTS.Int64() == 0 { 196 | return time.Unix(0, 0), errors.New("not registered") 197 | } 198 | 199 | return time.Unix(expiryTS.Int64(), 0), nil 200 | } 201 | 202 | // Controller obtains the controller for this name. 203 | // The controller can carry out operations on the name such as setting 204 | // records, but cannot transfer ultimate ownership of the name. 205 | func (n *Name) Controller() (common.Address, error) { 206 | return n.registry.Owner(n.Name) 207 | } 208 | 209 | // SetController sets the controller for this name. 210 | // The controller can carry out operations on the name such as setting 211 | // records, but cannot transfer ultimate ownership of the name. 212 | func (n *Name) SetController(controller common.Address, opts *bind.TransactOpts) (*types.Transaction, error) { 213 | // Are we the current controller? 214 | curController, err := n.Controller() 215 | if err != nil { 216 | return nil, err 217 | } 218 | if curController == opts.From { 219 | return n.registry.SetOwner(opts, n.Name, controller) 220 | } 221 | 222 | // Perhaps we are the registrant. 223 | registrant, err := n.Registrant() 224 | if err != nil { 225 | return nil, err 226 | } 227 | // Are we actually trying to reclaim? 228 | if registrant == opts.From && opts.From == controller { 229 | return n.Reclaim(opts) 230 | } 231 | 232 | return nil, errors.New("not authorised to change the controller") 233 | } 234 | 235 | // Reclaim reclaims controller rights by the registrant. 236 | func (n *Name) Reclaim(opts *bind.TransactOpts) (*types.Transaction, error) { 237 | // Ensure the we are the registrant. 238 | registrant, err := n.Registrant() 239 | if err != nil { 240 | return nil, err 241 | } 242 | if registrant != opts.From { 243 | return nil, errors.New("not the registrant") 244 | } 245 | return n.registrar.Reclaim(opts, n.Name, registrant) 246 | } 247 | 248 | // Registrant obtains the registrant for this name. 249 | func (n *Name) Registrant() (common.Address, error) { 250 | return n.registrar.Owner(n.Label) 251 | } 252 | 253 | // Transfer transfers the registration of this name to a new registrant. 254 | func (n *Name) Transfer(registrant common.Address, opts *bind.TransactOpts) (*types.Transaction, error) { 255 | // Ensure the we are the registrant. 256 | currentRegistrant, err := n.Registrant() 257 | if err != nil { 258 | return nil, err 259 | } 260 | if currentRegistrant != opts.From { 261 | return nil, errors.New("not the current registrant") 262 | } 263 | return n.registrar.SetOwner(opts, n.Label, registrant) 264 | } 265 | 266 | // RentCost returns the cost of rent in Wei-per-second. 267 | func (n *Name) RentCost() (*big.Int, error) { 268 | return n.controller.RentCost(n.Label) 269 | } 270 | 271 | // CreateSubdomain creates a subdomain on the name. 272 | func (n *Name) CreateSubdomain(label string, controller common.Address, opts *bind.TransactOpts) (*types.Transaction, error) { 273 | // Confirm the subdomain does not already exist. 274 | fqdn := fmt.Sprintf("%s.%s", label, n.Name) 275 | subdomainController, err := n.registry.Owner(fqdn) 276 | if err != nil { 277 | return nil, err 278 | } 279 | if subdomainController != UnknownAddress { 280 | return nil, errors.New("that subdomain already exists") 281 | } 282 | 283 | return n.registry.SetSubdomainOwner(opts, n.Name, label, controller) 284 | } 285 | 286 | // ResolverAddress fetches the address of the resolver contract for the name. 287 | func (n *Name) ResolverAddress() (common.Address, error) { 288 | return n.registry.ResolverAddress(n.Name) 289 | } 290 | 291 | // SetResolverAddress sets the resolver contract address for the name. 292 | func (n *Name) SetResolverAddress(address common.Address, opts *bind.TransactOpts) (*types.Transaction, error) { 293 | return n.registry.SetResolver(opts, n.Name, address) 294 | } 295 | 296 | // Address fetches the address of the name for a given coin type. 297 | // Coin types are defined at https://github.com/satoshilabs/slips/blob/master/slip-0044.md 298 | func (n *Name) Address(coinType uint64) ([]byte, error) { 299 | resolver, err := NewResolver(n.backend, n.Name) 300 | if err != nil { 301 | return nil, err 302 | } 303 | return resolver.MultiAddress(coinType) 304 | } 305 | 306 | // SetAddress sets the address of the name for a given coin type. 307 | // Coin types are defined at https://github.com/satoshilabs/slips/blob/master/slip-0044.md 308 | func (n *Name) SetAddress(coinType uint64, address []byte, opts *bind.TransactOpts) (*types.Transaction, error) { 309 | resolver, err := NewResolver(n.backend, n.Name) 310 | if err != nil { 311 | return nil, err 312 | } 313 | return resolver.SetMultiAddress(opts, coinType, address) 314 | } 315 | -------------------------------------------------------------------------------- /namehash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 - 2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "strings" 19 | 20 | "github.com/pkg/errors" 21 | "golang.org/x/net/idna" 22 | 23 | "golang.org/x/crypto/sha3" 24 | ) 25 | 26 | var ( 27 | p = idna.New(idna.MapForLookup(), idna.ValidateLabels(false), idna.CheckHyphens(false), idna.StrictDomainName(false), idna.Transitional(false)) 28 | pStrict = idna.New(idna.MapForLookup(), idna.ValidateLabels(false), idna.CheckHyphens(false), idna.StrictDomainName(true), idna.Transitional(false)) 29 | ) 30 | 31 | // Normalize normalizes a name according to the ENS rules. 32 | func Normalize(input string) (string, error) { 33 | output, err := p.ToUnicode(input) 34 | if err != nil { 35 | return "", errors.Wrap(err, "failed to convert to standard unicode") 36 | } 37 | // If the name started with a period then ToUnicode() removes it, but we want to keep it. 38 | if strings.HasPrefix(input, ".") && !strings.HasPrefix(output, ".") { 39 | output = "." + output 40 | } 41 | 42 | return output, nil 43 | } 44 | 45 | // LabelHash generates a simple hash for a piece of a name. 46 | func LabelHash(label string) ([32]byte, error) { 47 | var hash [32]byte 48 | 49 | normalizedLabel, err := Normalize(label) 50 | if err != nil { 51 | return [32]byte{}, err 52 | } 53 | 54 | sha := sha3.NewLegacyKeccak256() 55 | if _, err = sha.Write([]byte(normalizedLabel)); err != nil { 56 | return [32]byte{}, errors.Wrap(err, "failed to write hash") 57 | } 58 | sha.Sum(hash[:0]) 59 | 60 | return hash, nil 61 | } 62 | 63 | // NameHash generates a hash from a name that can be used to 64 | // look up the name in ENS. 65 | func NameHash(name string) ([32]byte, error) { 66 | var hash [32]byte 67 | 68 | if name == "" { 69 | return hash, nil 70 | } 71 | 72 | normalizedName, err := Normalize(name) 73 | if err != nil { 74 | return [32]byte{}, err 75 | } 76 | parts := strings.Split(normalizedName, ".") 77 | for i := len(parts) - 1; i >= 0; i-- { 78 | if hash, err = nameHashPart(hash, parts[i]); err != nil { 79 | return [32]byte{}, err 80 | } 81 | } 82 | 83 | return hash, nil 84 | } 85 | 86 | func nameHashPart(currentHash [32]byte, name string) ([32]byte, error) { 87 | var hash [32]byte 88 | 89 | sha := sha3.NewLegacyKeccak256() 90 | if _, err := sha.Write(currentHash[:]); err != nil { 91 | return [32]byte{}, errors.Wrap(err, "failed to write hash") 92 | } 93 | nameSha := sha3.NewLegacyKeccak256() 94 | if _, err := nameSha.Write([]byte(name)); err != nil { 95 | return [32]byte{}, errors.Wrap(err, "failed to write hash") 96 | } 97 | nameHash := nameSha.Sum(nil) 98 | if _, err := sha.Write(nameHash); err != nil { 99 | return [32]byte{}, errors.Wrap(err, "failed to write hash") 100 | } 101 | sha.Sum(hash[:0]) 102 | 103 | return hash, nil 104 | } 105 | -------------------------------------------------------------------------------- /namehash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Weald Technology Trading 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "encoding/hex" 19 | "testing" 20 | ) 21 | 22 | func TestNameHash(t *testing.T) { 23 | tests := []struct { 24 | input string 25 | output string 26 | err error 27 | }{ 28 | {"", "0000000000000000000000000000000000000000000000000000000000000000", nil}, 29 | {"eth", "93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae", nil}, 30 | {"Eth", "93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae", nil}, 31 | {".eth", "8cc9f31a5e7af6381efc751d98d289e3f3589f1b6f19b9b989ace1788b939cf7", nil}, 32 | {"resolver.eth", "fdd5d5de6dd63db72bbc2d487944ba13bf775b50a80805fe6fcaba9b0fba88f5", nil}, 33 | {"foo.eth", "de9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f", nil}, 34 | {"Foo.eth", "de9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f", nil}, 35 | {"foo..eth", "4143a5b2f547838d3b49982e3f2ec6a26415274e5b9c3ffeb21971bbfdfaa052", nil}, 36 | {"bar.foo.eth", "275ae88e7263cdce5ab6cf296cdd6253f5e385353fe39cfff2dd4a2b14551cf3", nil}, 37 | {"Bar.foo.eth", "275ae88e7263cdce5ab6cf296cdd6253f5e385353fe39cfff2dd4a2b14551cf3", nil}, 38 | {"addr.reverse", "91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2", nil}, 39 | {"🚣‍♂️.eth", "37f681401d88093779de0c910da1f9437759c2181f61b5dbc9e3ef0d9f192513", nil}, 40 | {"vitalik.eth", "ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835", nil}, 41 | {"nIcK.eTh", "05a67c0ee82964c4f7394cdd47fee7f4d9503a23c09c38341779ea012afe6e00", nil}, 42 | {"brantly.cash", "3cb8d24c572869f7ec5ec5e882243b4790de146ac11267f58fd856d290b593db", nil}, 43 | {"🏴‍☠.art", "4696ec532b8c8bb20dcfa6ae6da710422611ea03fc77aebc0284d0f12c93f768", nil}, 44 | {"öbb.eth", "2774094517aaa884fc7183cf681150529b9acc7c9e7b71cf3a470200135a20b4", nil}, 45 | {"Öbb.eth", "2774094517aaa884fc7183cf681150529b9acc7c9e7b71cf3a470200135a20b4", nil}, 46 | {"💷pound.eth", "2df37f43de8b5ab870370770e78e4503ea1199e46c16db7a4a9320732dbb42bc", nil}, 47 | {"firstwrappedname.eth", "c44eec7fb870ae46d4ef4392d33fbbbdc164e7817a86289a1fe30e5f4d98ae85", nil}, 48 | {"A.™️.Ю", "f48b2941db0e23087a8922f81c0b7dd1b83fa06657080fd28986f9a19752a093", nil}, 49 | {"Ⅷ.eth", "a468899f828f5d80c85c0e538018ba6516713a06aa6d92e8482ee91886630ae9", nil}, 50 | {"⨌.eth", "f0b6090714f4ab523b1c574077018fe49dc934f9334d092e412b3802898b2cee", nil}, 51 | {"-test.test-.t-e--s---t", "0d26fe02b1eb3fdcd7db77a432c60b20d2d8c4ef9012ef06ad3ae7bf12adb225", nil}, 52 | {"te--st.eth", "bea7eca33545aacbf3d6f1512c119aa7f04506d61354001e7af31ba73374b2c3", nil}, 53 | {"__ab.eth", "b3edf1f0b8dc6168b65d034c76b5670813514ac8867115e63b51eb496f3791a0", nil}, 54 | {"a_b.eth", "b99946eaeb8a557bc6329faf95d1b885ba608393109cb9609440acc8e6761c4f", nil}, 55 | {"ß.eth", "86262f2349fb651f652e87141e6db5856cd6e40f92d0b9dc486aaf8f9c509cff", nil}, 56 | {"💩💩💩.eth", "a74feb0e5fa5606d3e650275e3bb3873b006a10d558389d3ce2abbe681fcfc8e", nil}, 57 | {"آٍَ.ऩँं", "a1e3d72c39aec7ebcf62906994da7adc76b8a3ffa27e52b838abb24569610146", nil}, 58 | {"פעילותהבינאום.eth", "7900040d82be5a569289b0f7b30266d796e1ded13293557ed061b39367357a62", nil}, 59 | {"ஶ்ரீ.ஸ்ரீ", "ef5f28b8adff4b9b86b67c40792ef314ecd92a2123a72185b1411835cce5c110", nil}, 60 | {"🇸🇦سلمان.eth", "3484f75c4dbf69503310126dbfc02eec224eab46851de2288092d58785670808", nil}, 61 | {"0a〇.黑a8", "d7bc21f0cb86cafe27ebafe8e788b1c63b736467c65611110575f385a4d01e1f", nil}, 62 | {"apple.дррӏе.аррӏе.aррӏе", "a5428a7fc57a099d7926ac7fa30169933df167acf3d8fba6f5830e6cf7442b31", nil}, 63 | {"۰۱۲۳۷۸۹.eth", "430bc5e65fd3122d45c00dbce4e8e1b51be675794e548f92558dc4cecafd6418", nil}, 64 | {"𓀀𓀁𓀂.eth", "36f75325f4013011b96014606224f03c77366d89caf480a1060d7453edaf5c98", nil}, 65 | {"𓆏➡🐸️.eth", "f2663423c7aadf794d6f84addcfee1f877458d86c20740954482094a20985228", nil}, 66 | {"ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ.eth", "254a068876bdac64ad35425816bf444fbe9a26ec19287f47e663f928727ff69c", nil}, 67 | {"trish🫧.eth", "9a6aa3aaa97ed3b694265d4b0e776b2e8fad34b8869199b1a062e3cc0ee0d41d", nil}, 68 | } 69 | 70 | for _, tt := range tests { 71 | result, err := NameHash(tt.input) 72 | if tt.err == nil { 73 | if err != nil { 74 | t.Fatalf("unexpected error %v", err) 75 | } 76 | if tt.output != hex.EncodeToString(result[:]) { 77 | t.Errorf("Failure: %v => %v (expected %v)\n", tt.input, hex.EncodeToString(result[:]), tt.output) 78 | } 79 | } else { 80 | if err == nil { 81 | t.Fatalf("missing expected error") 82 | } 83 | if tt.err.Error() != err.Error() { 84 | t.Errorf("unexpected error value %v", err) 85 | } 86 | } 87 | } 88 | } 89 | 90 | func TestLabelHash(t *testing.T) { 91 | tests := []struct { 92 | input string 93 | output string 94 | err error 95 | }{ 96 | {"", "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", nil}, 97 | {"eth", "4f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0", nil}, 98 | {"foo", "41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d", nil}, 99 | } 100 | 101 | for _, tt := range tests { 102 | output, err := LabelHash(tt.input) 103 | if tt.err == nil { 104 | if err != nil { 105 | t.Fatalf("unexpected error %v", err) 106 | } 107 | if tt.output != hex.EncodeToString(output[:]) { 108 | t.Errorf("Failure: %v => %v (expected %v)\n", tt.input, hex.EncodeToString(output[:]), tt.output) 109 | } 110 | } else { 111 | if err == nil { 112 | t.Fatalf("missing expected error") 113 | } 114 | if tt.err.Error() != err.Error() { 115 | t.Errorf("unexpected error value %v", err) 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /registrar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 21 | "github.com/ethereum/go-ethereum/common" 22 | ) 23 | 24 | // RegistrarContractAddress obtains the registrar contract address for a given domain. 25 | func RegistrarContractAddress(backend bind.ContractBackend, domain string) (common.Address, error) { 26 | // Obtain a registry contract. 27 | registry, err := NewRegistry(backend) 28 | if err != nil { 29 | return UnknownAddress, err 30 | } 31 | 32 | // Obtain the registrar address from the registry. 33 | address, err := registry.Owner(domain) 34 | if address == UnknownAddress { 35 | err = fmt.Errorf("no registrar for %s", domain) 36 | } 37 | 38 | return address, err 39 | } 40 | -------------------------------------------------------------------------------- /registry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Weald Technology Trading 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "errors" 19 | "math/big" 20 | 21 | "github.com/ethereum/go-ethereum/accounts" 22 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 23 | "github.com/ethereum/go-ethereum/common" 24 | "github.com/ethereum/go-ethereum/core/types" 25 | "github.com/wealdtech/go-ens/v3/contracts/auctionregistrar" 26 | "github.com/wealdtech/go-ens/v3/contracts/registry" 27 | "github.com/wealdtech/go-ens/v3/util" 28 | ) 29 | 30 | // Registry is the structure for the registry contract. 31 | type Registry struct { 32 | backend bind.ContractBackend 33 | Contract *registry.Contract 34 | ContractAddr common.Address 35 | } 36 | 37 | // NewRegistry obtains the ENS registry. 38 | func NewRegistry(backend bind.ContractBackend) (*Registry, error) { 39 | address, err := RegistryContractAddress(backend) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return NewRegistryAt(backend, address) 44 | } 45 | 46 | // NewRegistryAt obtains the ENS registry at a given address. 47 | func NewRegistryAt(backend bind.ContractBackend, address common.Address) (*Registry, error) { 48 | contract, err := registry.NewContract(address, backend) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return &Registry{ 53 | backend: backend, 54 | Contract: contract, 55 | ContractAddr: address, 56 | }, nil 57 | } 58 | 59 | // Owner returns the address of the owner of a name. 60 | func (r *Registry) Owner(name string) (common.Address, error) { 61 | nameHash, err := NameHash(name) 62 | if err != nil { 63 | return UnknownAddress, err 64 | } 65 | return r.Contract.Owner(nil, nameHash) 66 | } 67 | 68 | // ResolverAddress returns the address of the resolver for a name. 69 | func (r *Registry) ResolverAddress(name string) (common.Address, error) { 70 | nameHash, err := NameHash(name) 71 | if err != nil { 72 | return UnknownAddress, err 73 | } 74 | return r.Contract.Resolver(nil, nameHash) 75 | } 76 | 77 | // SetResolver sets the resolver for a name. 78 | func (r *Registry) SetResolver(opts *bind.TransactOpts, name string, address common.Address) (*types.Transaction, error) { 79 | nameHash, err := NameHash(name) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return r.Contract.SetResolver(opts, nameHash, address) 84 | } 85 | 86 | // Resolver returns the resolver for a name. 87 | func (r *Registry) Resolver(name string) (*Resolver, error) { 88 | address, err := r.ResolverAddress(name) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return NewResolverAt(r.backend, name, address) 93 | } 94 | 95 | // SetOwner sets the ownership of a domain. 96 | func (r *Registry) SetOwner(opts *bind.TransactOpts, name string, address common.Address) (*types.Transaction, error) { 97 | nameHash, err := NameHash(name) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return r.Contract.SetOwner(opts, nameHash, address) 102 | } 103 | 104 | // SetSubdomainOwner sets the ownership of a subdomain, potentially creating it in the process. 105 | func (r *Registry) SetSubdomainOwner(opts *bind.TransactOpts, name string, subname string, address common.Address) (*types.Transaction, error) { 106 | nameHash, err := NameHash(name) 107 | if err != nil { 108 | return nil, err 109 | } 110 | labelHash, err := LabelHash(subname) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return r.Contract.SetSubnodeOwner(opts, nameHash, labelHash, address) 115 | } 116 | 117 | // RegistryContractAddress obtains the address of the registry contract for a chain. 118 | // This is (currently) the same for all chains. 119 | func RegistryContractAddress(_ bind.ContractBackend) (common.Address, error) { 120 | // Instantiate the registry contract. The same for all chains. 121 | return common.HexToAddress("00000000000C2E074eC69A0dFb2997BA6C7d2e1e"), nil 122 | } 123 | 124 | // RegistryContractFromRegistrar obtains the registry contract given an 125 | // existing registrar contract. 126 | func RegistryContractFromRegistrar(backend bind.ContractBackend, registrar *auctionregistrar.Contract) (*registry.Contract, error) { 127 | if registrar == nil { 128 | return nil, errors.New("no registrar contract") 129 | } 130 | registryAddress, err := registrar.Ens(nil) 131 | if err != nil { 132 | return nil, err 133 | } 134 | return registry.NewContract(registryAddress, backend) 135 | } 136 | 137 | // SetResolver sets the resolver for a name. 138 | func SetResolver(session *registry.ContractSession, name string, resolverAddr *common.Address) (*types.Transaction, error) { 139 | nameHash, err := NameHash(name) 140 | if err != nil { 141 | return nil, err 142 | } 143 | return session.SetResolver(nameHash, *resolverAddr) 144 | } 145 | 146 | // SetSubdomainOwner sets the owner for a subdomain of a name. 147 | func SetSubdomainOwner(session *registry.ContractSession, name string, subdomain string, ownerAddr *common.Address) (*types.Transaction, error) { 148 | nameHash, err := NameHash(name) 149 | if err != nil { 150 | return nil, err 151 | } 152 | labelHash, err := LabelHash(subdomain) 153 | if err != nil { 154 | return nil, err 155 | } 156 | return session.SetSubnodeOwner(nameHash, labelHash, *ownerAddr) 157 | } 158 | 159 | // CreateRegistrySession creates a session suitable for multiple calls. 160 | func CreateRegistrySession(chainID *big.Int, wallet *accounts.Wallet, account *accounts.Account, passphrase string, contract *registry.Contract, gasPrice *big.Int) *registry.ContractSession { 161 | // Create a signer. 162 | signer := util.AccountSigner(chainID, wallet, account, passphrase) 163 | 164 | // Return our session. 165 | session := ®istry.ContractSession{ 166 | Contract: contract, 167 | CallOpts: bind.CallOpts{ 168 | Pending: true, 169 | }, 170 | TransactOpts: bind.TransactOpts{ 171 | From: account.Address, 172 | Signer: signer, 173 | GasPrice: gasPrice, 174 | }, 175 | } 176 | 177 | return session 178 | } 179 | -------------------------------------------------------------------------------- /resolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 Weald Technology Trading 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "bytes" 19 | "compress/zlib" 20 | "errors" 21 | "io" 22 | "math/big" 23 | "strings" 24 | 25 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 26 | "github.com/ethereum/go-ethereum/common" 27 | "github.com/ethereum/go-ethereum/core/types" 28 | "github.com/wealdtech/go-ens/v3/contracts/resolver" 29 | ) 30 | 31 | var zeroHash = make([]byte, 32) 32 | 33 | // UnknownAddress is the address to which unknown entries resolve. 34 | var UnknownAddress = common.HexToAddress("00") 35 | 36 | // Resolver is the structure for the resolver contract. 37 | type Resolver struct { 38 | Contract *resolver.Contract 39 | ContractAddr common.Address 40 | domain string 41 | } 42 | 43 | // NewResolver obtains an ENS resolver for a given domain. 44 | func NewResolver(backend bind.ContractBackend, domain string) (*Resolver, error) { 45 | registry, err := NewRegistry(backend) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | // Ensure the name is registered. 51 | ownerAddress, err := registry.Owner(domain) 52 | if err != nil { 53 | return nil, err 54 | } 55 | if bytes.Equal(ownerAddress.Bytes(), UnknownAddress.Bytes()) { 56 | return nil, errors.New("unregistered name") 57 | } 58 | 59 | // Obtain the resolver address for this domain. 60 | resolver, err := registry.ResolverAddress(domain) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return NewResolverAt(backend, domain, resolver) 65 | } 66 | 67 | // NewResolverAt obtains an ENS resolver at a given address. 68 | func NewResolverAt(backend bind.ContractBackend, domain string, address common.Address) (*Resolver, error) { 69 | contract, err := resolver.NewContract(address, backend) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | // Ensure this really is a resolver contract. 75 | nameHash, err := NameHash("test.eth") 76 | if err != nil { 77 | return nil, err 78 | } 79 | _, err = contract.Addr(nil, nameHash) 80 | if err != nil { 81 | if err.Error() == "no contract code at given address" { 82 | return nil, errors.New("no resolver") 83 | } 84 | return nil, err 85 | } 86 | 87 | return &Resolver{ 88 | Contract: contract, 89 | ContractAddr: address, 90 | domain: domain, 91 | }, nil 92 | } 93 | 94 | // PublicResolverAddress obtains the address of the public resolver for a chain. 95 | func PublicResolverAddress(backend bind.ContractBackend) (common.Address, error) { 96 | return Resolve(backend, "resolver.eth") 97 | } 98 | 99 | // Address returns the Ethereum address of the domain. 100 | func (r *Resolver) Address() (common.Address, error) { 101 | nameHash, err := NameHash(r.domain) 102 | if err != nil { 103 | return UnknownAddress, err 104 | } 105 | return r.Contract.Addr(nil, nameHash) 106 | } 107 | 108 | // SetAddress sets the Ethereum address of the domain. 109 | func (r *Resolver) SetAddress(opts *bind.TransactOpts, address common.Address) (*types.Transaction, error) { 110 | nameHash, err := NameHash(r.domain) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return r.Contract.SetAddr(opts, nameHash, address) 115 | } 116 | 117 | // MultiAddress returns the address of the domain for a given coin type. 118 | // The coin type is as per https://github.com/satoshilabs/slips/blob/master/slip-0044.md 119 | func (r *Resolver) MultiAddress(coinType uint64) ([]byte, error) { 120 | nameHash, err := NameHash(r.domain) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return r.Contract.Addr0(nil, nameHash, big.NewInt(int64(coinType))) 125 | } 126 | 127 | // SetMultiAddress sets the iaddress of the domain for a given coin type. 128 | // The coin type is as per https://github.com/satoshilabs/slips/blob/master/slip-0044.md 129 | func (r *Resolver) SetMultiAddress(opts *bind.TransactOpts, coinType uint64, address []byte) (*types.Transaction, error) { 130 | nameHash, err := NameHash(r.domain) 131 | if err != nil { 132 | return nil, err 133 | } 134 | return r.Contract.SetAddr0(opts, nameHash, big.NewInt(int64(coinType)), address) 135 | } 136 | 137 | // PubKey returns the public key of the domain. 138 | func (r *Resolver) PubKey() ([32]byte, [32]byte, error) { 139 | nameHash, err := NameHash(r.domain) 140 | if err != nil { 141 | return [32]byte{}, [32]byte{}, err 142 | } 143 | res, err := r.Contract.Pubkey(nil, nameHash) 144 | return res.X, res.Y, err 145 | } 146 | 147 | // SetPubKey sets the public key of the domain. 148 | func (r *Resolver) SetPubKey(opts *bind.TransactOpts, x [32]byte, y [32]byte) (*types.Transaction, error) { 149 | nameHash, err := NameHash(r.domain) 150 | if err != nil { 151 | return nil, err 152 | } 153 | return r.Contract.SetPubkey(opts, nameHash, x, y) 154 | } 155 | 156 | // Contenthash returns the content hash of the domain. 157 | func (r *Resolver) Contenthash() ([]byte, error) { 158 | nameHash, err := NameHash(r.domain) 159 | if err != nil { 160 | return nil, err 161 | } 162 | return r.Contract.Contenthash(nil, nameHash) 163 | } 164 | 165 | // SetContenthash sets the content hash of the domain. 166 | func (r *Resolver) SetContenthash(opts *bind.TransactOpts, contenthash []byte) (*types.Transaction, error) { 167 | nameHash, err := NameHash(r.domain) 168 | if err != nil { 169 | return nil, err 170 | } 171 | return r.Contract.SetContenthash(opts, nameHash, contenthash) 172 | } 173 | 174 | // InterfaceImplementer returns the address of the contract that implements the given interface for the given domain. 175 | func (r *Resolver) InterfaceImplementer(interfaceID [4]byte) (common.Address, error) { 176 | nameHash, err := NameHash(r.domain) 177 | if err != nil { 178 | return UnknownAddress, err 179 | } 180 | return r.Contract.InterfaceImplementer(nil, nameHash, interfaceID) 181 | } 182 | 183 | // Resolve resolves an ENS name in to an Etheruem address. 184 | // This will return an error if the name is not found or otherwise 0. 185 | func Resolve(backend bind.ContractBackend, input string) (common.Address, error) { 186 | if strings.Contains(input, ".") { 187 | return resolveName(backend, input) 188 | } 189 | if (strings.HasPrefix(input, "0x") && len(input) > 42) || (!strings.HasPrefix(input, "0x") && len(input) > 40) { 190 | return UnknownAddress, errors.New("address too long") 191 | } 192 | address := common.HexToAddress(input) 193 | if address == UnknownAddress { 194 | return UnknownAddress, errors.New("could not parse address") 195 | } 196 | 197 | return address, nil 198 | } 199 | 200 | func resolveName(backend bind.ContractBackend, input string) (common.Address, error) { 201 | nameHash, err := NameHash(input) 202 | if err != nil { 203 | return UnknownAddress, err 204 | } 205 | if bytes.Equal(nameHash[:], zeroHash) { 206 | return UnknownAddress, errors.New("bad name") 207 | } 208 | address, err := resolveHash(backend, input) 209 | if err != nil { 210 | return UnknownAddress, err 211 | } 212 | 213 | return address, nil 214 | } 215 | 216 | func resolveHash(backend bind.ContractBackend, domain string) (common.Address, error) { 217 | resolver, err := NewResolver(backend, domain) 218 | if err != nil { 219 | return UnknownAddress, err 220 | } 221 | 222 | // Resolve the domain. 223 | address, err := resolver.Address() 224 | if err != nil { 225 | return UnknownAddress, err 226 | } 227 | if bytes.Equal(address.Bytes(), UnknownAddress.Bytes()) { 228 | return UnknownAddress, errors.New("no address") 229 | } 230 | 231 | return address, nil 232 | } 233 | 234 | // SetText sets the text associated with a name. 235 | func (r *Resolver) SetText(opts *bind.TransactOpts, name string, value string) (*types.Transaction, error) { 236 | nameHash, err := NameHash(r.domain) 237 | if err != nil { 238 | return nil, err 239 | } 240 | return r.Contract.SetText(opts, nameHash, name, value) 241 | } 242 | 243 | // Text obtains the text associated with a name. 244 | func (r *Resolver) Text(name string) (string, error) { 245 | nameHash, err := NameHash(r.domain) 246 | if err != nil { 247 | return "", err 248 | } 249 | return r.Contract.Text(nil, nameHash, name) 250 | } 251 | 252 | // SetABI sets the ABI associated with a name. 253 | func (r *Resolver) SetABI(opts *bind.TransactOpts, _ string, abi string, contentType *big.Int) (*types.Transaction, error) { 254 | var data []byte 255 | switch contentType.Uint64() { 256 | case 1: 257 | // Uncompressed JSON. 258 | data = []byte(abi) 259 | case 2: 260 | // Zlib-compressed JSON. 261 | var b bytes.Buffer 262 | w := zlib.NewWriter(&b) 263 | if _, err := w.Write([]byte(abi)); err != nil { 264 | return nil, err 265 | } 266 | w.Close() 267 | data = b.Bytes() 268 | default: 269 | return nil, errors.New("unsupported content type") 270 | } 271 | 272 | nameHash, err := NameHash(r.domain) 273 | if err != nil { 274 | return nil, err 275 | } 276 | return r.Contract.SetABI(opts, nameHash, contentType, data) 277 | } 278 | 279 | // ABI returns the ABI associated with a name. 280 | func (r *Resolver) ABI(name string) (string, error) { 281 | contentTypes := big.NewInt(3) 282 | nameHash, err := NameHash(name) 283 | if err != nil { 284 | return "", err 285 | } 286 | contentType, data, err := r.Contract.ABI(nil, nameHash, contentTypes) 287 | var abi string 288 | if err == nil { 289 | if contentType.Cmp(big.NewInt(1)) == 0 { 290 | // Uncompressed JSON. 291 | abi = string(data) 292 | } else if contentType.Cmp(big.NewInt(2)) == 0 { 293 | // Zlib-compressed JSON. 294 | b := bytes.NewReader(data) 295 | var z io.ReadCloser 296 | z, err = zlib.NewReader(b) 297 | if err != nil { 298 | return "", err 299 | } 300 | defer z.Close() 301 | var uncompressed []byte 302 | uncompressed, err = io.ReadAll(z) 303 | if err != nil { 304 | return "", err 305 | } 306 | abi = string(uncompressed) 307 | } 308 | } 309 | return abi, nil 310 | } 311 | -------------------------------------------------------------------------------- /resolver_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Weald Technology Trading 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "encoding/hex" 19 | "testing" 20 | 21 | "github.com/ethereum/go-ethereum/common" 22 | "github.com/ethereum/go-ethereum/ethclient" 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | var client, _ = ethclient.Dial("https://mainnet.infura.io/v3/831a5442dc2e4536a9f8dee4ea1707a6") 28 | 29 | func TestResolveEmpty(t *testing.T) { 30 | _, err := Resolve(client, "") 31 | assert.NotNil(t, err, "Resolved empty name") 32 | } 33 | 34 | func TestResolveZero(t *testing.T) { 35 | _, err := Resolve(client, "0") 36 | assert.NotNil(t, err, "Resolved empty name") 37 | } 38 | 39 | func TestResolveNotPresent(t *testing.T) { 40 | _, err := Resolve(client, "sirnotappearinginthisregistry.eth") 41 | require.NotNil(t, err, "Resolved name that does not exist") 42 | assert.Equal(t, "unregistered name", err.Error(), "Unexpected error") 43 | } 44 | 45 | // func TestResolveNoResolver(t *testing.T) { 46 | // _, err := Resolve(client, "noresolver.eth") 47 | // require.NotNil(t, err, "Resolved name without a resolver") 48 | // assert.Equal(t, "no resolver", err.Error(), "Unexpected error") 49 | // } 50 | 51 | func TestResolveBadResolver(t *testing.T) { 52 | _, err := Resolve(client, "resolvestozero.eth") 53 | require.NotNil(t, err, "Resolved name with a bad resolver") 54 | assert.Equal(t, "no address", err.Error(), "Unexpected error") 55 | } 56 | 57 | func TestResolveTestEnsTest(t *testing.T) { 58 | expected := "ed96dd3be847b387217ef9de5b20d8392a6cdf40" 59 | actual, err := Resolve(client, "test.enstest.eth") 60 | require.Nil(t, err, "Error resolving name") 61 | assert.Equal(t, expected, hex.EncodeToString(actual[:]), "Did not receive expected result") 62 | } 63 | 64 | func TestResolveResolverEth(t *testing.T) { 65 | expected := "231b0ee14048e9dccd1d247744d114a4eb5e8e63" 66 | actual, err := Resolve(client, "resolver.eth") 67 | require.Nil(t, err, "Error resolving name") 68 | assert.Equal(t, expected, hex.EncodeToString(actual[:]), "Did not receive expected result") 69 | } 70 | 71 | func TestResolveEthereum(t *testing.T) { 72 | expected := "de0b295669a9fd93d5f28d9ec85e40f4cb697bae" 73 | actual, err := Resolve(client, "ethereum.eth") 74 | require.Nil(t, err, "Error resolving name") 75 | assert.Equal(t, expected, hex.EncodeToString(actual[:]), "Did not receive expected result") 76 | } 77 | 78 | func TestResolveAddress(t *testing.T) { 79 | expected := "b8c2c29ee19d8307cb7255e1cd9cbde883a267d5" 80 | actual, err := Resolve(client, "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5") 81 | require.Nil(t, err, "Error resolving address") 82 | assert.Equal(t, expected, hex.EncodeToString(actual[:]), "Did not receive expected result") 83 | } 84 | 85 | func TestResolveShortAddress(t *testing.T) { 86 | expected := "0000000000000000000000000000000000000001" 87 | actual, err := Resolve(client, "0x1") 88 | require.Nil(t, err, "Error resolving address") 89 | assert.Equal(t, expected, hex.EncodeToString(actual[:]), "Did not receive expected result") 90 | } 91 | 92 | func TestResolveHexString(t *testing.T) { 93 | _, err := Resolve(client, "0xe32c6d1a964749b6de2130e20daed821a45b9e7261118801ff5319d0ffc6b54a") 94 | assert.NotNil(t, err, "Resolved too-long hex string") 95 | } 96 | 97 | func TestReverseResolveTestEnsTest(t *testing.T) { 98 | expected := "nick.eth" 99 | address := common.HexToAddress("b8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5") 100 | actual, err := ReverseResolve(client, address) 101 | require.Nil(t, err, "Error resolving address") 102 | assert.Equal(t, expected, actual, "Did not receive expected result") 103 | } 104 | -------------------------------------------------------------------------------- /reverseregistrar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 Weald Technology Trading 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 21 | "github.com/ethereum/go-ethereum/common" 22 | "github.com/ethereum/go-ethereum/core/types" 23 | "github.com/wealdtech/go-ens/v3/contracts/reverseregistrar" 24 | ) 25 | 26 | // ReverseRegistrar is the structure for the reverse registrar. 27 | type ReverseRegistrar struct { 28 | Contract *reverseregistrar.Contract 29 | ContractAddr common.Address 30 | } 31 | 32 | // NewReverseRegistrar obtains the reverse registrar. 33 | func NewReverseRegistrar(backend bind.ContractBackend) (*ReverseRegistrar, error) { 34 | registry, err := NewRegistry(backend) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | // Obtain the registry address from the registrar. 40 | address, err := registry.Owner("addr.reverse") 41 | if err != nil { 42 | return nil, err 43 | } 44 | if address == UnknownAddress { 45 | return nil, errors.New("no registrar for that network") 46 | } 47 | return NewReverseRegistrarAt(backend, address) 48 | } 49 | 50 | // NewReverseRegistrarAt obtains the reverse registrar at a given address. 51 | func NewReverseRegistrarAt(backend bind.ContractBackend, address common.Address) (*ReverseRegistrar, error) { 52 | contract, err := reverseregistrar.NewContract(address, backend) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return &ReverseRegistrar{ 57 | Contract: contract, 58 | ContractAddr: address, 59 | }, nil 60 | } 61 | 62 | // SetName sets the name. 63 | func (r *ReverseRegistrar) SetName(opts *bind.TransactOpts, name string) (*types.Transaction, error) { 64 | return r.Contract.SetName(opts, name) 65 | } 66 | 67 | // DefaultResolverAddress obtains the default resolver address. 68 | func (r *ReverseRegistrar) DefaultResolverAddress() (common.Address, error) { 69 | return r.Contract.DefaultResolver(nil) 70 | } 71 | -------------------------------------------------------------------------------- /reverseresolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 22 | "github.com/ethereum/go-ethereum/common" 23 | "github.com/wealdtech/go-ens/v3/contracts/reverseresolver" 24 | ) 25 | 26 | // ReverseResolver is the structure for the reverse resolver contract. 27 | type ReverseResolver struct { 28 | Contract *reverseresolver.Contract 29 | ContractAddr common.Address 30 | } 31 | 32 | // NewReverseResolverFor creates a reverse resolver contract for the given address. 33 | func NewReverseResolverFor(backend bind.ContractBackend, address common.Address) (*ReverseResolver, error) { 34 | registry, err := NewRegistry(backend) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | // Now fetch the resolver. 40 | domain := fmt.Sprintf("%x.addr.reverse", address.Bytes()) 41 | contractAddress, err := registry.ResolverAddress(domain) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return NewReverseResolverAt(backend, contractAddress) 46 | } 47 | 48 | // NewReverseResolver obtains the reverse resolver. 49 | func NewReverseResolver(backend bind.ContractBackend) (*ReverseResolver, error) { 50 | reverseRegistrar, err := NewReverseRegistrar(backend) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | // Now fetch the default resolver. 56 | address, err := reverseRegistrar.DefaultResolverAddress() 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | return NewReverseResolverAt(backend, address) 62 | } 63 | 64 | // NewReverseResolverAt obtains the reverse resolver at a given address. 65 | func NewReverseResolverAt(backend bind.ContractBackend, address common.Address) (*ReverseResolver, error) { 66 | // Instantiate the reverse registrar contract. 67 | contract, err := reverseresolver.NewContract(address, backend) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | // Ensure the contract is a resolver. 73 | nameHash, err := NameHash("0.addr.reverse") 74 | if err != nil { 75 | return nil, err 76 | } 77 | _, err = contract.Name(nil, nameHash) 78 | if err != nil && err.Error() == "no contract code at given address" { 79 | return nil, fmt.Errorf("not a resolver") 80 | } 81 | 82 | return &ReverseResolver{ 83 | Contract: contract, 84 | ContractAddr: address, 85 | }, nil 86 | } 87 | 88 | // Name obtains the name for an address. 89 | func (r *ReverseResolver) Name(address common.Address) (string, error) { 90 | nameHash, err := NameHash(fmt.Sprintf("%s.addr.reverse", address.Hex()[2:])) 91 | if err != nil { 92 | return "", err 93 | } 94 | return r.Contract.Name(nil, nameHash) 95 | } 96 | 97 | // Format provides a string version of an address, reverse resolving it if possible. 98 | func Format(backend bind.ContractBackend, address common.Address) string { 99 | result, err := ReverseResolve(backend, address) 100 | if err != nil { 101 | result = address.Hex() 102 | } 103 | return result 104 | } 105 | 106 | // ReverseResolve resolves an address in to an ENS name. 107 | // This will return an error if the name is not found or otherwise 0. 108 | func ReverseResolve(backend bind.ContractBackend, address common.Address) (string, error) { 109 | resolver, err := NewReverseResolverFor(backend, address) 110 | if err != nil { 111 | return "", err 112 | } 113 | 114 | // Resolve the name. 115 | name, err := resolver.Name(address) 116 | if err != nil { 117 | return "", err 118 | } 119 | if name == "" { 120 | err = errors.New("no resolution") 121 | } 122 | 123 | return name, err 124 | } 125 | -------------------------------------------------------------------------------- /reverseresolver_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Weald Technology Trading 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/ethereum/go-ethereum/common" 21 | "github.com/ethereum/go-ethereum/ethclient" 22 | "github.com/stretchr/testify/require" 23 | ens "github.com/wealdtech/go-ens/v3" 24 | ) 25 | 26 | // TestReverseResolve tests the reverse resolution functionality. 27 | func TestReverseResolve(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | address common.Address 31 | res string 32 | err string 33 | }{ 34 | { 35 | name: "NoResolver", 36 | address: common.Address{}, 37 | err: "not a resolver", 38 | }, 39 | { 40 | name: "NoReverseRecord", 41 | address: common.HexToAddress("0x388ea662ef2c223ec0b047d41bf3c0f362142ad5"), 42 | err: "no resolution", 43 | }, 44 | { 45 | name: "Exists", 46 | address: common.HexToAddress("0x809FA673fe2ab515FaA168259cB14E2BeDeBF68e"), 47 | res: "avsa.eth", 48 | }, 49 | } 50 | 51 | client, err := ethclient.Dial("https://mainnet.infura.io/v3/831a5442dc2e4536a9f8dee4ea1707a6") 52 | require.NoError(t, err) 53 | 54 | for _, test := range tests { 55 | t.Run(test.name, func(t *testing.T) { 56 | res, err := ens.ReverseResolve(client, test.address) 57 | if test.err != "" { 58 | require.EqualError(t, err, test.err) 59 | } else { 60 | require.NoError(t, err) 61 | require.Equal(t, test.res, res) 62 | } 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tokenid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ens 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 22 | "github.com/ethereum/go-ethereum/common/math" 23 | ) 24 | 25 | // DeriveTokenID derive tokenID from the ENS domain. 26 | // 27 | // The tokenID of the ENS name is simply the uint256 representation of the tokenID of ERC721. 28 | func DeriveTokenID(backend bind.ContractBackend, domain string) (string, error) { 29 | if domain == "" { 30 | return "", errors.New("empty domain") 31 | } 32 | _, err := Resolve(backend, domain) 33 | if err != nil { 34 | return "", err 35 | } 36 | domain, err = NormaliseDomain(domain) 37 | if err != nil { 38 | return "", err 39 | } 40 | 41 | domain, err = DomainPart(domain, 1) 42 | if err != nil { 43 | return "", err 44 | } 45 | labelHash, err := LabelHash(domain) 46 | if err != nil { 47 | return "", err 48 | } 49 | hash := fmt.Sprintf("%#x", labelHash) 50 | tokenID, ok := math.ParseBig256(hash) 51 | if !ok { 52 | return "", err 53 | } 54 | return tokenID.String(), nil 55 | } 56 | -------------------------------------------------------------------------------- /tokenid_test.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ethereum/go-ethereum/ethclient" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestDeriveTokenId(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | expected string 14 | input string 15 | err string 16 | }{ 17 | { 18 | name: "Valid ENS domain", 19 | expected: "79233663829379634837589865448569342784712482819484549289560981379859480642508", 20 | input: "vitalik.eth", 21 | }, 22 | { 23 | name: "Invalid ENS domain", 24 | expected: "", 25 | input: "foo.bar", 26 | err: "unregistered name", 27 | }, 28 | { 29 | name: "Blank ENS domain", 30 | expected: "", 31 | input: "", 32 | err: "empty domain", 33 | }, 34 | } 35 | client, err := ethclient.Dial("https://mainnet.infura.io/v3/831a5442dc2e4536a9f8dee4ea1707a6") 36 | require.NoError(t, err) 37 | for _, test := range tests { 38 | t.Run(test.name, func(t *testing.T) { 39 | actual, err := DeriveTokenID(client, test.input) 40 | if test.err != "" { 41 | require.EqualError(t, err, test.err) 42 | } else { 43 | require.NoError(t, err) 44 | require.Equal(t, test.expected, actual) 45 | } 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /util/signer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 - 2023 Weald Technology Trading. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "crypto/ecdsa" 19 | "errors" 20 | "math/big" 21 | 22 | "github.com/ethereum/go-ethereum/accounts" 23 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 24 | "github.com/ethereum/go-ethereum/common" 25 | "github.com/ethereum/go-ethereum/core/types" 26 | "github.com/ethereum/go-ethereum/crypto" 27 | ) 28 | 29 | // KeySigner generates a signer using a private key. 30 | func KeySigner(chainID *big.Int, key *ecdsa.PrivateKey) bind.SignerFn { 31 | return func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { 32 | keyAddr := crypto.PubkeyToAddress(key.PublicKey) 33 | if address != keyAddr { 34 | return nil, errors.New("not authorized to sign this account") 35 | } 36 | return types.SignTx(tx, types.NewEIP155Signer(chainID), key) 37 | } 38 | } 39 | 40 | // AccountSigner generates a signer using an account. 41 | func AccountSigner(chainID *big.Int, wallet *accounts.Wallet, account *accounts.Account, passphrase string) bind.SignerFn { 42 | return func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { 43 | if address != account.Address { 44 | return nil, errors.New("not authorized to sign this account") 45 | } 46 | return (*wallet).SignTxWithPassphrase(*account, passphrase, tx, chainID) 47 | } 48 | } 49 | --------------------------------------------------------------------------------