github.com/status-im/status-go@v1.1.0/protocol/messenger_profile_showcase.go (about)

     1  package protocol
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/ecdsa"
     7  	crand "crypto/rand"
     8  	"errors"
     9  	"math/big"
    10  	"reflect"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/golang/protobuf/proto"
    15  	"go.uber.org/zap"
    16  
    17  	eth_common "github.com/ethereum/go-ethereum/common"
    18  
    19  	"github.com/status-im/status-go/eth-node/crypto"
    20  	"github.com/status-im/status-go/eth-node/types"
    21  	"github.com/status-im/status-go/multiaccounts/accounts"
    22  	"github.com/status-im/status-go/protocol/common"
    23  	"github.com/status-im/status-go/protocol/communities"
    24  	"github.com/status-im/status-go/protocol/identity"
    25  	"github.com/status-im/status-go/protocol/protobuf"
    26  	"github.com/status-im/status-go/services/wallet/bigint"
    27  	w_common "github.com/status-im/status-go/services/wallet/common"
    28  	"github.com/status-im/status-go/services/wallet/thirdparty"
    29  )
    30  
    31  var errorDecryptingPayloadEncryptionKey = errors.New("decrypting the payload encryption key resulted in no error and a nil key")
    32  var errorConvertCollectibleTokenIDToInt = errors.New("failed to convert collectible token id to bigint")
    33  var errorNoAccountPresentedForCollectible = errors.New("account holding the collectible is not presented in the profile showcase")
    34  var errorDublicateAccountAddress = errors.New("duplicate account address")
    35  var errorAccountVisibilityLowerThanCollectible = errors.New("account visibility lower than collectible")
    36  
    37  func sortProfileEntyByOrder(slice interface{}, getOrder func(int) int) {
    38  	sort.Slice(slice, func(i, j int) bool {
    39  		return getOrder(j) > getOrder(i)
    40  	})
    41  }
    42  
    43  func toCollectibleUniqueID(contractAddress string, tokenID string, chainID uint64) (thirdparty.CollectibleUniqueID, error) {
    44  	tokenIDInt := new(big.Int)
    45  	tokenIDInt, isTokenIDOk := tokenIDInt.SetString(tokenID, 10)
    46  	if !isTokenIDOk {
    47  		return thirdparty.CollectibleUniqueID{}, errorConvertCollectibleTokenIDToInt
    48  	}
    49  
    50  	return thirdparty.CollectibleUniqueID{
    51  		ContractID: thirdparty.ContractID{
    52  			ChainID: w_common.ChainID(chainID),
    53  			Address: eth_common.HexToAddress(contractAddress),
    54  		},
    55  		TokenID: &bigint.BigInt{Int: tokenIDInt},
    56  	}, nil
    57  }
    58  
    59  func (m *Messenger) fetchCollectibleOwner(contractAddress string, tokenID string, chainID uint64) ([]thirdparty.AccountBalance, error) {
    60  	collectibleID, err := toCollectibleUniqueID(contractAddress, tokenID, chainID)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	balance, err := m.communitiesManager.GetCollectiblesManager().GetCollectibleOwnership(collectibleID)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	return balance, nil
    70  }
    71  
    72  func (m *Messenger) validateCollectiblesOwnership(accounts []*identity.ProfileShowcaseAccountPreference,
    73  	collectibles []*identity.ProfileShowcaseCollectiblePreference) error {
    74  	accountsMap := make(map[string]identity.ProfileShowcaseVisibility)
    75  
    76  	for _, accountProfile := range accounts {
    77  		addressCapitalized := strings.ToUpper(accountProfile.Address)
    78  		if _, ok := accountsMap[addressCapitalized]; ok {
    79  			return errorDublicateAccountAddress
    80  		}
    81  		accountsMap[addressCapitalized] = accountProfile.ShowcaseVisibility
    82  	}
    83  
    84  	for _, collectibleProfile := range collectibles {
    85  		balances, err := m.fetchCollectibleOwner(collectibleProfile.ContractAddress, collectibleProfile.TokenID,
    86  			collectibleProfile.ChainID)
    87  		if err != nil {
    88  			return err
    89  		}
    90  
    91  		// NOTE: ERC721 tokens can have only a single holder
    92  		// but ERC1155 which can be supported later can have more than one holder and balances > 1
    93  		found := false
    94  		for _, balance := range balances {
    95  			addressCapitalized := strings.ToUpper(balance.Address.String())
    96  			if accountShowcaseVisibility, ok := accountsMap[addressCapitalized]; ok {
    97  				if accountShowcaseVisibility < collectibleProfile.ShowcaseVisibility {
    98  					return errorAccountVisibilityLowerThanCollectible
    99  				}
   100  				found = true
   101  				break
   102  			}
   103  		}
   104  		if !found {
   105  			return errorNoAccountPresentedForCollectible
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func (m *Messenger) validateCommunityMembershipEntry(
   113  	entry *identity.ProfileShowcaseCommunity,
   114  	community *communities.Community,
   115  	contactPubKey *ecdsa.PublicKey) (identity.ProfileShowcaseMembershipStatus, error) {
   116  	if community == nil {
   117  		return identity.ProfileShowcaseMembershipStatusUnproven, nil
   118  	}
   119  
   120  	if community.Encrypted() {
   121  		verifiedGrant, err := community.VerifyGrantSignature(entry.Grant)
   122  		if err != nil {
   123  			m.logger.Warn("failed to verify grant signature ", zap.Error(err))
   124  			return identity.ProfileShowcaseMembershipStatusUnproven, nil
   125  		}
   126  
   127  		if bytes.Equal(verifiedGrant.MemberId, crypto.CompressPubkey(contactPubKey)) {
   128  			return identity.ProfileShowcaseMembershipStatusProvenMember, nil
   129  		}
   130  		// Show as not a member if membership can't be proven
   131  		return identity.ProfileShowcaseMembershipStatusNotAMember, nil
   132  	}
   133  
   134  	if community.HasMember(contactPubKey) {
   135  		return identity.ProfileShowcaseMembershipStatusProvenMember, nil
   136  	}
   137  
   138  	return identity.ProfileShowcaseMembershipStatusNotAMember, nil
   139  }
   140  
   141  func (m *Messenger) validateCommunitiesMembership(communities []*identity.ProfileShowcaseCommunity, contactPubKey *ecdsa.PublicKey) ([]*identity.ProfileShowcaseCommunity, error) {
   142  	validatedCommunities := []*identity.ProfileShowcaseCommunity{}
   143  
   144  	for _, communityEntry := range communities {
   145  		community, err := m.FetchCommunity(&FetchCommunityRequest{
   146  			CommunityKey:    communityEntry.CommunityID,
   147  			Shard:           nil,
   148  			TryDatabase:     true,
   149  			WaitForResponse: true,
   150  		})
   151  		if err != nil {
   152  			m.logger.Warn("failed to fetch community for profile entry ", zap.Error(err))
   153  			continue
   154  		}
   155  
   156  		communityEntry.MembershipStatus, err = m.validateCommunityMembershipEntry(communityEntry, community, contactPubKey)
   157  		if err != nil {
   158  			m.logger.Warn("failed to verify grant signature ", zap.Error(err))
   159  		}
   160  		validatedCommunities = append(validatedCommunities, communityEntry)
   161  	}
   162  
   163  	return validatedCommunities, nil
   164  }
   165  
   166  func (m *Messenger) toProfileShowcaseCommunityProto(preferences []*identity.ProfileShowcaseCommunityPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseCommunity {
   167  	entries := []*protobuf.ProfileShowcaseCommunity{}
   168  	for _, preference := range preferences {
   169  		if preference.ShowcaseVisibility != visibility {
   170  			continue
   171  		}
   172  
   173  		entry := &protobuf.ProfileShowcaseCommunity{
   174  			CommunityId: preference.CommunityID,
   175  			Order:       uint32(preference.Order),
   176  		}
   177  
   178  		community, err := m.communitiesManager.GetByIDString(preference.CommunityID)
   179  		if err != nil {
   180  			m.logger.Warn("failed to get community for profile entry ", zap.Error(err))
   181  		}
   182  
   183  		if community != nil && community.Encrypted() {
   184  			grant, _, err := m.communitiesManager.GetCommunityGrant(preference.CommunityID)
   185  			if err != nil {
   186  				m.logger.Warn("failed to get community for profile entry ", zap.Error(err))
   187  			}
   188  
   189  			entry.Grant = grant
   190  		}
   191  
   192  		entries = append(entries, entry)
   193  	}
   194  	return entries
   195  }
   196  
   197  func (m *Messenger) toProfileShowcaseAccountProto(preferences []*identity.ProfileShowcaseAccountPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseAccount {
   198  	entries := []*protobuf.ProfileShowcaseAccount{}
   199  	for _, preference := range preferences {
   200  		if preference.ShowcaseVisibility != visibility {
   201  			continue
   202  		}
   203  
   204  		account, err := m.settings.GetAccountByAddress(types.HexToAddress(preference.Address))
   205  		if err != nil {
   206  			m.logger.Warn("failed to get account for profile entry ", zap.Error(err))
   207  		}
   208  		if account == nil {
   209  			m.logger.Warn("can not find wallet account for profile entry ")
   210  			continue
   211  		}
   212  
   213  		entries = append(entries, &protobuf.ProfileShowcaseAccount{
   214  			Address: preference.Address,
   215  			Name:    account.Name,
   216  			ColorId: string(account.ColorID),
   217  			Emoji:   account.Emoji,
   218  			Order:   uint32(preference.Order),
   219  		})
   220  	}
   221  	return entries
   222  }
   223  
   224  func (m *Messenger) toProfileShowcaseCollectibleProto(preferences []*identity.ProfileShowcaseCollectiblePreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseCollectible {
   225  	entries := []*protobuf.ProfileShowcaseCollectible{}
   226  	for _, preference := range preferences {
   227  		if preference.ShowcaseVisibility != visibility {
   228  			continue
   229  		}
   230  
   231  		entries = append(entries, &protobuf.ProfileShowcaseCollectible{
   232  			ContractAddress: preference.ContractAddress,
   233  			ChainId:         preference.ChainID,
   234  			TokenId:         preference.TokenID,
   235  			Order:           uint32(preference.Order),
   236  		})
   237  	}
   238  	return entries
   239  }
   240  
   241  func (m *Messenger) toProfileShowcaseVerifiedTokensProto(preferences []*identity.ProfileShowcaseVerifiedTokenPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseVerifiedToken {
   242  	entries := []*protobuf.ProfileShowcaseVerifiedToken{}
   243  	for _, preference := range preferences {
   244  		if preference.ShowcaseVisibility != visibility {
   245  			continue
   246  		}
   247  
   248  		entries = append(entries, &protobuf.ProfileShowcaseVerifiedToken{
   249  			Symbol: preference.Symbol,
   250  			Order:  uint32(preference.Order),
   251  		})
   252  	}
   253  	return entries
   254  }
   255  
   256  func (m *Messenger) toProfileShowcaseUnverifiedTokensProto(preferences []*identity.ProfileShowcaseUnverifiedTokenPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseUnverifiedToken {
   257  	entries := []*protobuf.ProfileShowcaseUnverifiedToken{}
   258  	for _, preference := range preferences {
   259  		if preference.ShowcaseVisibility != visibility {
   260  			continue
   261  		}
   262  
   263  		entries = append(entries, &protobuf.ProfileShowcaseUnverifiedToken{
   264  			ContractAddress: preference.ContractAddress,
   265  			ChainId:         preference.ChainID,
   266  			Order:           uint32(preference.Order),
   267  		})
   268  	}
   269  	return entries
   270  }
   271  
   272  func (m *Messenger) toProfileShowcaseSocialLinksProto(preferences []*identity.ProfileShowcaseSocialLinkPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseSocialLink {
   273  	entries := []*protobuf.ProfileShowcaseSocialLink{}
   274  	for _, preference := range preferences {
   275  		if preference.ShowcaseVisibility != visibility {
   276  			continue
   277  		}
   278  
   279  		entries = append(entries, &protobuf.ProfileShowcaseSocialLink{
   280  			Text:  preference.Text,
   281  			Url:   preference.URL,
   282  			Order: uint32(preference.Order),
   283  		})
   284  	}
   285  	return entries
   286  }
   287  
   288  func (m *Messenger) fromProfileShowcaseCommunityProto(senderPubKey *ecdsa.PublicKey, messages []*protobuf.ProfileShowcaseCommunity) []*identity.ProfileShowcaseCommunity {
   289  	// NOTE: no requests to the network are allowed to be made here, called in the receiver thread
   290  	entries := []*identity.ProfileShowcaseCommunity{}
   291  	for _, message := range messages {
   292  		entry := &identity.ProfileShowcaseCommunity{
   293  			CommunityID: message.CommunityId,
   294  			Order:       int(message.Order),
   295  			Grant:       message.Grant,
   296  		}
   297  
   298  		entries = append(entries, entry)
   299  	}
   300  	return entries
   301  }
   302  
   303  func (m *Messenger) fromProfileShowcaseAccountProto(messages []*protobuf.ProfileShowcaseAccount) []*identity.ProfileShowcaseAccount {
   304  	// NOTE: no requests to the network are allowed to be made here, called in the receiver thread
   305  	entries := []*identity.ProfileShowcaseAccount{}
   306  	for _, entry := range messages {
   307  		entries = append(entries, &identity.ProfileShowcaseAccount{
   308  			Address: entry.Address,
   309  			Name:    entry.Name,
   310  			ColorID: entry.ColorId,
   311  			Emoji:   entry.Emoji,
   312  			Order:   int(entry.Order),
   313  		})
   314  	}
   315  	return entries
   316  }
   317  
   318  func (m *Messenger) fromProfileShowcaseCollectibleProto(messages []*protobuf.ProfileShowcaseCollectible) []*identity.ProfileShowcaseCollectible {
   319  	// NOTE: no requests to the network are allowed to be made here, called in the receiver thread
   320  	entries := []*identity.ProfileShowcaseCollectible{}
   321  	for _, message := range messages {
   322  		entry := &identity.ProfileShowcaseCollectible{
   323  			ContractAddress: message.ContractAddress,
   324  			ChainID:         message.ChainId,
   325  			TokenID:         message.TokenId,
   326  			Order:           int(message.Order),
   327  		}
   328  		entries = append(entries, entry)
   329  	}
   330  	return entries
   331  }
   332  
   333  func (m *Messenger) fromProfileShowcaseVerifiedTokenProto(messages []*protobuf.ProfileShowcaseVerifiedToken) []*identity.ProfileShowcaseVerifiedToken {
   334  	// NOTE: no requests to the network are allowed to be made here, called in the receiver thread
   335  	entries := []*identity.ProfileShowcaseVerifiedToken{}
   336  	for _, entry := range messages {
   337  		entries = append(entries, &identity.ProfileShowcaseVerifiedToken{
   338  			Symbol: entry.Symbol,
   339  			Order:  int(entry.Order),
   340  		})
   341  	}
   342  	return entries
   343  }
   344  
   345  func (m *Messenger) fromProfileShowcaseUnverifiedTokenProto(messages []*protobuf.ProfileShowcaseUnverifiedToken) []*identity.ProfileShowcaseUnverifiedToken {
   346  	// NOTE: no requests to the network are allowed to be made here, called in the receiver thread
   347  	entries := []*identity.ProfileShowcaseUnverifiedToken{}
   348  	for _, entry := range messages {
   349  		entries = append(entries, &identity.ProfileShowcaseUnverifiedToken{
   350  			ContractAddress: entry.ContractAddress,
   351  			ChainID:         entry.ChainId,
   352  			Order:           int(entry.Order),
   353  		})
   354  	}
   355  	return entries
   356  }
   357  
   358  func (m *Messenger) fromProfileShowcaseSocialLinkProto(messages []*protobuf.ProfileShowcaseSocialLink) []*identity.ProfileShowcaseSocialLink {
   359  	// NOTE: no requests to the network are allowed to be made here, called in the receiver thread
   360  	entries := []*identity.ProfileShowcaseSocialLink{}
   361  	for _, entry := range messages {
   362  		entries = append(entries, &identity.ProfileShowcaseSocialLink{
   363  			Text:  entry.Text,
   364  			URL:   entry.Url,
   365  			Order: int(entry.Order),
   366  		})
   367  	}
   368  	return entries
   369  }
   370  
   371  func (m *Messenger) SetProfileShowcasePreferences(preferences *identity.ProfileShowcasePreferences, sync bool) error {
   372  	clock, _ := m.getLastClockWithRelatedChat()
   373  	preferences.Clock = clock
   374  	return m.setProfileShowcasePreferences(preferences, sync)
   375  }
   376  
   377  func (m *Messenger) setProfileShowcasePreferences(preferences *identity.ProfileShowcasePreferences, sync bool) error {
   378  	err := identity.Validate(preferences)
   379  	if err != nil {
   380  		return err
   381  	}
   382  
   383  	err = m.validateCollectiblesOwnership(preferences.Accounts, preferences.Collectibles)
   384  	if err != nil {
   385  		return err
   386  	}
   387  
   388  	err = m.persistence.SaveProfileShowcasePreferences(preferences)
   389  	if err != nil {
   390  		return err
   391  	}
   392  
   393  	if sync {
   394  		err = m.syncProfileShowcasePreferences(context.Background(), m.dispatchMessage)
   395  		if err != nil {
   396  			return err
   397  		}
   398  	}
   399  
   400  	return m.DispatchProfileShowcase()
   401  }
   402  
   403  func (m *Messenger) DispatchProfileShowcase() error {
   404  	err := m.publishContactCode()
   405  	if err != nil {
   406  		return err
   407  	}
   408  	return nil
   409  }
   410  
   411  func (m *Messenger) GetProfileShowcasePreferences() (*identity.ProfileShowcasePreferences, error) {
   412  	return m.persistence.GetProfileShowcasePreferences()
   413  }
   414  
   415  func (m *Messenger) GetProfileShowcaseForContact(contactID string, validate bool) (*identity.ProfileShowcase, error) {
   416  	profileShowcase, err := m.persistence.GetProfileShowcaseForContact(contactID)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  
   421  	if !validate {
   422  		return profileShowcase, nil
   423  	}
   424  
   425  	contactPubKey, err := common.HexToPubkey(contactID)
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  
   430  	profileShowcase.Communities, err = m.validateCommunitiesMembership(profileShowcase.Communities, contactPubKey)
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  
   435  	return profileShowcase, nil
   436  }
   437  
   438  func (m *Messenger) GetProfileShowcaseAccountsByAddress(address string) ([]*identity.ProfileShowcaseAccount, error) {
   439  	return m.persistence.GetProfileShowcaseAccountsByAddress(address)
   440  }
   441  
   442  func (m *Messenger) GetProfileShowcaseSocialLinksLimit() (int, error) {
   443  	return identity.MaxProfileShowcaseSocialLinksLimit, nil
   444  }
   445  
   446  func (m *Messenger) GetProfileShowcaseEntriesLimit() (int, error) {
   447  	return identity.MaxProfileShowcaseEntriesLimit, nil
   448  }
   449  
   450  func (m *Messenger) EncryptProfileShowcaseEntriesWithContactPubKeys(entries *protobuf.ProfileShowcaseEntries, contacts []*Contact) (*protobuf.ProfileShowcaseEntriesEncrypted, error) {
   451  	// Make AES key
   452  	AESKey := make([]byte, 32)
   453  	_, err := crand.Read(AESKey)
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  
   458  	// Encrypt showcase entries with the AES key
   459  	data, err := proto.Marshal(entries)
   460  	if err != nil {
   461  		return nil, err
   462  	}
   463  
   464  	encrypted, err := common.Encrypt(data, AESKey, crand.Reader)
   465  	if err != nil {
   466  		return nil, err
   467  	}
   468  
   469  	eAESKeys := [][]byte{}
   470  	// Sign for each contact
   471  	for _, contact := range contacts {
   472  		var pubK *ecdsa.PublicKey
   473  		var sharedKey []byte
   474  		var eAESKey []byte
   475  
   476  		pubK, err = contact.PublicKey()
   477  		if err != nil {
   478  			return nil, err
   479  		}
   480  		// Generate a Diffie-Helman (DH) between the sender private key and the recipient's public key
   481  		sharedKey, err = common.MakeECDHSharedKey(m.identity, pubK)
   482  		if err != nil {
   483  			return nil, err
   484  		}
   485  
   486  		// Encrypt the main AES key with AES encryption using the DH key
   487  		eAESKey, err = common.Encrypt(AESKey, sharedKey, crand.Reader)
   488  		if err != nil {
   489  			return nil, err
   490  		}
   491  
   492  		eAESKeys = append(eAESKeys, eAESKey)
   493  	}
   494  
   495  	return &protobuf.ProfileShowcaseEntriesEncrypted{
   496  		EncryptedEntries: encrypted,
   497  		EncryptionKeys:   eAESKeys,
   498  	}, nil
   499  }
   500  
   501  func (m *Messenger) DecryptProfileShowcaseEntriesWithPubKey(senderPubKey *ecdsa.PublicKey, encrypted *protobuf.ProfileShowcaseEntriesEncrypted) (*protobuf.ProfileShowcaseEntries, error) {
   502  	for _, eAESKey := range encrypted.EncryptionKeys {
   503  		// Generate a Diffie-Helman (DH) between the recipient's private key and the sender's public key
   504  		sharedKey, err := common.MakeECDHSharedKey(m.identity, senderPubKey)
   505  		if err != nil {
   506  			return nil, err
   507  		}
   508  
   509  		// Decrypt the main encryption AES key with AES encryption using the DH key
   510  		dAESKey, err := common.Decrypt(eAESKey, sharedKey)
   511  		if err != nil {
   512  			if err.Error() == ErrCipherMessageAutentificationFailed {
   513  				continue
   514  			}
   515  			return nil, err
   516  		}
   517  		if dAESKey == nil {
   518  			return nil, errorDecryptingPayloadEncryptionKey
   519  		}
   520  
   521  		// Decrypt profile entries with the newly decrypted main encryption AES key
   522  		entriesData, err := common.Decrypt(encrypted.EncryptedEntries, dAESKey)
   523  		if err != nil {
   524  			return nil, err
   525  		}
   526  
   527  		entries := &protobuf.ProfileShowcaseEntries{}
   528  		err = proto.Unmarshal(entriesData, entries)
   529  		if err != nil {
   530  			return nil, err
   531  		}
   532  
   533  		return entries, nil
   534  	}
   535  
   536  	// Return empty if no matching key found
   537  	return &protobuf.ProfileShowcaseEntries{}, nil
   538  }
   539  
   540  func (m *Messenger) GetProfileShowcaseForSelfIdentity() (*protobuf.ProfileShowcase, error) {
   541  	preferences, err := m.GetProfileShowcasePreferences()
   542  	if err != nil {
   543  		return nil, err
   544  	}
   545  
   546  	forEveryone := &protobuf.ProfileShowcaseEntries{
   547  		Communities:      m.toProfileShowcaseCommunityProto(preferences.Communities, identity.ProfileShowcaseVisibilityEveryone),
   548  		Accounts:         m.toProfileShowcaseAccountProto(preferences.Accounts, identity.ProfileShowcaseVisibilityEveryone),
   549  		Collectibles:     m.toProfileShowcaseCollectibleProto(preferences.Collectibles, identity.ProfileShowcaseVisibilityEveryone),
   550  		VerifiedTokens:   m.toProfileShowcaseVerifiedTokensProto(preferences.VerifiedTokens, identity.ProfileShowcaseVisibilityEveryone),
   551  		UnverifiedTokens: m.toProfileShowcaseUnverifiedTokensProto(preferences.UnverifiedTokens, identity.ProfileShowcaseVisibilityEveryone),
   552  		SocialLinks:      m.toProfileShowcaseSocialLinksProto(preferences.SocialLinks, identity.ProfileShowcaseVisibilityEveryone),
   553  	}
   554  
   555  	forContacts := &protobuf.ProfileShowcaseEntries{
   556  		Communities:      m.toProfileShowcaseCommunityProto(preferences.Communities, identity.ProfileShowcaseVisibilityContacts),
   557  		Accounts:         m.toProfileShowcaseAccountProto(preferences.Accounts, identity.ProfileShowcaseVisibilityContacts),
   558  		Collectibles:     m.toProfileShowcaseCollectibleProto(preferences.Collectibles, identity.ProfileShowcaseVisibilityContacts),
   559  		VerifiedTokens:   m.toProfileShowcaseVerifiedTokensProto(preferences.VerifiedTokens, identity.ProfileShowcaseVisibilityContacts),
   560  		UnverifiedTokens: m.toProfileShowcaseUnverifiedTokensProto(preferences.UnverifiedTokens, identity.ProfileShowcaseVisibilityContacts),
   561  		SocialLinks:      m.toProfileShowcaseSocialLinksProto(preferences.SocialLinks, identity.ProfileShowcaseVisibilityContacts),
   562  	}
   563  
   564  	forIDVerifiedContacts := &protobuf.ProfileShowcaseEntries{
   565  		Communities:      m.toProfileShowcaseCommunityProto(preferences.Communities, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
   566  		Accounts:         m.toProfileShowcaseAccountProto(preferences.Accounts, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
   567  		Collectibles:     m.toProfileShowcaseCollectibleProto(preferences.Collectibles, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
   568  		VerifiedTokens:   m.toProfileShowcaseVerifiedTokensProto(preferences.VerifiedTokens, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
   569  		UnverifiedTokens: m.toProfileShowcaseUnverifiedTokensProto(preferences.UnverifiedTokens, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
   570  		SocialLinks:      m.toProfileShowcaseSocialLinksProto(preferences.SocialLinks, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
   571  	}
   572  
   573  	mutualContacts := []*Contact{}
   574  	iDVerifiedContacts := []*Contact{}
   575  
   576  	m.allContacts.Range(func(_ string, contact *Contact) (shouldContinue bool) {
   577  		if contact.mutual() {
   578  			mutualContacts = append(mutualContacts, contact)
   579  			if contact.IsVerified() {
   580  				iDVerifiedContacts = append(iDVerifiedContacts, contact)
   581  			}
   582  		}
   583  		return true
   584  	})
   585  
   586  	forContactsEncrypted, err := m.EncryptProfileShowcaseEntriesWithContactPubKeys(forContacts, mutualContacts)
   587  	if err != nil {
   588  		return nil, err
   589  	}
   590  
   591  	forIDVerifiedContactsEncrypted, err := m.EncryptProfileShowcaseEntriesWithContactPubKeys(forIDVerifiedContacts, iDVerifiedContacts)
   592  	if err != nil {
   593  		return nil, err
   594  	}
   595  
   596  	return &protobuf.ProfileShowcase{
   597  		ForEveryone:           forEveryone,
   598  		ForContacts:           forContactsEncrypted,
   599  		ForIdVerifiedContacts: forIDVerifiedContactsEncrypted,
   600  	}, nil
   601  }
   602  
   603  func (m *Messenger) BuildProfileShowcaseFromIdentity(state *ReceivedMessageState, message *protobuf.ProfileShowcase) error {
   604  	senderPubKey := state.CurrentMessageState.PublicKey
   605  	contactID := state.CurrentMessageState.Contact.ID
   606  
   607  	communities := []*identity.ProfileShowcaseCommunity{}
   608  	accounts := []*identity.ProfileShowcaseAccount{}
   609  	collectibles := []*identity.ProfileShowcaseCollectible{}
   610  	verifiedTokens := []*identity.ProfileShowcaseVerifiedToken{}
   611  	unverifiedTokens := []*identity.ProfileShowcaseUnverifiedToken{}
   612  	socialLinks := []*identity.ProfileShowcaseSocialLink{}
   613  
   614  	communities = append(communities, m.fromProfileShowcaseCommunityProto(senderPubKey, message.ForEveryone.Communities)...)
   615  	accounts = append(accounts, m.fromProfileShowcaseAccountProto(message.ForEveryone.Accounts)...)
   616  	collectibles = append(collectibles, m.fromProfileShowcaseCollectibleProto(message.ForEveryone.Collectibles)...)
   617  	verifiedTokens = append(verifiedTokens, m.fromProfileShowcaseVerifiedTokenProto(message.ForEveryone.VerifiedTokens)...)
   618  	unverifiedTokens = append(unverifiedTokens, m.fromProfileShowcaseUnverifiedTokenProto(message.ForEveryone.UnverifiedTokens)...)
   619  	socialLinks = append(socialLinks, m.fromProfileShowcaseSocialLinkProto(message.ForEveryone.SocialLinks)...)
   620  
   621  	forContacts, err := m.DecryptProfileShowcaseEntriesWithPubKey(senderPubKey, message.ForContacts)
   622  	if err != nil {
   623  		return err
   624  	}
   625  
   626  	if forContacts != nil {
   627  		communities = append(communities, m.fromProfileShowcaseCommunityProto(senderPubKey, forContacts.Communities)...)
   628  		accounts = append(accounts, m.fromProfileShowcaseAccountProto(forContacts.Accounts)...)
   629  		collectibles = append(collectibles, m.fromProfileShowcaseCollectibleProto(forContacts.Collectibles)...)
   630  		verifiedTokens = append(verifiedTokens, m.fromProfileShowcaseVerifiedTokenProto(forContacts.VerifiedTokens)...)
   631  		unverifiedTokens = append(unverifiedTokens, m.fromProfileShowcaseUnverifiedTokenProto(forContacts.UnverifiedTokens)...)
   632  		socialLinks = append(socialLinks, m.fromProfileShowcaseSocialLinkProto(forContacts.SocialLinks)...)
   633  	}
   634  
   635  	forIDVerifiedContacts, err := m.DecryptProfileShowcaseEntriesWithPubKey(senderPubKey, message.ForIdVerifiedContacts)
   636  	if err != nil {
   637  		return err
   638  	}
   639  
   640  	if forIDVerifiedContacts != nil {
   641  		communities = append(communities, m.fromProfileShowcaseCommunityProto(senderPubKey, forIDVerifiedContacts.Communities)...)
   642  		accounts = append(accounts, m.fromProfileShowcaseAccountProto(forIDVerifiedContacts.Accounts)...)
   643  		collectibles = append(collectibles, m.fromProfileShowcaseCollectibleProto(forIDVerifiedContacts.Collectibles)...)
   644  		verifiedTokens = append(verifiedTokens, m.fromProfileShowcaseVerifiedTokenProto(forIDVerifiedContacts.VerifiedTokens)...)
   645  		unverifiedTokens = append(unverifiedTokens, m.fromProfileShowcaseUnverifiedTokenProto(forIDVerifiedContacts.UnverifiedTokens)...)
   646  		socialLinks = append(socialLinks, m.fromProfileShowcaseSocialLinkProto(forIDVerifiedContacts.SocialLinks)...)
   647  	}
   648  
   649  	sortProfileEntyByOrder(communities, func(i int) int { return communities[i].Order })
   650  	sortProfileEntyByOrder(accounts, func(i int) int { return accounts[i].Order })
   651  	sortProfileEntyByOrder(collectibles, func(i int) int { return collectibles[i].Order })
   652  	sortProfileEntyByOrder(verifiedTokens, func(i int) int { return verifiedTokens[i].Order })
   653  	sortProfileEntyByOrder(unverifiedTokens, func(i int) int { return unverifiedTokens[i].Order })
   654  	sortProfileEntyByOrder(socialLinks, func(i int) int { return socialLinks[i].Order })
   655  
   656  	newShowcase := &identity.ProfileShowcase{
   657  		ContactID:        contactID,
   658  		Communities:      communities,
   659  		Accounts:         accounts,
   660  		Collectibles:     collectibles,
   661  		VerifiedTokens:   verifiedTokens,
   662  		UnverifiedTokens: unverifiedTokens,
   663  		SocialLinks:      socialLinks,
   664  	}
   665  
   666  	oldShowcase, err := m.persistence.GetProfileShowcaseForContact(contactID)
   667  	if err != nil {
   668  		return err
   669  	}
   670  
   671  	if reflect.DeepEqual(newShowcase, oldShowcase) {
   672  		return nil
   673  	}
   674  
   675  	err = m.persistence.ClearProfileShowcaseForContact(contactID)
   676  	if err != nil {
   677  		return err
   678  	}
   679  
   680  	err = m.persistence.SaveProfileShowcaseForContact(newShowcase)
   681  	if err != nil {
   682  		return err
   683  	}
   684  
   685  	return nil
   686  }
   687  
   688  func (m *Messenger) UpdateProfileShowcaseWalletAccount(account *accounts.Account) error {
   689  	profileAccount, err := m.persistence.GetProfileShowcaseAccountPreference(account.Address.Hex())
   690  	if err != nil {
   691  		return err
   692  	}
   693  
   694  	if profileAccount == nil {
   695  		// No corresponding profile entry, exit
   696  		return nil
   697  	}
   698  
   699  	return m.DispatchProfileShowcase()
   700  }
   701  
   702  func (m *Messenger) DeleteProfileShowcaseWalletAccount(account *accounts.Account) error {
   703  	deleted, err := m.persistence.DeleteProfileShowcaseAccountPreference(account.Address.Hex())
   704  	if err != nil {
   705  		return err
   706  	}
   707  
   708  	if deleted {
   709  		return m.DispatchProfileShowcase()
   710  	}
   711  	return nil
   712  }
   713  
   714  func (m *Messenger) UpdateProfileShowcaseCommunity(community *communities.Community) error {
   715  	profileCommunity, err := m.persistence.GetProfileShowcaseCommunityPreference(community.IDString())
   716  	if err != nil {
   717  		return err
   718  	}
   719  
   720  	if profileCommunity == nil {
   721  		// No corresponding profile entry, exit
   722  		return nil
   723  	}
   724  	return m.DispatchProfileShowcase()
   725  }
   726  
   727  func (m *Messenger) DeleteProfileShowcaseCommunity(community *communities.Community) error {
   728  	deleted, err := m.persistence.DeleteProfileShowcaseCommunityPreference(community.IDString())
   729  	if err != nil {
   730  		return err
   731  	}
   732  
   733  	if deleted {
   734  		return m.DispatchProfileShowcase()
   735  	}
   736  	return nil
   737  }
   738  
   739  func (m *Messenger) saveProfileShowcasePreferencesProto(p *protobuf.SyncProfileShowcasePreferences, shouldSync bool) (*identity.ProfileShowcasePreferences, error) {
   740  	if p == nil {
   741  		return nil, nil
   742  	}
   743  	preferences := FromProfileShowcasePreferencesProto(p)
   744  	return preferences, m.setProfileShowcasePreferences(preferences, shouldSync)
   745  }
   746  
   747  func (m *Messenger) syncProfileShowcasePreferences(ctx context.Context, rawMessageHandler RawMessageHandler) error {
   748  	preferences, err := m.GetProfileShowcasePreferences()
   749  	if err != nil {
   750  		return err
   751  	}
   752  
   753  	syncMessage := ToProfileShowcasePreferencesProto(preferences)
   754  	encodedMessage, err := proto.Marshal(syncMessage)
   755  	if err != nil {
   756  		return err
   757  	}
   758  
   759  	_, chat := m.getLastClockWithRelatedChat()
   760  	rawMessage := common.RawMessage{
   761  		LocalChatID: chat.ID,
   762  		Payload:     encodedMessage,
   763  		MessageType: protobuf.ApplicationMetadataMessage_SYNC_PROFILE_SHOWCASE_PREFERENCES,
   764  		ResendType:  common.ResendTypeDataSync,
   765  	}
   766  
   767  	_, err = rawMessageHandler(ctx, rawMessage)
   768  	return err
   769  }