package communitytokens import ( "context" "database/sql" "encoding/json" "fmt" "math/big" "strings" "github.com/pkg/errors" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" ethRpc "github.com/ethereum/go-ethereum/rpc" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts/community-tokens/mastertoken" "github.com/status-im/status-go/contracts/community-tokens/ownertoken" communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" "github.com/status-im/status-go/protocol" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/communities/token" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/communitytokens/communitytokensdatabase" "github.com/status-im/status-go/services/utils" "github.com/status-im/status-go/services/wallet/bigint" wcommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/router" "github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" ) // Collectibles service type Service struct { manager *Manager accountsManager *account.GethManager pendingTracker *transactions.PendingTxTracker config *params.NodeConfig db *communitytokensdatabase.Database Messenger *protocol.Messenger walletFeed *event.Feed walletWatcher *walletevent.Watcher transactor *transactions.Transactor feeManager *router.FeeManager } // Returns a new Collectibles Service. func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, config *params.NodeConfig, appDb *sql.DB, walletFeed *event.Feed, transactor *transactions.Transactor) *Service { return &Service{ manager: &Manager{rpcClient: rpcClient}, accountsManager: accountsManager, pendingTracker: pendingTracker, config: config, db: communitytokensdatabase.NewCommunityTokensDatabase(appDb), walletFeed: walletFeed, transactor: transactor, feeManager: &router.FeeManager{RPCClient: rpcClient}, } } // Protocols returns a new protocols list. In this case, there are none. func (s *Service) Protocols() []p2p.Protocol { return []p2p.Protocol{} } // APIs returns a list of new APIs. func (s *Service) APIs() []ethRpc.API { return []ethRpc.API{ { Namespace: "communitytokens", Version: "0.1.0", Service: NewAPI(s), Public: true, }, } } // Start is run when a service is started. func (s *Service) Start() error { s.walletWatcher = walletevent.NewWatcher(s.walletFeed, s.handleWalletEvent) s.walletWatcher.Start() return nil } func (s *Service) handleWalletEvent(event walletevent.Event) { if event.Type == transactions.EventPendingTransactionStatusChanged { var p transactions.StatusChangedPayload err := json.Unmarshal([]byte(event.Message), &p) if err != nil { log.Error(errors.Wrap(err, fmt.Sprintf("can't parse transaction message %v\n", event.Message)).Error()) return } if p.Status == transactions.Pending { return } pendingTransaction, err := s.pendingTracker.GetPendingEntry(p.ChainID, p.Hash) if err != nil { log.Error(errors.Wrap(err, fmt.Sprintf("no pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error()) return } var communityToken, ownerToken, masterToken *token.CommunityToken = &token.CommunityToken{}, &token.CommunityToken{}, &token.CommunityToken{} var tokenErr error switch pendingTransaction.Type { case transactions.DeployCommunityToken: communityToken, tokenErr = s.handleDeployCommunityToken(p.Status, pendingTransaction) case transactions.AirdropCommunityToken: communityToken, tokenErr = s.handleAirdropCommunityToken(p.Status, pendingTransaction) case transactions.RemoteDestructCollectible: communityToken, tokenErr = s.handleRemoteDestructCollectible(p.Status, pendingTransaction) case transactions.BurnCommunityToken: communityToken, tokenErr = s.handleBurnCommunityToken(p.Status, pendingTransaction) case transactions.DeployOwnerToken: ownerToken, masterToken, tokenErr = s.handleDeployOwnerToken(p.Status, pendingTransaction) case transactions.SetSignerPublicKey: communityToken, tokenErr = s.handleSetSignerPubKey(p.Status, pendingTransaction) default: return } err = s.pendingTracker.Delete(context.Background(), p.ChainID, p.Hash) if err != nil { log.Error(errors.Wrap(err, fmt.Sprintf("can't delete pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error()) } errorStr := "" if tokenErr != nil { errorStr = tokenErr.Error() } signal.SendCommunityTokenTransactionStatusSignal(string(pendingTransaction.Type), p.Status == transactions.Success, pendingTransaction.Hash, communityToken, ownerToken, masterToken, errorStr) } } func (s *Service) handleAirdropCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) if communityToken == nil { return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) } else { publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), communityToken.Address, protobuf.CommunityTokenAction_AIRDROP) if publishErr != nil { log.Warn("can't publish airdrop action") } } return communityToken, err } func (s *Service) handleRemoteDestructCollectible(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) if communityToken == nil { return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) } else { publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), communityToken.Address, protobuf.CommunityTokenAction_REMOTE_DESTRUCT) if publishErr != nil { log.Warn("can't publish remote destruct action") } } return communityToken, err } func (s *Service) handleBurnCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { if status == transactions.Success { // get new max supply and update database newMaxSupply, err := s.maxSupply(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.To.String()) if err != nil { return nil, err } err = s.Messenger.UpdateCommunityTokenSupply(int(pendingTransaction.ChainID), pendingTransaction.To.String(), &bigint.BigInt{Int: newMaxSupply}) if err != nil { return nil, err } } communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) if communityToken == nil { return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) } else { publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), communityToken.Address, protobuf.CommunityTokenAction_BURN) if publishErr != nil { log.Warn("can't publish burn action") } } return communityToken, err } func (s *Service) handleDeployOwnerToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, *token.CommunityToken, error) { newMasterAddress, err := s.GetMasterTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex()) if err != nil { return nil, nil, err } newOwnerAddress, err := s.GetOwnerTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex()) if err != nil { return nil, nil, err } err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryOwnerContractAddress(pendingTransaction.Hash.Hex()), newOwnerAddress) if err != nil { return nil, nil, err } err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryMasterContractAddress(pendingTransaction.Hash.Hex()), newMasterAddress) if err != nil { return nil, nil, err } ownerToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newOwnerAddress) if err != nil { return nil, nil, err } masterToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newMasterAddress) if err != nil { return nil, nil, err } return ownerToken, masterToken, nil } func (s *Service) updateStateAndAddTokenToCommunityDescription(status string, chainID int, address string) (*token.CommunityToken, error) { tokenToUpdate, err := s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address) if err != nil { return nil, err } if tokenToUpdate == nil { return nil, fmt.Errorf("token does not exist in database: chainID=%v, address=%v", chainID, address) } if status == transactions.Success { err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Deployed) if err != nil { return nil, err } err = s.Messenger.AddCommunityToken(tokenToUpdate.CommunityID, chainID, address) if err != nil { return nil, err } } else { err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Failed) if err != nil { return nil, err } } return s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address) } func (s *Service) handleDeployCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { return s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), pendingTransaction.To.String()) } func (s *Service) handleSetSignerPubKey(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) if err != nil { return nil, err } if communityToken == nil { return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) } if status == transactions.Success { _, err := s.Messenger.PromoteSelfToControlNode(types.FromHex(communityToken.CommunityID)) if err != nil { return nil, err } } return communityToken, err } // Stop is run when a service is stopped. func (s *Service) Stop() error { s.walletWatcher.Stop() return nil } func (s *Service) Init(messenger *protocol.Messenger) { s.Messenger = messenger } func (s *Service) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) { backend, err := s.manager.rpcClient.EthClient(chainID) if err != nil { return nil, err } return communityownertokenregistry.NewCommunityOwnerTokenRegistry(common.HexToAddress(contractAddress), backend) } func (s *Service) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) { backend, err := s.manager.rpcClient.EthClient(chainID) if err != nil { return nil, err } return ownertoken.NewOwnerToken(common.HexToAddress(contractAddress), backend) } func (s *Service) NewMasterTokenInstance(chainID uint64, contractAddress string) (*mastertoken.MasterToken, error) { backend, err := s.manager.rpcClient.EthClient(chainID) if err != nil { return nil, err } return mastertoken.NewMasterToken(common.HexToAddress(contractAddress), backend) } func (s *Service) validateTokens(tokenIds []*bigint.BigInt) error { if len(tokenIds) == 0 { return errors.New("token list is empty") } return nil } func (s *Service) validateBurnAmount(ctx context.Context, burnAmount *bigint.BigInt, chainID uint64, contractAddress string) error { if burnAmount.Cmp(big.NewInt(0)) <= 0 { return errors.New("burnAmount is less than 0") } remainingSupply, err := s.remainingSupply(ctx, chainID, contractAddress) if err != nil { return err } if burnAmount.Cmp(remainingSupply.Int) > 1 { return errors.New("burnAmount is bigger than remaining amount") } return nil } func (s *Service) remainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { tokenType, err := s.db.GetTokenType(chainID, contractAddress) if err != nil { return nil, err } switch tokenType { case protobuf.CommunityTokenType_ERC721: return s.remainingCollectiblesSupply(ctx, chainID, contractAddress) case protobuf.CommunityTokenType_ERC20: return s.remainingAssetsSupply(ctx, chainID, contractAddress) default: return nil, fmt.Errorf("unknown token type: %v", tokenType) } } func (s *Service) prepareNewMaxSupply(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (*big.Int, error) { maxSupply, err := s.maxSupply(ctx, chainID, contractAddress) if err != nil { return nil, err } var newMaxSupply = new(big.Int) newMaxSupply.Sub(maxSupply, burnAmount.Int) return newMaxSupply, nil } // RemainingSupply = MaxSupply - MintedCount func (s *Service) remainingCollectiblesSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress) if err != nil { return nil, err } maxSupply, err := contractInst.MaxSupply(callOpts) if err != nil { return nil, err } mintedCount, err := contractInst.MintedCount(callOpts) if err != nil { return nil, err } var res = new(big.Int) res.Sub(maxSupply, mintedCount) return &bigint.BigInt{Int: res}, nil } // RemainingSupply = MaxSupply - TotalSupply func (s *Service) remainingAssetsSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress) if err != nil { return nil, err } maxSupply, err := contractInst.MaxSupply(callOpts) if err != nil { return nil, err } totalSupply, err := contractInst.TotalSupply(callOpts) if err != nil { return nil, err } var res = new(big.Int) res.Sub(maxSupply, totalSupply) return &bigint.BigInt{Int: res}, nil } func (s *Service) ValidateWalletsAndAmounts(walletAddresses []string, amount *bigint.BigInt) error { if len(walletAddresses) == 0 { return errors.New("wallet addresses list is empty") } if amount.Cmp(big.NewInt(0)) <= 0 { return errors.New("amount is <= 0") } return nil } func (s *Service) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress) if err != nil { return "", err } signerPubKey, err := contractInst.SignerPublicKey(callOpts) if err != nil { return "", err } return types.ToHex(signerPubKey), nil } func (s *Service) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) { // 1. Get Owner Token contract address from deployer contract - SafeGetOwnerTokenAddress() ownerTokenAddr, err := s.SafeGetOwnerTokenAddress(ctx, chainID, communityID) if err != nil { return "", err } // 2. Get Signer from owner token contract - GetSignerPubKey() return s.GetSignerPubKey(ctx, chainID, ownerTokenAddr) } func (s *Service) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) if err != nil { return "", err } registryAddr, err := deployerContractInst.DeploymentRegistry(callOpts) if err != nil { return "", err } registryContractInst, err := s.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr.Hex()) if err != nil { return "", err } communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID) if err != nil { return "", err } ownerTokenAddress, err := registryContractInst.GetEntry(callOpts, communityEthAddress) return ownerTokenAddress.Hex(), err } func (s *Service) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { return s.manager.GetCollectibleContractData(chainID, contractAddress) } func (s *Service) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { return s.manager.GetAssetContractData(chainID, contractAddress) } func (s *Service) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) { return s.manager.DeploymentSignatureDigest(chainID, addressFrom, communityID) } func (s *Service) ProcessCommunityTokenAction(message *protobuf.CommunityTokenAction) error { communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress) if err != nil { return err } if communityToken == nil { return fmt.Errorf("can't find community token in database: chain %v, address %v", message.ChainId, message.ContractAddress) } if message.ActionType == protobuf.CommunityTokenAction_BURN { // get new max supply and update database newMaxSupply, err := s.maxSupply(context.Background(), uint64(communityToken.ChainID), communityToken.Address) if err != nil { return nil } err = s.Messenger.UpdateCommunityTokenSupply(communityToken.ChainID, communityToken.Address, &bigint.BigInt{Int: newMaxSupply}) if err != nil { return err } communityToken, _ = s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress) } signal.SendCommunityTokenActionSignal(communityToken, message.ActionType) return nil } func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) { if len(newSignerPubKey) <= 0 { return "", fmt.Errorf("signerPubKey is empty") } transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, s.accountsManager, s.config.KeyStoreDir, txArgs.From, password)) contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress) if err != nil { return "", err } tx, err := contractInst.SetSignerPublicKey(transactOpts, common.FromHex(newSignerPubKey)) if err != nil { return "", err } err = s.pendingTracker.TrackPendingTransaction( wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), common.HexToAddress(contractAddress), transactions.SetSignerPublicKey, transactions.Keep, "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) return "", err } return tx.Hash().Hex(), nil } func (s *Service) maxSupplyCollectibles(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress) if err != nil { return nil, err } return contractInst.MaxSupply(callOpts) } func (s *Service) maxSupplyAssets(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress) if err != nil { return nil, err } return contractInst.MaxSupply(callOpts) } func (s *Service) maxSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { tokenType, err := s.db.GetTokenType(chainID, contractAddress) if err != nil { return nil, err } switch tokenType { case protobuf.CommunityTokenType_ERC721: return s.maxSupplyCollectibles(ctx, chainID, contractAddress) case protobuf.CommunityTokenType_ERC20: return s.maxSupplyAssets(ctx, chainID, contractAddress) default: return nil, fmt.Errorf("unknown token type: %v", tokenType) } } func (s *Service) CreateCommunityTokenAndSave(chainID int, deploymentParameters DeploymentParameters, deployerAddress string, contractAddress string, tokenType protobuf.CommunityTokenType, privilegesLevel token.PrivilegesLevel, transactionHash string) (*token.CommunityToken, error) { contractVersion := "" if privilegesLevel == token.CommunityLevel { contractVersion = s.currentVersion() } tokenToSave := &token.CommunityToken{ TokenType: tokenType, CommunityID: deploymentParameters.CommunityID, Address: contractAddress, Name: deploymentParameters.Name, Symbol: deploymentParameters.Symbol, Description: deploymentParameters.Description, Supply: &bigint.BigInt{Int: deploymentParameters.GetSupply()}, InfiniteSupply: deploymentParameters.InfiniteSupply, Transferable: deploymentParameters.Transferable, RemoteSelfDestruct: deploymentParameters.RemoteSelfDestruct, ChainID: chainID, DeployState: token.InProgress, Decimals: deploymentParameters.Decimals, Deployer: deployerAddress, PrivilegesLevel: privilegesLevel, Base64Image: deploymentParameters.Base64Image, TransactionHash: transactionHash, Version: contractVersion, } return s.Messenger.SaveCommunityToken(tokenToSave, deploymentParameters.CroppedImage) } const ( MasterSuffix = "-master" OwnerSuffix = "-owner" ) func (s *Service) TemporaryMasterContractAddress(hash string) string { return hash + MasterSuffix } func (s *Service) TemporaryOwnerContractAddress(hash string) string { return hash + OwnerSuffix } func (s *Service) HashFromTemporaryContractAddress(address string) string { if strings.HasSuffix(address, OwnerSuffix) { return strings.TrimSuffix(address, OwnerSuffix) } else if strings.HasSuffix(address, MasterSuffix) { return strings.TrimSuffix(address, MasterSuffix) } return "" } func (s *Service) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { ethClient, err := s.manager.rpcClient.EthClient(chainID) if err != nil { return "", err } receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash)) if err != nil { return "", err } deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) if err != nil { return "", err } logMasterTokenCreatedSig := []byte("DeployMasterToken(address)") logMasterTokenCreatedSigHash := crypto.Keccak256Hash(logMasterTokenCreatedSig) for _, vLog := range receipt.Logs { if vLog.Topics[0].Hex() == logMasterTokenCreatedSigHash.Hex() { event, err := deployerContractInst.ParseDeployMasterToken(*vLog) if err != nil { return "", err } return event.Arg0.Hex(), nil } } return "", fmt.Errorf("can't find master token address in transaction: %v", txHash) } func (s *Service) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { ethClient, err := s.manager.rpcClient.EthClient(chainID) if err != nil { return "", err } receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash)) if err != nil { return "", err } deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) if err != nil { return "", err } logOwnerTokenCreatedSig := []byte("DeployOwnerToken(address)") logOwnerTokenCreatedSigHash := crypto.Keccak256Hash(logOwnerTokenCreatedSig) for _, vLog := range receipt.Logs { if vLog.Topics[0].Hex() == logOwnerTokenCreatedSigHash.Hex() { event, err := deployerContractInst.ParseDeployOwnerToken(*vLog) if err != nil { return "", err } return event.Arg0.Hex(), nil } } return "", fmt.Errorf("can't find owner token address in transaction: %v", txHash) } func (s *Service) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, chainID uint64, contractAddress string) error { communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), contractAddress) if err != nil { return err } if communityToken == nil { return fmt.Errorf("can't find token with address %v on chain %v", contractAddress, chainID) } if communityToken.DeployState != token.InProgress { return fmt.Errorf("token with address %v on chain %v is not in progress", contractAddress, chainID) } hashString := communityToken.TransactionHash if hashString == "" && (communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel) { hashString = s.HashFromTemporaryContractAddress(communityToken.Address) } if hashString == "" { return fmt.Errorf("can't find transaction hash for token with address %v on chain %v", contractAddress, chainID) } transactionType := transactions.DeployCommunityToken if communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel { transactionType = transactions.DeployOwnerToken } _, err = s.pendingTracker.GetPendingEntry(wcommon.ChainID(chainID), common.HexToHash(hashString)) if errors.Is(err, sql.ErrNoRows) { // start only if no pending transaction in database err = s.pendingTracker.TrackPendingTransaction( wcommon.ChainID(chainID), common.HexToHash(hashString), common.HexToAddress(communityToken.Deployer), common.Address{}, transactionType, transactions.Keep, "", ) log.Debug("retracking pending transaction with hashId ", hashString) } else { log.Debug("pending transaction with hashId is already tracked ", hashString) } return err } func (s *Service) publishTokenActionToPrivilegedMembers(communityID string, chainID uint64, contractAddress string, actionType protobuf.CommunityTokenAction_ActionType) error { decodedCommunityID, err := types.DecodeHex(communityID) if err != nil { return err } return s.Messenger.PublishTokenActionToPrivilegedMembers(decodedCommunityID, chainID, contractAddress, actionType) }