github.com/status-im/status-go@v1.1.0/services/ens/api.go (about)

     1  package ens
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/binary"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"math/big"
    10  	"net/url"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/ipfs/go-cid"
    16  	"github.com/multiformats/go-multibase"
    17  	"github.com/multiformats/go-multihash"
    18  	"github.com/pkg/errors"
    19  	"github.com/wealdtech/go-ens/v3"
    20  	"github.com/wealdtech/go-multicodec"
    21  
    22  	"github.com/ethereum/go-ethereum"
    23  	"github.com/ethereum/go-ethereum/accounts/abi"
    24  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    25  	"github.com/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/common/hexutil"
    27  	"github.com/ethereum/go-ethereum/log"
    28  	"github.com/status-im/status-go/account"
    29  	"github.com/status-im/status-go/contracts"
    30  	"github.com/status-im/status-go/contracts/registrar"
    31  	"github.com/status-im/status-go/contracts/resolver"
    32  	"github.com/status-im/status-go/contracts/snt"
    33  	"github.com/status-im/status-go/params"
    34  	"github.com/status-im/status-go/rpc"
    35  	"github.com/status-im/status-go/services/utils"
    36  	wcommon "github.com/status-im/status-go/services/wallet/common"
    37  	"github.com/status-im/status-go/transactions"
    38  )
    39  
    40  const StatusDomain = "stateofus.eth"
    41  
    42  func NewAPI(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, config *params.NodeConfig, appDb *sql.DB, timeSource func() time.Time, syncUserDetailFunc *syncUsernameDetail) *API {
    43  	return &API{
    44  		contractMaker: &contracts.ContractMaker{
    45  			RPCClient: rpcClient,
    46  		},
    47  		accountsManager: accountsManager,
    48  		pendingTracker:  pendingTracker,
    49  		config:          config,
    50  		addrPerChain:    make(map[uint64]common.Address),
    51  		db:              NewEnsDatabase(appDb),
    52  
    53  		quit:               make(chan struct{}),
    54  		timeSource:         timeSource,
    55  		syncUserDetailFunc: syncUserDetailFunc,
    56  	}
    57  }
    58  
    59  type URI struct {
    60  	Scheme string
    61  	Host   string
    62  	Path   string
    63  }
    64  
    65  // use this to avoid using messenger directly to avoid circular dependency (protocol->ens->protocol)
    66  type syncUsernameDetail func(context.Context, *UsernameDetail) error
    67  
    68  type API struct {
    69  	contractMaker   *contracts.ContractMaker
    70  	accountsManager *account.GethManager
    71  	pendingTracker  *transactions.PendingTxTracker
    72  	config          *params.NodeConfig
    73  
    74  	addrPerChain      map[uint64]common.Address
    75  	addrPerChainMutex sync.Mutex
    76  
    77  	quitOnce sync.Once
    78  	quit     chan struct{}
    79  
    80  	db                 *Database
    81  	syncUserDetailFunc *syncUsernameDetail
    82  
    83  	timeSource func() time.Time
    84  }
    85  
    86  func (api *API) Stop() {
    87  	api.quitOnce.Do(func() {
    88  		close(api.quit)
    89  	})
    90  }
    91  
    92  func (api *API) unixTime() uint64 {
    93  	return uint64(api.timeSource().Unix())
    94  }
    95  
    96  func (api *API) GetEnsUsernames(ctx context.Context) ([]*UsernameDetail, error) {
    97  	removed := false
    98  	return api.db.GetEnsUsernames(&removed)
    99  }
   100  
   101  func (api *API) Add(ctx context.Context, chainID uint64, username string) error {
   102  	ud := &UsernameDetail{Username: username, ChainID: chainID, Clock: api.unixTime()}
   103  	err := api.db.AddEnsUsername(ud)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	return (*api.syncUserDetailFunc)(ctx, ud)
   108  }
   109  
   110  func (api *API) Remove(ctx context.Context, chainID uint64, username string) error {
   111  	ud := &UsernameDetail{Username: username, ChainID: chainID, Clock: api.unixTime()}
   112  	affected, err := api.db.RemoveEnsUsername(ud)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	if affected {
   117  		return (*api.syncUserDetailFunc)(ctx, ud)
   118  	}
   119  	return nil
   120  }
   121  
   122  func (api *API) GetRegistrarAddress(ctx context.Context, chainID uint64) (common.Address, error) {
   123  	return api.usernameRegistrarAddr(ctx, chainID)
   124  }
   125  
   126  func (api *API) Resolver(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
   127  	err := ValidateENSUsername(username)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	registry, err := api.contractMaker.NewRegistry(chainID)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   138  	resolver, err := registry.Resolver(callOpts, NameHash(username))
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	return &resolver, nil
   144  }
   145  
   146  func (api *API) GetName(ctx context.Context, chainID uint64, address common.Address) (string, error) {
   147  	backend, err := api.contractMaker.RPCClient.EthClient(chainID)
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  	return ens.ReverseResolve(backend, address)
   152  }
   153  
   154  func (api *API) OwnerOf(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
   155  	err := ValidateENSUsername(username)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	registry, err := api.contractMaker.NewRegistry(chainID)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   166  	owner, err := registry.Owner(callOpts, NameHash(username))
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	return &owner, nil
   172  }
   173  
   174  func (api *API) ContentHash(ctx context.Context, chainID uint64, username string) ([]byte, error) {
   175  	err := ValidateENSUsername(username)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	resolverAddress, err := api.Resolver(ctx, chainID, username)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   191  	contentHash, err := resolver.Contenthash(callOpts, NameHash(username))
   192  	if err != nil {
   193  		return nil, nil
   194  	}
   195  
   196  	return contentHash, nil
   197  }
   198  
   199  func (api *API) PublicKeyOf(ctx context.Context, chainID uint64, username string) (string, error) {
   200  	err := ValidateENSUsername(username)
   201  	if err != nil {
   202  		return "", err
   203  	}
   204  
   205  	resolverAddress, err := api.Resolver(ctx, chainID, username)
   206  	if err != nil {
   207  		return "", err
   208  	}
   209  
   210  	resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
   211  	if err != nil {
   212  		return "", err
   213  	}
   214  
   215  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   216  	pubKey, err := resolver.Pubkey(callOpts, NameHash(username))
   217  	if err != nil {
   218  		return "", err
   219  	}
   220  	return "0x04" + hex.EncodeToString(pubKey.X[:]) + hex.EncodeToString(pubKey.Y[:]), nil
   221  }
   222  
   223  func (api *API) AddressOf(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
   224  	err := ValidateENSUsername(username)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	resolverAddress, err := api.Resolver(ctx, chainID, username)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   240  	addr, err := resolver.Addr(callOpts, NameHash(username))
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	return &addr, nil
   246  }
   247  
   248  func (api *API) usernameRegistrarAddr(ctx context.Context, chainID uint64) (common.Address, error) {
   249  	log.Info("obtaining username registrar address")
   250  	api.addrPerChainMutex.Lock()
   251  	defer api.addrPerChainMutex.Unlock()
   252  	addr, ok := api.addrPerChain[chainID]
   253  	if ok {
   254  		return addr, nil
   255  	}
   256  
   257  	registryAddr, err := api.OwnerOf(ctx, chainID, StatusDomain)
   258  	if err != nil {
   259  		return common.Address{}, err
   260  	}
   261  
   262  	api.addrPerChain[chainID] = *registryAddr
   263  
   264  	go func() {
   265  		registry, err := api.contractMaker.NewRegistry(chainID)
   266  		if err != nil {
   267  			return
   268  		}
   269  
   270  		logs := make(chan *resolver.ENSRegistryWithFallbackNewOwner)
   271  
   272  		sub, err := registry.WatchNewOwner(&bind.WatchOpts{}, logs, nil, nil)
   273  		if err != nil {
   274  			return
   275  		}
   276  
   277  		for {
   278  			select {
   279  			case <-api.quit:
   280  				log.Info("quitting ens contract subscription")
   281  				sub.Unsubscribe()
   282  				return
   283  			case err := <-sub.Err():
   284  				if err != nil {
   285  					log.Error("ens contract subscription error: " + err.Error())
   286  				}
   287  				return
   288  			case vLog := <-logs:
   289  				api.addrPerChainMutex.Lock()
   290  				api.addrPerChain[chainID] = vLog.Owner
   291  				api.addrPerChainMutex.Unlock()
   292  			}
   293  		}
   294  	}()
   295  
   296  	return *registryAddr, nil
   297  }
   298  
   299  func (api *API) ExpireAt(ctx context.Context, chainID uint64, username string) (string, error) {
   300  	registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
   301  	if err != nil {
   302  		return "", err
   303  	}
   304  
   305  	registrar, err := api.contractMaker.NewUsernameRegistrar(chainID, registryAddr)
   306  	if err != nil {
   307  		return "", err
   308  	}
   309  
   310  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   311  	expTime, err := registrar.GetExpirationTime(callOpts, UsernameToLabel(username))
   312  	if err != nil {
   313  		return "", err
   314  	}
   315  
   316  	return fmt.Sprintf("%x", expTime), nil
   317  }
   318  
   319  func (api *API) Price(ctx context.Context, chainID uint64) (string, error) {
   320  	registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
   321  	if err != nil {
   322  		return "", err
   323  	}
   324  
   325  	registrar, err := api.contractMaker.NewUsernameRegistrar(chainID, registryAddr)
   326  	if err != nil {
   327  		return "", err
   328  	}
   329  
   330  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   331  	price, err := registrar.GetPrice(callOpts)
   332  	if err != nil {
   333  		return "", err
   334  	}
   335  
   336  	return fmt.Sprintf("%x", price), nil
   337  }
   338  
   339  func (api *API) Release(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string) (string, error) {
   340  	registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
   341  	if err != nil {
   342  		return "", err
   343  	}
   344  
   345  	registrar, err := api.contractMaker.NewUsernameRegistrar(chainID, registryAddr)
   346  	if err != nil {
   347  		return "", err
   348  	}
   349  
   350  	txOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
   351  	tx, err := registrar.Release(txOpts, UsernameToLabel(username))
   352  	if err != nil {
   353  		return "", err
   354  	}
   355  
   356  	err = api.pendingTracker.TrackPendingTransaction(
   357  		wcommon.ChainID(chainID),
   358  		tx.Hash(),
   359  		common.Address(txArgs.From),
   360  		registryAddr,
   361  		transactions.ReleaseENS,
   362  		transactions.AutoDelete,
   363  		"",
   364  	)
   365  	if err != nil {
   366  		log.Error("TrackPendingTransaction error", "error", err)
   367  		return "", err
   368  	}
   369  
   370  	err = api.Remove(ctx, chainID, fullDomainName(username))
   371  
   372  	if err != nil {
   373  		log.Warn("Releasing ENS username: transaction successful, but removing failed")
   374  	}
   375  
   376  	return tx.Hash().String(), nil
   377  }
   378  
   379  func (api *API) ReleasePrepareTxCallMsg(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string) (ethereum.CallMsg, error) {
   380  	registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
   381  	if err != nil {
   382  		return ethereum.CallMsg{}, err
   383  	}
   384  
   385  	data, err := registrarABI.Pack("release", UsernameToLabel(username))
   386  	if err != nil {
   387  		return ethereum.CallMsg{}, err
   388  	}
   389  
   390  	sntAddress, err := snt.ContractAddress(chainID)
   391  	if err != nil {
   392  		return ethereum.CallMsg{}, err
   393  	}
   394  	return ethereum.CallMsg{
   395  		From:  common.Address(txArgs.From),
   396  		To:    &sntAddress,
   397  		Value: big.NewInt(0),
   398  		Data:  data,
   399  	}, nil
   400  }
   401  
   402  func (api *API) ReleasePrepareTx(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string) (interface{}, error) {
   403  	callMsg, err := api.ReleasePrepareTxCallMsg(ctx, chainID, txArgs, username)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  
   408  	return toCallArg(callMsg), nil
   409  }
   410  
   411  func (api *API) ReleaseEstimate(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string) (uint64, error) {
   412  	registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
   413  	if err != nil {
   414  		return 0, err
   415  	}
   416  
   417  	data, err := registrarABI.Pack("release", UsernameToLabel(username))
   418  	if err != nil {
   419  		return 0, err
   420  	}
   421  
   422  	ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
   423  	if err != nil {
   424  		return 0, err
   425  	}
   426  
   427  	registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
   428  	if err != nil {
   429  		return 0, err
   430  	}
   431  
   432  	estimate, err := ethClient.EstimateGas(ctx, ethereum.CallMsg{
   433  		From:  common.Address(txArgs.From),
   434  		To:    &registryAddr,
   435  		Value: big.NewInt(0),
   436  		Data:  data,
   437  	})
   438  	if err != nil {
   439  		return 0, err
   440  	}
   441  	return estimate + 1000, nil
   442  }
   443  
   444  func (api *API) Register(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string, pubkey string) (string, error) {
   445  	snt, err := api.contractMaker.NewSNT(chainID)
   446  	if err != nil {
   447  		return "", err
   448  	}
   449  
   450  	priceHex, err := api.Price(ctx, chainID)
   451  	if err != nil {
   452  		return "", err
   453  	}
   454  	price := new(big.Int)
   455  	price.SetString(priceHex, 16)
   456  
   457  	registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
   458  	if err != nil {
   459  		return "", err
   460  	}
   461  
   462  	x, y := ExtractCoordinates(pubkey)
   463  	extraData, err := registrarABI.Pack("register", UsernameToLabel(username), common.Address(txArgs.From), x, y)
   464  	if err != nil {
   465  		return "", err
   466  	}
   467  
   468  	registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
   469  	if err != nil {
   470  		return "", err
   471  	}
   472  
   473  	txOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
   474  	tx, err := snt.ApproveAndCall(
   475  		txOpts,
   476  		registryAddr,
   477  		price,
   478  		extraData,
   479  	)
   480  
   481  	if err != nil {
   482  		return "", err
   483  	}
   484  
   485  	err = api.pendingTracker.TrackPendingTransaction(
   486  		wcommon.ChainID(chainID),
   487  		tx.Hash(),
   488  		common.Address(txArgs.From),
   489  		registryAddr,
   490  		transactions.RegisterENS,
   491  		transactions.AutoDelete,
   492  		"",
   493  	)
   494  	if err != nil {
   495  		log.Error("TrackPendingTransaction error", "error", err)
   496  		return "", err
   497  	}
   498  
   499  	err = api.Add(ctx, chainID, fullDomainName(username))
   500  	if err != nil {
   501  		log.Warn("Registering ENS username: transaction successful, but adding failed")
   502  	}
   503  
   504  	return tx.Hash().String(), nil
   505  }
   506  
   507  func (api *API) RegisterPrepareTxCallMsg(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (ethereum.CallMsg, error) {
   508  	priceHex, err := api.Price(ctx, chainID)
   509  	if err != nil {
   510  		return ethereum.CallMsg{}, err
   511  	}
   512  	price := new(big.Int)
   513  	price.SetString(priceHex, 16)
   514  
   515  	registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
   516  	if err != nil {
   517  		return ethereum.CallMsg{}, err
   518  	}
   519  
   520  	x, y := ExtractCoordinates(pubkey)
   521  	extraData, err := registrarABI.Pack("register", UsernameToLabel(username), common.Address(txArgs.From), x, y)
   522  	if err != nil {
   523  		return ethereum.CallMsg{}, err
   524  	}
   525  
   526  	sntABI, err := abi.JSON(strings.NewReader(snt.SNTABI))
   527  	if err != nil {
   528  		return ethereum.CallMsg{}, err
   529  	}
   530  
   531  	registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
   532  	if err != nil {
   533  		return ethereum.CallMsg{}, err
   534  	}
   535  
   536  	data, err := sntABI.Pack("approveAndCall", registryAddr, price, extraData)
   537  	if err != nil {
   538  		return ethereum.CallMsg{}, err
   539  	}
   540  
   541  	sntAddress, err := snt.ContractAddress(chainID)
   542  	if err != nil {
   543  		return ethereum.CallMsg{}, err
   544  	}
   545  	return ethereum.CallMsg{
   546  		From:  common.Address(txArgs.From),
   547  		To:    &sntAddress,
   548  		Value: big.NewInt(0),
   549  		Data:  data,
   550  	}, nil
   551  }
   552  
   553  func (api *API) RegisterPrepareTx(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (interface{}, error) {
   554  	callMsg, err := api.RegisterPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
   555  	if err != nil {
   556  		return nil, err
   557  	}
   558  
   559  	return toCallArg(callMsg), nil
   560  }
   561  
   562  func (api *API) RegisterEstimate(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (uint64, error) {
   563  	ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
   564  	if err != nil {
   565  		return 0, err
   566  	}
   567  
   568  	callMsg, err := api.RegisterPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
   569  	if err != nil {
   570  		return 0, err
   571  	}
   572  
   573  	estimate, err := ethClient.EstimateGas(ctx, callMsg)
   574  	if err != nil {
   575  		return 0, err
   576  	}
   577  	return estimate + 1000, nil
   578  }
   579  
   580  func (api *API) SetPubKey(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string, pubkey string) (string, error) {
   581  	err := ValidateENSUsername(username)
   582  	if err != nil {
   583  		return "", err
   584  	}
   585  
   586  	resolverAddress, err := api.Resolver(ctx, chainID, username)
   587  	if err != nil {
   588  		return "", err
   589  	}
   590  
   591  	resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
   592  	if err != nil {
   593  		return "", err
   594  	}
   595  
   596  	x, y := ExtractCoordinates(pubkey)
   597  	txOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
   598  	tx, err := resolver.SetPubkey(txOpts, NameHash(username), x, y)
   599  	if err != nil {
   600  		return "", err
   601  	}
   602  
   603  	err = api.pendingTracker.TrackPendingTransaction(
   604  		wcommon.ChainID(chainID),
   605  		tx.Hash(),
   606  		common.Address(txArgs.From),
   607  		*resolverAddress,
   608  		transactions.SetPubKey,
   609  		transactions.AutoDelete,
   610  		"",
   611  	)
   612  	if err != nil {
   613  		log.Error("TrackPendingTransaction error", "error", err)
   614  		return "", err
   615  	}
   616  
   617  	err = api.Add(ctx, chainID, fullDomainName(username))
   618  
   619  	if err != nil {
   620  		log.Warn("Registering ENS username: transaction successful, but adding failed")
   621  	}
   622  
   623  	return tx.Hash().String(), nil
   624  }
   625  
   626  func (api *API) SetPubKeyPrepareTxCallMsg(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (ethereum.CallMsg, error) {
   627  	err := ValidateENSUsername(username)
   628  	if err != nil {
   629  		return ethereum.CallMsg{}, err
   630  	}
   631  	x, y := ExtractCoordinates(pubkey)
   632  
   633  	resolverABI, err := abi.JSON(strings.NewReader(resolver.PublicResolverABI))
   634  	if err != nil {
   635  		return ethereum.CallMsg{}, err
   636  	}
   637  
   638  	data, err := resolverABI.Pack("setPubkey", NameHash(username), x, y)
   639  	if err != nil {
   640  		return ethereum.CallMsg{}, err
   641  	}
   642  
   643  	resolverAddress, err := api.Resolver(ctx, chainID, username)
   644  	if err != nil {
   645  		return ethereum.CallMsg{}, err
   646  	}
   647  
   648  	return ethereum.CallMsg{
   649  		From:  common.Address(txArgs.From),
   650  		To:    resolverAddress,
   651  		Value: big.NewInt(0),
   652  		Data:  data,
   653  	}, nil
   654  }
   655  
   656  func (api *API) SetPubKeyPrepareTx(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (interface{}, error) {
   657  	callMsg, err := api.SetPubKeyPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
   658  	if err != nil {
   659  		return nil, err
   660  	}
   661  
   662  	return toCallArg(callMsg), nil
   663  }
   664  
   665  func (api *API) SetPubKeyEstimate(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (uint64, error) {
   666  	ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
   667  	if err != nil {
   668  		return 0, err
   669  	}
   670  
   671  	callMsg, err := api.SetPubKeyPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
   672  	if err != nil {
   673  		return 0, err
   674  	}
   675  
   676  	estimate, err := ethClient.EstimateGas(ctx, callMsg)
   677  	if err != nil {
   678  		return 0, err
   679  	}
   680  	return estimate + 1000, nil
   681  }
   682  
   683  func (api *API) ResourceURL(ctx context.Context, chainID uint64, username string) (*URI, error) {
   684  	scheme := "https"
   685  	contentHash, err := api.ContentHash(ctx, chainID, username)
   686  	if err != nil {
   687  		return nil, err
   688  	}
   689  
   690  	if len(contentHash) == 0 {
   691  		return &URI{}, nil
   692  	}
   693  
   694  	data, codec, err := multicodec.RemoveCodec(contentHash)
   695  	if err != nil {
   696  		return nil, err
   697  	}
   698  	codecName, err := multicodec.Name(codec)
   699  	if err != nil {
   700  		return nil, err
   701  	}
   702  
   703  	switch codecName {
   704  	case "ipfs-ns":
   705  		thisCID, err := cid.Parse(data)
   706  		if err != nil {
   707  			return nil, errors.Wrap(err, "failed to parse CID")
   708  		}
   709  		str, err := thisCID.StringOfBase(multibase.Base32)
   710  		if err != nil {
   711  			return nil, errors.Wrap(err, "failed to obtain base36 representation")
   712  		}
   713  
   714  		parsedURL, _ := url.Parse(params.IpfsGatewayURL)
   715  		// Remove scheme from the url
   716  		host := parsedURL.Hostname() + parsedURL.Path + str
   717  		return &URI{scheme, host, ""}, nil
   718  	case "ipns-ns":
   719  		id, offset := binary.Uvarint(data)
   720  		if id == 0 {
   721  			return nil, fmt.Errorf("unknown CID")
   722  		}
   723  
   724  		data, _, err := multicodec.RemoveCodec(data[offset:])
   725  		if err != nil {
   726  			return nil, err
   727  		}
   728  		decodedMHash, err := multihash.Decode(data)
   729  		if err != nil {
   730  			return nil, err
   731  		}
   732  
   733  		return &URI{scheme, string(decodedMHash.Digest), ""}, nil
   734  	case "swarm-ns":
   735  		id, offset := binary.Uvarint(data)
   736  		if id == 0 {
   737  			return nil, fmt.Errorf("unknown CID")
   738  		}
   739  		data, _, err := multicodec.RemoveCodec(data[offset:])
   740  		if err != nil {
   741  			return nil, err
   742  		}
   743  		decodedMHash, err := multihash.Decode(data)
   744  		if err != nil {
   745  			return nil, err
   746  		}
   747  		path := "/bzz:/" + hex.EncodeToString(decodedMHash.Digest) + "/"
   748  		return &URI{scheme, "swarm-gateways.net", path}, nil
   749  	default:
   750  		return nil, fmt.Errorf("unknown codec name %s", codecName)
   751  	}
   752  }
   753  
   754  func toCallArg(msg ethereum.CallMsg) interface{} {
   755  	arg := map[string]interface{}{
   756  		"from": msg.From,
   757  		"to":   msg.To,
   758  	}
   759  	if len(msg.Data) > 0 {
   760  		arg["data"] = hexutil.Bytes(msg.Data)
   761  	}
   762  	if msg.Value != nil {
   763  		arg["value"] = (*hexutil.Big)(msg.Value)
   764  	}
   765  	if msg.Gas != 0 {
   766  		arg["gas"] = hexutil.Uint64(msg.Gas)
   767  	}
   768  	if msg.GasPrice != nil {
   769  		arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
   770  	}
   771  	return arg
   772  }
   773  
   774  func fullDomainName(username string) string {
   775  	return username + "." + StatusDomain
   776  }