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 }