2019-11-04 10:08:22 +00:00
|
|
|
// Copyright 2017-2019 Weald Technology Trading
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package ens
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Name represents an ENS name, for example 'foo.bar.eth'.
|
|
|
|
type Name struct {
|
|
|
|
backend bind.ContractBackend
|
|
|
|
// Name is the fully-qualified name of an ENS domain e.g. foo.bar.eth
|
|
|
|
Name string
|
|
|
|
// Domain is the domain of an ENS domain e.g. bar.eth
|
|
|
|
Domain string
|
|
|
|
// Label is the name part of an ENS domain e.g. foo
|
|
|
|
Label string
|
|
|
|
// Contracts
|
|
|
|
registry *Registry
|
|
|
|
registrar *BaseRegistrar
|
|
|
|
controller *ETHController
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewName creates an ENS name structure.
|
|
|
|
// Note that this does not create the name on-chain.
|
|
|
|
func NewName(backend bind.ContractBackend, name string) (*Name, error) {
|
|
|
|
name, err := NormaliseDomain(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
domain := Domain(name)
|
|
|
|
label, err := DomainPart(name, 1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
registry, err := NewRegistry(backend)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
registrar, err := NewBaseRegistrar(backend, domain)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
controller, err := NewETHController(backend, domain)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
isValid, err := controller.IsValid(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !isValid {
|
|
|
|
return nil, errors.New("name is not valid according to the rules of the registrar (too short, invalid characters, etc.)")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Name{
|
|
|
|
backend: backend,
|
|
|
|
Name: name,
|
|
|
|
Domain: domain,
|
|
|
|
Label: label,
|
|
|
|
registry: registry,
|
|
|
|
registrar: registrar,
|
|
|
|
controller: controller,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsRegistered returns true if the name is registered in the registrar
|
|
|
|
func (n *Name) IsRegistered() (bool, error) {
|
|
|
|
registrant, err := n.Registrant()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return registrant != UnknownAddress, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtendRegistration sends a transaction that extends the registration of the name.
|
|
|
|
func (n *Name) ExtendRegistration(opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
isRegistered, err := n.IsRegistered()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !isRegistered {
|
|
|
|
return nil, errors.New("name is not registered")
|
|
|
|
}
|
|
|
|
|
|
|
|
rentCost, err := n.RentCost()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.Value == nil || opts.Value.Cmp(rentCost) < 0 {
|
|
|
|
return nil, errors.New("not enough funds to extend the registration")
|
|
|
|
}
|
|
|
|
|
|
|
|
return n.controller.Renew(opts, n.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegistrationInterval obtains the minimum interval between commit and reveal
|
|
|
|
// when registering this name.
|
|
|
|
func (n *Name) RegistrationInterval() (time.Duration, error) {
|
|
|
|
interval, err := n.controller.MinCommitmentInterval()
|
|
|
|
if err != nil {
|
|
|
|
return time.Duration(0), err
|
|
|
|
}
|
|
|
|
return time.Duration(interval.Int64()) * time.Second, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterStageOne sends a transaction that starts the registration process.
|
|
|
|
func (n *Name) RegisterStageOne(registrant common.Address, opts *bind.TransactOpts) (*types.Transaction, [32]byte, error) {
|
|
|
|
var secret [32]byte
|
|
|
|
_, err := rand.Read(secret[:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, secret, err
|
|
|
|
}
|
|
|
|
|
|
|
|
isRegistered, err := n.IsRegistered()
|
|
|
|
if err != nil {
|
|
|
|
return nil, secret, err
|
|
|
|
}
|
|
|
|
if isRegistered {
|
|
|
|
return nil, secret, errors.New("name is already registered")
|
|
|
|
}
|
|
|
|
|
|
|
|
signedTx, err := n.controller.Commit(opts, n.Label, registrant, secret)
|
|
|
|
return signedTx, secret, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterStageTwo sends a transaction that completes the registration process.
|
|
|
|
// The registrant must be the same as supplied in RegisterStageOne.
|
|
|
|
// The secret is that returned by RegisterStageOne.
|
|
|
|
// At least RegistrationInterval() time must have passed since the stage one
|
|
|
|
// transaction was mined for this to work.
|
|
|
|
func (n *Name) RegisterStageTwo(registrant common.Address, secret [32]byte, opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
commitTS, err := n.controller.CommitmentTime(n.Label, registrant, secret)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if commitTS.Cmp(big.NewInt(0)) == 0 {
|
|
|
|
return nil, errors.New("stage 2 attempted prior to successful stage 1 transaction")
|
|
|
|
}
|
|
|
|
commit := time.Unix(commitTS.Int64(), 0)
|
|
|
|
|
|
|
|
minCommitIntervalTS, err := n.controller.MinCommitmentInterval()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
minCommitInterval := time.Duration(minCommitIntervalTS.Int64()) * time.Second
|
|
|
|
minStageTwoTime := commit.Add(minCommitInterval)
|
|
|
|
if time.Now().Before(minStageTwoTime) {
|
|
|
|
return nil, errors.New("too early to send second transaction")
|
|
|
|
}
|
|
|
|
|
|
|
|
maxCommitIntervalTS, err := n.controller.MaxCommitmentInterval()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
maxCommitInterval := time.Duration(maxCommitIntervalTS.Int64()) * time.Second
|
|
|
|
maxStageTwoTime := commit.Add(maxCommitInterval)
|
|
|
|
if time.Now().After(maxStageTwoTime) {
|
|
|
|
return nil, errors.New("too late to send second transaction")
|
|
|
|
}
|
|
|
|
|
|
|
|
return n.controller.Reveal(opts, n.Label, registrant, secret)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expires obtain the time at which the registration for this name expires.
|
|
|
|
func (n *Name) Expires() (time.Time, error) {
|
|
|
|
expiryTS, err := n.registrar.Expiry(n.Label)
|
|
|
|
if err != nil {
|
|
|
|
return time.Unix(0, 0), err
|
|
|
|
}
|
|
|
|
|
|
|
|
if expiryTS.Int64() == 0 {
|
|
|
|
return time.Unix(0, 0), errors.New("not registered")
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Unix(expiryTS.Int64(), 0), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Controller obtains the controller for this name.
|
|
|
|
// The controller can carry out operations on the name such as setting
|
|
|
|
// records, but cannot transfer ultimate ownership of the name.
|
|
|
|
func (n *Name) Controller() (common.Address, error) {
|
|
|
|
return n.registry.Owner(n.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetController sets the controller for this name.
|
|
|
|
// The controller can carry out operations on the name such as setting
|
|
|
|
// records, but cannot transfer ultimate ownership of the name.
|
|
|
|
func (n *Name) SetController(controller common.Address, opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
// Are we the current controller?
|
|
|
|
curController, err := n.Controller()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if curController == opts.From {
|
|
|
|
return n.registry.SetOwner(opts, n.Name, controller)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perhaps we are the registrant
|
|
|
|
registrant, err := n.Registrant()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Are we actually trying to reclaim?
|
|
|
|
if registrant == opts.From && registrant == controller {
|
|
|
|
return n.Reclaim(opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("not authorised to change the controller")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reclaim reclaims controller rights by the registrant
|
|
|
|
func (n *Name) Reclaim(opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
// Ensure the we are the registrant
|
|
|
|
registrant, err := n.Registrant()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if registrant != opts.From {
|
|
|
|
return nil, errors.New("not the registrant")
|
|
|
|
}
|
|
|
|
return n.registrar.Reclaim(opts, n.Name, registrant)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Registrant obtains the registrant for this name.
|
|
|
|
func (n *Name) Registrant() (common.Address, error) {
|
|
|
|
return n.registrar.Owner(n.Label)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transfer transfers the registration of this name to a new registrant.
|
|
|
|
func (n *Name) Transfer(registrant common.Address, opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
// Ensure the we are the registrant
|
|
|
|
currentRegistrant, err := n.Registrant()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if currentRegistrant != opts.From {
|
|
|
|
return nil, errors.New("not the current registrant")
|
|
|
|
}
|
|
|
|
return n.registrar.SetOwner(opts, n.Label, registrant)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RentCost returns the cost of rent in Wei-per-second.
|
|
|
|
func (n *Name) RentCost() (*big.Int, error) {
|
|
|
|
return n.controller.RentCost(n.Label)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateSubdomain creates a subdomain on the name.
|
|
|
|
func (n *Name) CreateSubdomain(label string, controller common.Address, opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
// Confirm the subdomain does not already exist
|
|
|
|
fqdn := fmt.Sprintf("%s.%s", label, n.Name)
|
|
|
|
subdomainController, err := n.registry.Owner(fqdn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if subdomainController != UnknownAddress {
|
|
|
|
return nil, errors.New("that subdomain already exists")
|
|
|
|
}
|
|
|
|
|
|
|
|
return n.registry.SetSubdomainOwner(opts, n.Name, label, controller)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResolverAddress fetches the address of the resolver contract for the name.
|
|
|
|
func (n *Name) ResolverAddress() (common.Address, error) {
|
|
|
|
return n.registry.ResolverAddress(n.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetResolverAddress sets the resolver contract address for the name.
|
|
|
|
func (n *Name) SetResolverAddress(address common.Address, opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
return n.registry.SetResolver(opts, n.Name, address)
|
|
|
|
}
|
2019-11-23 17:57:05 +00:00
|
|
|
|
|
|
|
// Address fetches the address of the name for a given coin type.
|
|
|
|
// Coin types are defined at https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
|
|
|
func (n *Name) Address(coinType uint64) ([]byte, error) {
|
|
|
|
resolver, err := NewResolver(n.backend, n.Name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return resolver.MultiAddress(coinType)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetAddress sets the address of the name for a given coin type.
|
|
|
|
// Coin types are defined at https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
|
|
|
func (n *Name) SetAddress(coinType uint64, address []byte, opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
|
|
resolver, err := NewResolver(n.backend, n.Name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return resolver.SetMultiAddress(opts, coinType, address)
|
|
|
|
}
|