github.com/status-im/status-go@v1.1.0/services/communitytokens/service.go (about)

     1  package communitytokens
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math/big"
     9  	"strings"
    10  
    11  	"github.com/pkg/errors"
    12  
    13  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/event"
    16  	"github.com/ethereum/go-ethereum/log"
    17  	"github.com/ethereum/go-ethereum/p2p"
    18  	ethRpc "github.com/ethereum/go-ethereum/rpc"
    19  	"github.com/status-im/status-go/account"
    20  	"github.com/status-im/status-go/contracts/community-tokens/mastertoken"
    21  	"github.com/status-im/status-go/contracts/community-tokens/ownertoken"
    22  	communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry"
    23  	"github.com/status-im/status-go/eth-node/crypto"
    24  	"github.com/status-im/status-go/eth-node/types"
    25  	"github.com/status-im/status-go/params"
    26  	"github.com/status-im/status-go/protocol"
    27  	"github.com/status-im/status-go/protocol/communities"
    28  	"github.com/status-im/status-go/protocol/communities/token"
    29  	"github.com/status-im/status-go/protocol/protobuf"
    30  	"github.com/status-im/status-go/rpc"
    31  	"github.com/status-im/status-go/services/communitytokens/communitytokensdatabase"
    32  	"github.com/status-im/status-go/services/utils"
    33  	"github.com/status-im/status-go/services/wallet/bigint"
    34  	wcommon "github.com/status-im/status-go/services/wallet/common"
    35  	"github.com/status-im/status-go/services/wallet/router/fees"
    36  	"github.com/status-im/status-go/services/wallet/walletevent"
    37  	"github.com/status-im/status-go/signal"
    38  	"github.com/status-im/status-go/transactions"
    39  )
    40  
    41  // Collectibles service
    42  type Service struct {
    43  	manager         *Manager
    44  	accountsManager *account.GethManager
    45  	pendingTracker  *transactions.PendingTxTracker
    46  	config          *params.NodeConfig
    47  	db              *communitytokensdatabase.Database
    48  	Messenger       *protocol.Messenger
    49  	walletFeed      *event.Feed
    50  	walletWatcher   *walletevent.Watcher
    51  	transactor      *transactions.Transactor
    52  	feeManager      *fees.FeeManager
    53  }
    54  
    55  // Returns a new Collectibles Service.
    56  func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker,
    57  	config *params.NodeConfig, appDb *sql.DB, walletFeed *event.Feed, transactor *transactions.Transactor) *Service {
    58  	return &Service{
    59  		manager:         &Manager{rpcClient: rpcClient},
    60  		accountsManager: accountsManager,
    61  		pendingTracker:  pendingTracker,
    62  		config:          config,
    63  		db:              communitytokensdatabase.NewCommunityTokensDatabase(appDb),
    64  		walletFeed:      walletFeed,
    65  		transactor:      transactor,
    66  		feeManager:      &fees.FeeManager{RPCClient: rpcClient},
    67  	}
    68  }
    69  
    70  // Protocols returns a new protocols list. In this case, there are none.
    71  func (s *Service) Protocols() []p2p.Protocol {
    72  	return []p2p.Protocol{}
    73  }
    74  
    75  // APIs returns a list of new APIs.
    76  func (s *Service) APIs() []ethRpc.API {
    77  	return []ethRpc.API{
    78  		{
    79  			Namespace: "communitytokens",
    80  			Version:   "0.1.0",
    81  			Service:   NewAPI(s),
    82  			Public:    true,
    83  		},
    84  	}
    85  }
    86  
    87  // Start is run when a service is started.
    88  func (s *Service) Start() error {
    89  
    90  	s.walletWatcher = walletevent.NewWatcher(s.walletFeed, s.handleWalletEvent)
    91  	s.walletWatcher.Start()
    92  
    93  	return nil
    94  }
    95  
    96  func (s *Service) handleWalletEvent(event walletevent.Event) {
    97  	if event.Type == transactions.EventPendingTransactionStatusChanged {
    98  		var p transactions.StatusChangedPayload
    99  		err := json.Unmarshal([]byte(event.Message), &p)
   100  		if err != nil {
   101  			log.Error(errors.Wrap(err, fmt.Sprintf("can't parse transaction message %v\n", event.Message)).Error())
   102  			return
   103  		}
   104  		if p.Status == transactions.Pending {
   105  			return
   106  		}
   107  		pendingTransaction, err := s.pendingTracker.GetPendingEntry(p.ChainID, p.Hash)
   108  		if err != nil {
   109  			log.Error(errors.Wrap(err, fmt.Sprintf("no pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error())
   110  			return
   111  		}
   112  
   113  		var communityToken, ownerToken, masterToken *token.CommunityToken = &token.CommunityToken{}, &token.CommunityToken{}, &token.CommunityToken{}
   114  		var tokenErr error
   115  		switch pendingTransaction.Type {
   116  		case transactions.DeployCommunityToken:
   117  			communityToken, tokenErr = s.handleDeployCommunityToken(p.Status, pendingTransaction)
   118  		case transactions.AirdropCommunityToken:
   119  			communityToken, tokenErr = s.handleAirdropCommunityToken(p.Status, pendingTransaction)
   120  		case transactions.RemoteDestructCollectible:
   121  			communityToken, tokenErr = s.handleRemoteDestructCollectible(p.Status, pendingTransaction)
   122  		case transactions.BurnCommunityToken:
   123  			communityToken, tokenErr = s.handleBurnCommunityToken(p.Status, pendingTransaction)
   124  		case transactions.DeployOwnerToken:
   125  			ownerToken, masterToken, tokenErr = s.handleDeployOwnerToken(p.Status, pendingTransaction)
   126  		case transactions.SetSignerPublicKey:
   127  			communityToken, tokenErr = s.handleSetSignerPubKey(p.Status, pendingTransaction)
   128  		default:
   129  			return
   130  		}
   131  
   132  		err = s.pendingTracker.Delete(context.Background(), p.ChainID, p.Hash)
   133  		if err != nil {
   134  			log.Error(errors.Wrap(err, fmt.Sprintf("can't delete pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error())
   135  		}
   136  
   137  		errorStr := ""
   138  		if tokenErr != nil {
   139  			errorStr = tokenErr.Error()
   140  		}
   141  
   142  		signal.SendCommunityTokenTransactionStatusSignal(string(pendingTransaction.Type), p.Status == transactions.Success, pendingTransaction.Hash,
   143  			communityToken, ownerToken, masterToken, errorStr)
   144  	}
   145  }
   146  
   147  func (s *Service) handleAirdropCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) {
   148  	communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String())
   149  	if communityToken == nil {
   150  		return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String())
   151  	} else {
   152  		publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID),
   153  			communityToken.Address, protobuf.CommunityTokenAction_AIRDROP)
   154  		if publishErr != nil {
   155  			log.Warn("can't publish airdrop action")
   156  		}
   157  	}
   158  	return communityToken, err
   159  }
   160  
   161  func (s *Service) handleRemoteDestructCollectible(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) {
   162  	communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String())
   163  	if communityToken == nil {
   164  		return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String())
   165  	} else {
   166  		publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID),
   167  			communityToken.Address, protobuf.CommunityTokenAction_REMOTE_DESTRUCT)
   168  		if publishErr != nil {
   169  			log.Warn("can't publish remote destruct action")
   170  		}
   171  	}
   172  	return communityToken, err
   173  }
   174  
   175  func (s *Service) handleBurnCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) {
   176  	if status == transactions.Success {
   177  		// get new max supply and update database
   178  		newMaxSupply, err := s.maxSupply(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.To.String())
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  		err = s.Messenger.UpdateCommunityTokenSupply(int(pendingTransaction.ChainID), pendingTransaction.To.String(), &bigint.BigInt{Int: newMaxSupply})
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  	}
   187  
   188  	communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String())
   189  
   190  	if communityToken == nil {
   191  		return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String())
   192  	} else {
   193  		publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID),
   194  			communityToken.Address, protobuf.CommunityTokenAction_BURN)
   195  		if publishErr != nil {
   196  			log.Warn("can't publish burn action")
   197  		}
   198  	}
   199  	return communityToken, err
   200  }
   201  
   202  func (s *Service) handleDeployOwnerToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, *token.CommunityToken, error) {
   203  	newMasterAddress, err := s.GetMasterTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex())
   204  	if err != nil {
   205  		return nil, nil, err
   206  	}
   207  	newOwnerAddress, err := s.GetOwnerTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex())
   208  	if err != nil {
   209  		return nil, nil, err
   210  	}
   211  
   212  	err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryOwnerContractAddress(pendingTransaction.Hash.Hex()), newOwnerAddress)
   213  	if err != nil {
   214  		return nil, nil, err
   215  	}
   216  	err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryMasterContractAddress(pendingTransaction.Hash.Hex()), newMasterAddress)
   217  	if err != nil {
   218  		return nil, nil, err
   219  	}
   220  
   221  	ownerToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newOwnerAddress)
   222  	if err != nil {
   223  		return nil, nil, err
   224  	}
   225  
   226  	masterToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newMasterAddress)
   227  	if err != nil {
   228  		return nil, nil, err
   229  	}
   230  
   231  	return ownerToken, masterToken, nil
   232  }
   233  
   234  func (s *Service) updateStateAndAddTokenToCommunityDescription(status string, chainID int, address string) (*token.CommunityToken, error) {
   235  	tokenToUpdate, err := s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	if tokenToUpdate == nil {
   240  		return nil, fmt.Errorf("token does not exist in database: chainID=%v, address=%v", chainID, address)
   241  	}
   242  
   243  	if status == transactions.Success {
   244  		err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Deployed)
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  		err = s.Messenger.AddCommunityToken(tokenToUpdate.CommunityID, chainID, address)
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  	} else {
   253  		err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Failed)
   254  		if err != nil {
   255  			return nil, err
   256  		}
   257  	}
   258  	return s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address)
   259  }
   260  
   261  func (s *Service) handleDeployCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) {
   262  	return s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), pendingTransaction.To.String())
   263  }
   264  
   265  func (s *Service) handleSetSignerPubKey(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) {
   266  
   267  	communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String())
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	if communityToken == nil {
   272  		return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String())
   273  	}
   274  
   275  	if status == transactions.Success {
   276  		_, err := s.Messenger.PromoteSelfToControlNode(types.FromHex(communityToken.CommunityID))
   277  		if err != nil {
   278  			return nil, err
   279  		}
   280  	}
   281  	return communityToken, err
   282  }
   283  
   284  // Stop is run when a service is stopped.
   285  func (s *Service) Stop() error {
   286  	s.walletWatcher.Stop()
   287  	return nil
   288  }
   289  
   290  func (s *Service) Init(messenger *protocol.Messenger) {
   291  	s.Messenger = messenger
   292  }
   293  
   294  func (s *Service) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) {
   295  	backend, err := s.manager.rpcClient.EthClient(chainID)
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  	return communityownertokenregistry.NewCommunityOwnerTokenRegistry(common.HexToAddress(contractAddress), backend)
   300  }
   301  
   302  func (s *Service) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) {
   303  
   304  	backend, err := s.manager.rpcClient.EthClient(chainID)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	return ownertoken.NewOwnerToken(common.HexToAddress(contractAddress), backend)
   309  
   310  }
   311  
   312  func (s *Service) NewMasterTokenInstance(chainID uint64, contractAddress string) (*mastertoken.MasterToken, error) {
   313  	backend, err := s.manager.rpcClient.EthClient(chainID)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	return mastertoken.NewMasterToken(common.HexToAddress(contractAddress), backend)
   318  }
   319  
   320  func (s *Service) validateTokens(tokenIds []*bigint.BigInt) error {
   321  	if len(tokenIds) == 0 {
   322  		return errors.New("token list is empty")
   323  	}
   324  	return nil
   325  }
   326  
   327  func (s *Service) validateBurnAmount(ctx context.Context, burnAmount *bigint.BigInt, chainID uint64, contractAddress string) error {
   328  	if burnAmount.Cmp(big.NewInt(0)) <= 0 {
   329  		return errors.New("burnAmount is less than 0")
   330  	}
   331  	remainingSupply, err := s.remainingSupply(ctx, chainID, contractAddress)
   332  	if err != nil {
   333  		return err
   334  	}
   335  	if burnAmount.Cmp(remainingSupply.Int) > 1 {
   336  		return errors.New("burnAmount is bigger than remaining amount")
   337  	}
   338  	return nil
   339  }
   340  
   341  func (s *Service) remainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
   342  	tokenType, err := s.db.GetTokenType(chainID, contractAddress)
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  	switch tokenType {
   347  	case protobuf.CommunityTokenType_ERC721:
   348  		return s.remainingCollectiblesSupply(ctx, chainID, contractAddress)
   349  	case protobuf.CommunityTokenType_ERC20:
   350  		return s.remainingAssetsSupply(ctx, chainID, contractAddress)
   351  	default:
   352  		return nil, fmt.Errorf("unknown token type: %v", tokenType)
   353  	}
   354  }
   355  
   356  func (s *Service) prepareNewMaxSupply(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (*big.Int, error) {
   357  	maxSupply, err := s.maxSupply(ctx, chainID, contractAddress)
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  	var newMaxSupply = new(big.Int)
   362  	newMaxSupply.Sub(maxSupply, burnAmount.Int)
   363  	return newMaxSupply, nil
   364  }
   365  
   366  // RemainingSupply = MaxSupply - MintedCount
   367  func (s *Service) remainingCollectiblesSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
   368  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   369  	contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress)
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  	maxSupply, err := contractInst.MaxSupply(callOpts)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  	mintedCount, err := contractInst.MintedCount(callOpts)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  	var res = new(big.Int)
   382  	res.Sub(maxSupply, mintedCount)
   383  	return &bigint.BigInt{Int: res}, nil
   384  }
   385  
   386  // RemainingSupply = MaxSupply - TotalSupply
   387  func (s *Service) remainingAssetsSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
   388  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   389  	contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	maxSupply, err := contractInst.MaxSupply(callOpts)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  	totalSupply, err := contractInst.TotalSupply(callOpts)
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  	var res = new(big.Int)
   402  	res.Sub(maxSupply, totalSupply)
   403  	return &bigint.BigInt{Int: res}, nil
   404  }
   405  
   406  func (s *Service) ValidateWalletsAndAmounts(walletAddresses []string, amount *bigint.BigInt) error {
   407  	if len(walletAddresses) == 0 {
   408  		return errors.New("wallet addresses list is empty")
   409  	}
   410  	if amount.Cmp(big.NewInt(0)) <= 0 {
   411  		return errors.New("amount is <= 0")
   412  	}
   413  	return nil
   414  }
   415  
   416  func (s *Service) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
   417  
   418  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   419  	contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress)
   420  	if err != nil {
   421  		return "", err
   422  	}
   423  	signerPubKey, err := contractInst.SignerPublicKey(callOpts)
   424  	if err != nil {
   425  		return "", err
   426  	}
   427  
   428  	return types.ToHex(signerPubKey), nil
   429  }
   430  
   431  func (s *Service) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) {
   432  	// 1. Get Owner Token contract address from deployer contract - SafeGetOwnerTokenAddress()
   433  	ownerTokenAddr, err := s.SafeGetOwnerTokenAddress(ctx, chainID, communityID)
   434  	if err != nil {
   435  		return "", err
   436  	}
   437  	// 2. Get Signer from owner token contract - GetSignerPubKey()
   438  	return s.GetSignerPubKey(ctx, chainID, ownerTokenAddr)
   439  }
   440  
   441  func (s *Service) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) {
   442  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   443  	deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID)
   444  	if err != nil {
   445  		return "", err
   446  	}
   447  	registryAddr, err := deployerContractInst.DeploymentRegistry(callOpts)
   448  	if err != nil {
   449  		return "", err
   450  	}
   451  	registryContractInst, err := s.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr.Hex())
   452  	if err != nil {
   453  		return "", err
   454  	}
   455  	communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID)
   456  	if err != nil {
   457  		return "", err
   458  	}
   459  	ownerTokenAddress, err := registryContractInst.GetEntry(callOpts, communityEthAddress)
   460  
   461  	return ownerTokenAddress.Hex(), err
   462  }
   463  
   464  func (s *Service) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) {
   465  	return s.manager.GetCollectibleContractData(chainID, contractAddress)
   466  }
   467  
   468  func (s *Service) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) {
   469  	return s.manager.GetAssetContractData(chainID, contractAddress)
   470  }
   471  
   472  func (s *Service) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
   473  	return s.manager.DeploymentSignatureDigest(chainID, addressFrom, communityID)
   474  }
   475  
   476  func (s *Service) ProcessCommunityTokenAction(message *protobuf.CommunityTokenAction) error {
   477  	communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress)
   478  	if err != nil {
   479  		return err
   480  	}
   481  	if communityToken == nil {
   482  		return fmt.Errorf("can't find community token in database: chain %v, address %v", message.ChainId, message.ContractAddress)
   483  	}
   484  
   485  	if message.ActionType == protobuf.CommunityTokenAction_BURN {
   486  		// get new max supply and update database
   487  		newMaxSupply, err := s.maxSupply(context.Background(), uint64(communityToken.ChainID), communityToken.Address)
   488  		if err != nil {
   489  			return nil
   490  		}
   491  		err = s.Messenger.UpdateCommunityTokenSupply(communityToken.ChainID, communityToken.Address, &bigint.BigInt{Int: newMaxSupply})
   492  		if err != nil {
   493  			return err
   494  		}
   495  		communityToken, _ = s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress)
   496  	}
   497  
   498  	signal.SendCommunityTokenActionSignal(communityToken, message.ActionType)
   499  
   500  	return nil
   501  }
   502  
   503  func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) {
   504  
   505  	if len(newSignerPubKey) <= 0 {
   506  		return "", fmt.Errorf("signerPubKey is empty")
   507  	}
   508  
   509  	transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, s.accountsManager, s.config.KeyStoreDir, txArgs.From, password))
   510  
   511  	contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress)
   512  	if err != nil {
   513  		return "", err
   514  	}
   515  
   516  	tx, err := contractInst.SetSignerPublicKey(transactOpts, common.FromHex(newSignerPubKey))
   517  	if err != nil {
   518  		return "", err
   519  	}
   520  
   521  	err = s.pendingTracker.TrackPendingTransaction(
   522  		wcommon.ChainID(chainID),
   523  		tx.Hash(),
   524  		common.Address(txArgs.From),
   525  		common.HexToAddress(contractAddress),
   526  		transactions.SetSignerPublicKey,
   527  		transactions.Keep,
   528  		"",
   529  	)
   530  	if err != nil {
   531  		log.Error("TrackPendingTransaction error", "error", err)
   532  		return "", err
   533  	}
   534  
   535  	return tx.Hash().Hex(), nil
   536  }
   537  
   538  func (s *Service) maxSupplyCollectibles(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
   539  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   540  	contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress)
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	return contractInst.MaxSupply(callOpts)
   545  }
   546  
   547  func (s *Service) maxSupplyAssets(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
   548  	callOpts := &bind.CallOpts{Context: ctx, Pending: false}
   549  	contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	return contractInst.MaxSupply(callOpts)
   554  }
   555  
   556  func (s *Service) maxSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
   557  	tokenType, err := s.db.GetTokenType(chainID, contractAddress)
   558  	if err != nil {
   559  		return nil, err
   560  	}
   561  
   562  	switch tokenType {
   563  	case protobuf.CommunityTokenType_ERC721:
   564  		return s.maxSupplyCollectibles(ctx, chainID, contractAddress)
   565  	case protobuf.CommunityTokenType_ERC20:
   566  		return s.maxSupplyAssets(ctx, chainID, contractAddress)
   567  	default:
   568  		return nil, fmt.Errorf("unknown token type: %v", tokenType)
   569  	}
   570  }
   571  
   572  func (s *Service) CreateCommunityTokenAndSave(chainID int, deploymentParameters DeploymentParameters,
   573  	deployerAddress string, contractAddress string, tokenType protobuf.CommunityTokenType, privilegesLevel token.PrivilegesLevel, transactionHash string) (*token.CommunityToken, error) {
   574  
   575  	contractVersion := ""
   576  	if privilegesLevel == token.CommunityLevel {
   577  		contractVersion = s.currentVersion()
   578  	}
   579  
   580  	tokenToSave := &token.CommunityToken{
   581  		TokenType:          tokenType,
   582  		CommunityID:        deploymentParameters.CommunityID,
   583  		Address:            contractAddress,
   584  		Name:               deploymentParameters.Name,
   585  		Symbol:             deploymentParameters.Symbol,
   586  		Description:        deploymentParameters.Description,
   587  		Supply:             &bigint.BigInt{Int: deploymentParameters.GetSupply()},
   588  		InfiniteSupply:     deploymentParameters.InfiniteSupply,
   589  		Transferable:       deploymentParameters.Transferable,
   590  		RemoteSelfDestruct: deploymentParameters.RemoteSelfDestruct,
   591  		ChainID:            chainID,
   592  		DeployState:        token.InProgress,
   593  		Decimals:           deploymentParameters.Decimals,
   594  		Deployer:           deployerAddress,
   595  		PrivilegesLevel:    privilegesLevel,
   596  		Base64Image:        deploymentParameters.Base64Image,
   597  		TransactionHash:    transactionHash,
   598  		Version:            contractVersion,
   599  	}
   600  
   601  	return s.Messenger.SaveCommunityToken(tokenToSave, deploymentParameters.CroppedImage)
   602  }
   603  
   604  const (
   605  	MasterSuffix = "-master"
   606  	OwnerSuffix  = "-owner"
   607  )
   608  
   609  func (s *Service) TemporaryMasterContractAddress(hash string) string {
   610  	return hash + MasterSuffix
   611  }
   612  
   613  func (s *Service) TemporaryOwnerContractAddress(hash string) string {
   614  	return hash + OwnerSuffix
   615  }
   616  
   617  func (s *Service) HashFromTemporaryContractAddress(address string) string {
   618  	if strings.HasSuffix(address, OwnerSuffix) {
   619  		return strings.TrimSuffix(address, OwnerSuffix)
   620  	} else if strings.HasSuffix(address, MasterSuffix) {
   621  		return strings.TrimSuffix(address, MasterSuffix)
   622  	}
   623  	return ""
   624  }
   625  
   626  func (s *Service) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) {
   627  	ethClient, err := s.manager.rpcClient.EthClient(chainID)
   628  	if err != nil {
   629  		return "", err
   630  	}
   631  
   632  	receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash))
   633  	if err != nil {
   634  		return "", err
   635  	}
   636  
   637  	deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID)
   638  	if err != nil {
   639  		return "", err
   640  	}
   641  
   642  	logMasterTokenCreatedSig := []byte("DeployMasterToken(address)")
   643  	logMasterTokenCreatedSigHash := crypto.Keccak256Hash(logMasterTokenCreatedSig)
   644  
   645  	for _, vLog := range receipt.Logs {
   646  		if vLog.Topics[0].Hex() == logMasterTokenCreatedSigHash.Hex() {
   647  			event, err := deployerContractInst.ParseDeployMasterToken(*vLog)
   648  			if err != nil {
   649  				return "", err
   650  			}
   651  			return event.Arg0.Hex(), nil
   652  		}
   653  	}
   654  	return "", fmt.Errorf("can't find master token address in transaction: %v", txHash)
   655  }
   656  
   657  func (s *Service) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) {
   658  	ethClient, err := s.manager.rpcClient.EthClient(chainID)
   659  	if err != nil {
   660  		return "", err
   661  	}
   662  
   663  	receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash))
   664  	if err != nil {
   665  		return "", err
   666  	}
   667  
   668  	deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID)
   669  	if err != nil {
   670  		return "", err
   671  	}
   672  
   673  	logOwnerTokenCreatedSig := []byte("DeployOwnerToken(address)")
   674  	logOwnerTokenCreatedSigHash := crypto.Keccak256Hash(logOwnerTokenCreatedSig)
   675  
   676  	for _, vLog := range receipt.Logs {
   677  		if vLog.Topics[0].Hex() == logOwnerTokenCreatedSigHash.Hex() {
   678  			event, err := deployerContractInst.ParseDeployOwnerToken(*vLog)
   679  			if err != nil {
   680  				return "", err
   681  			}
   682  			return event.Arg0.Hex(), nil
   683  		}
   684  	}
   685  	return "", fmt.Errorf("can't find owner token address in transaction: %v", txHash)
   686  }
   687  
   688  func (s *Service) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, chainID uint64, contractAddress string) error {
   689  	communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), contractAddress)
   690  	if err != nil {
   691  		return err
   692  	}
   693  	if communityToken == nil {
   694  		return fmt.Errorf("can't find token with address %v on chain %v", contractAddress, chainID)
   695  	}
   696  	if communityToken.DeployState != token.InProgress {
   697  		return fmt.Errorf("token with address %v on chain %v is not in progress", contractAddress, chainID)
   698  	}
   699  
   700  	hashString := communityToken.TransactionHash
   701  	if hashString == "" && (communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel) {
   702  		hashString = s.HashFromTemporaryContractAddress(communityToken.Address)
   703  	}
   704  
   705  	if hashString == "" {
   706  		return fmt.Errorf("can't find transaction hash for token with address %v on chain %v", contractAddress, chainID)
   707  	}
   708  
   709  	transactionType := transactions.DeployCommunityToken
   710  	if communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel {
   711  		transactionType = transactions.DeployOwnerToken
   712  	}
   713  
   714  	_, err = s.pendingTracker.GetPendingEntry(wcommon.ChainID(chainID), common.HexToHash(hashString))
   715  	if errors.Is(err, sql.ErrNoRows) {
   716  		// start only if no pending transaction in database
   717  		err = s.pendingTracker.TrackPendingTransaction(
   718  			wcommon.ChainID(chainID),
   719  			common.HexToHash(hashString),
   720  			common.HexToAddress(communityToken.Deployer),
   721  			common.Address{},
   722  			transactionType,
   723  			transactions.Keep,
   724  			"",
   725  		)
   726  		log.Debug("retracking pending transaction with hashId ", hashString)
   727  	} else {
   728  		log.Debug("pending transaction with hashId is already tracked ", hashString)
   729  	}
   730  	return err
   731  }
   732  
   733  func (s *Service) publishTokenActionToPrivilegedMembers(communityID string, chainID uint64, contractAddress string, actionType protobuf.CommunityTokenAction_ActionType) error {
   734  	decodedCommunityID, err := types.DecodeHex(communityID)
   735  	if err != nil {
   736  		return err
   737  	}
   738  	return s.Messenger.PublishTokenActionToPrivilegedMembers(decodedCommunityID, chainID, contractAddress, actionType)
   739  }