github.com/status-im/status-go@v1.1.0/protocol/communities/permission_checker.go (about) 1 package communities 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math/big" 8 "strconv" 9 "strings" 10 11 "go.uber.org/zap" 12 13 maps "golang.org/x/exp/maps" 14 slices "golang.org/x/exp/slices" 15 16 gethcommon "github.com/ethereum/go-ethereum/common" 17 "github.com/ethereum/go-ethereum/common/hexutil" 18 "github.com/status-im/status-go/protocol/ens" 19 "github.com/status-im/status-go/protocol/protobuf" 20 walletcommon "github.com/status-im/status-go/services/wallet/common" 21 "github.com/status-im/status-go/services/wallet/thirdparty" 22 ) 23 24 type PermissionChecker interface { 25 CheckPermissionToJoin(*Community, []gethcommon.Address) (*CheckPermissionToJoinResponse, error) 26 CheckPermissions(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) 27 CheckPermissionsWithPreFetchedData(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool, collectiblesOwners CollectiblesOwners) (*CheckPermissionsResponse, error) 28 } 29 30 type DefaultPermissionChecker struct { 31 tokenManager TokenManager 32 collectiblesManager CollectiblesManager 33 ensVerifier *ens.Verifier 34 35 logger *zap.Logger 36 } 37 38 type PreParsedPermissionsData struct { 39 Erc721TokenRequirements map[uint64]map[string]*protobuf.TokenCriteria 40 Erc20TokenAddresses []gethcommon.Address 41 Erc20ChainIDsMap map[uint64]bool 42 Erc721ChainIDsMap map[uint64]bool 43 } 44 45 type PreParsedCommunityPermissionsData struct { 46 *PreParsedPermissionsData 47 Permissions []*CommunityTokenPermission 48 } 49 50 func (p *DefaultPermissionChecker) getOwnedENS(addresses []gethcommon.Address) ([]string, error) { 51 ownedENS := make([]string, 0) 52 if p.ensVerifier == nil { 53 p.logger.Warn("no ensVerifier configured for communities manager") 54 return ownedENS, nil 55 } 56 for _, address := range addresses { 57 name, err := p.ensVerifier.ReverseResolve(address) 58 if err != nil && err.Error() != "not a resolver" { 59 return ownedENS, err 60 } 61 if name != "" { 62 ownedENS = append(ownedENS, name) 63 } 64 } 65 return ownedENS, nil 66 } 67 68 type collectiblesBalancesGetter = func(ctx context.Context, chainID walletcommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) 69 70 func (p *DefaultPermissionChecker) getOwnedERC721Tokens(walletAddresses []gethcommon.Address, tokenRequirements map[uint64]map[string]*protobuf.TokenCriteria, chainIDs []uint64, getCollectiblesBalances collectiblesBalancesGetter) (CollectiblesByChain, error) { 71 if p.collectiblesManager == nil { 72 return nil, errors.New("no collectibles manager") 73 } 74 75 ctx := context.Background() 76 77 ownedERC721Tokens := make(CollectiblesByChain) 78 79 for chainID, erc721Tokens := range tokenRequirements { 80 81 skipChain := true 82 for _, cID := range chainIDs { 83 if chainID == cID { 84 skipChain = false 85 } 86 } 87 88 if skipChain { 89 continue 90 } 91 92 contractAddresses := make([]gethcommon.Address, 0) 93 for contractAddress := range erc721Tokens { 94 contractAddresses = append(contractAddresses, gethcommon.HexToAddress(contractAddress)) 95 } 96 97 if _, exists := ownedERC721Tokens[chainID]; !exists { 98 ownedERC721Tokens[chainID] = make(map[gethcommon.Address]thirdparty.TokenBalancesPerContractAddress) 99 } 100 101 for _, owner := range walletAddresses { 102 balances, err := getCollectiblesBalances(ctx, walletcommon.ChainID(chainID), owner, contractAddresses) 103 if err != nil { 104 p.logger.Info("couldn't fetch owner assets", zap.Error(err)) 105 return nil, err 106 } 107 ownedERC721Tokens[chainID][owner] = balances 108 } 109 } 110 return ownedERC721Tokens, nil 111 } 112 113 func (p *DefaultPermissionChecker) accountChainsCombinationToMap(combinations []*AccountChainIDsCombination) map[gethcommon.Address][]uint64 { 114 result := make(map[gethcommon.Address][]uint64) 115 for _, combination := range combinations { 116 result[combination.Address] = combination.ChainIDs 117 } 118 return result 119 } 120 121 // merge valid combinations w/o duplicates 122 func (p *DefaultPermissionChecker) MergeValidCombinations(left, right []*AccountChainIDsCombination) []*AccountChainIDsCombination { 123 124 leftMap := p.accountChainsCombinationToMap(left) 125 rightMap := p.accountChainsCombinationToMap(right) 126 127 // merge maps, result in left map 128 for k, v := range rightMap { 129 if _, exists := leftMap[k]; !exists { 130 leftMap[k] = v 131 continue 132 } else { 133 // append chains which are new 134 chains := leftMap[k] 135 for _, chainID := range v { 136 if !slices.Contains(chains, chainID) { 137 chains = append(chains, chainID) 138 } 139 } 140 leftMap[k] = chains 141 } 142 } 143 144 result := []*AccountChainIDsCombination{} 145 for k, v := range leftMap { 146 result = append(result, &AccountChainIDsCombination{ 147 Address: k, 148 ChainIDs: v, 149 }) 150 } 151 152 return result 153 } 154 155 func (p *DefaultPermissionChecker) CheckPermissionToJoin(community *Community, addresses []gethcommon.Address) (*CheckPermissionToJoinResponse, error) { 156 becomeAdminPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_ADMIN) 157 becomeMemberPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER) 158 becomeTokenMasterPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER) 159 160 adminOrTokenMasterPermissionsToJoin := append(becomeAdminPermissions, becomeTokenMasterPermissions...) 161 162 allChainIDs, err := p.tokenManager.GetAllChainIDs() 163 if err != nil { 164 return nil, err 165 } 166 167 accountsAndChainIDs := combineAddressesAndChainIDs(addresses, allChainIDs) 168 169 // Check becomeMember and (admin & token master) permissions separately. 170 becomeMemberPermissionsResponse, err := p.checkPermissionsOrDefault(becomeMemberPermissions, accountsAndChainIDs) 171 if err != nil { 172 return nil, err 173 } 174 175 if len(adminOrTokenMasterPermissionsToJoin) <= 0 { 176 return becomeMemberPermissionsResponse, nil 177 } 178 // If there are any admin or token master permissions, combine result. 179 preParsedPermissions := preParsedCommunityPermissionsData(adminOrTokenMasterPermissionsToJoin) 180 var adminOrTokenPermissionsResponse *CheckPermissionsResponse 181 182 if community.IsControlNode() { 183 adminOrTokenPermissionsResponse, err = p.CheckPermissions(preParsedPermissions, accountsAndChainIDs, false) 184 } else { 185 adminOrTokenPermissionsResponse, err = p.CheckCachedPermissions(preParsedPermissions, accountsAndChainIDs, false) 186 } 187 if err != nil { 188 return nil, err 189 } 190 191 mergedPermissions := make(map[string]*PermissionTokenCriteriaResult) 192 maps.Copy(mergedPermissions, becomeMemberPermissionsResponse.Permissions) 193 maps.Copy(mergedPermissions, adminOrTokenPermissionsResponse.Permissions) 194 195 mergedCombinations := p.MergeValidCombinations(becomeMemberPermissionsResponse.ValidCombinations, adminOrTokenPermissionsResponse.ValidCombinations) 196 197 combinedResponse := &CheckPermissionsResponse{ 198 Satisfied: becomeMemberPermissionsResponse.Satisfied || adminOrTokenPermissionsResponse.Satisfied, 199 Permissions: mergedPermissions, 200 ValidCombinations: mergedCombinations, 201 } 202 203 return combinedResponse, nil 204 } 205 206 func (p *DefaultPermissionChecker) checkPermissionsOrDefault(permissions []*CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination) (*CheckPermissionsResponse, error) { 207 if len(permissions) == 0 { 208 // There are no permissions to join on this community at the moment, 209 // so we reveal all accounts + all chain IDs 210 response := &CheckPermissionsResponse{ 211 Satisfied: true, 212 Permissions: make(map[string]*PermissionTokenCriteriaResult), 213 ValidCombinations: accountsAndChainIDs, 214 } 215 return response, nil 216 } 217 218 preParsedPermissions := preParsedCommunityPermissionsData(permissions) 219 return p.CheckCachedPermissions(preParsedPermissions, accountsAndChainIDs, false) 220 } 221 222 type ownedERC721TokensGetter = func(walletAddresses []gethcommon.Address, tokenRequirements map[uint64]map[string]*protobuf.TokenCriteria, chainIDs []uint64) (CollectiblesByChain, error) 223 type balancesByChainGetter = func(ctx context.Context, accounts, tokens []gethcommon.Address, chainIDs []uint64) (BalancesByChain, error) 224 225 func (p *DefaultPermissionChecker) checkTokenRequirement( 226 tokenRequirement *protobuf.TokenCriteria, 227 accounts []gethcommon.Address, ownedERC20TokenBalances BalancesByChain, ownedERC721Tokens CollectiblesByChain, 228 accountsChainIDsCombinations map[gethcommon.Address]map[uint64]bool, 229 ) (TokenRequirementResponse, error) { 230 tokenRequirementResponse := TokenRequirementResponse{TokenCriteria: tokenRequirement} 231 232 switch tokenRequirement.Type { 233 234 case protobuf.CommunityTokenType_ERC721: 235 236 if len(ownedERC721Tokens) == 0 { 237 return tokenRequirementResponse, nil 238 } 239 240 // Limit NFTs count to uint32 241 requiredCount, err := strconv.ParseUint(tokenRequirement.AmountInWei, 10, 32) 242 if err != nil { 243 return tokenRequirementResponse, fmt.Errorf("invalid ERC721 amount: %s", tokenRequirement.AmountInWei) 244 } 245 accumulatedCount := uint64(0) 246 247 for chainID, addressStr := range tokenRequirement.ContractAddresses { 248 contractAddress := gethcommon.HexToAddress(addressStr) 249 if _, exists := ownedERC721Tokens[chainID]; !exists || len(ownedERC721Tokens[chainID]) == 0 { 250 continue 251 } 252 253 for account := range ownedERC721Tokens[chainID] { 254 if _, exists := ownedERC721Tokens[chainID][account]; !exists { 255 continue 256 } 257 258 tokenBalances := ownedERC721Tokens[chainID][account][contractAddress] 259 accumulatedCount += uint64(len(tokenBalances)) 260 261 if len(tokenBalances) > 0 { 262 // 'account' owns some TokenID owned from contract 'address' 263 if _, exists := accountsChainIDsCombinations[account]; !exists { 264 accountsChainIDsCombinations[account] = make(map[uint64]bool) 265 } 266 267 // account has balance > 0 on this chain for this token, so let's add it the chain IDs 268 accountsChainIDsCombinations[account][chainID] = true 269 270 if len(tokenRequirement.TokenIds) == 0 { 271 // no specific tokenId of this collection is needed 272 273 if accumulatedCount >= requiredCount { 274 tokenRequirementResponse.Satisfied = true 275 return tokenRequirementResponse, nil 276 } 277 } 278 279 for _, tokenID := range tokenRequirement.TokenIds { 280 tokenIDBigInt := new(big.Int).SetUint64(tokenID) 281 282 for _, asset := range tokenBalances { 283 if asset.TokenID.Cmp(tokenIDBigInt) == 0 && asset.Balance.Sign() > 0 { 284 tokenRequirementResponse.Satisfied = true 285 return tokenRequirementResponse, nil 286 } 287 } 288 } 289 } 290 } 291 } 292 293 case protobuf.CommunityTokenType_ERC20: 294 295 if len(ownedERC20TokenBalances) == 0 { 296 return tokenRequirementResponse, nil 297 } 298 299 accumulatedBalance := new(big.Int) 300 301 chainIDLoopERC20: 302 for chainID, address := range tokenRequirement.ContractAddresses { 303 if _, exists := ownedERC20TokenBalances[chainID]; !exists || len(ownedERC20TokenBalances[chainID]) == 0 { 304 continue chainIDLoopERC20 305 } 306 contractAddress := gethcommon.HexToAddress(address) 307 for account := range ownedERC20TokenBalances[chainID] { 308 if _, exists := ownedERC20TokenBalances[chainID][account][contractAddress]; !exists { 309 continue 310 } 311 312 value := ownedERC20TokenBalances[chainID][account][contractAddress] 313 314 if _, exists := accountsChainIDsCombinations[account]; !exists { 315 accountsChainIDsCombinations[account] = make(map[uint64]bool) 316 } 317 318 if value.ToInt().Cmp(big.NewInt(0)) > 0 { 319 // account has balance > 0 on this chain for this token, so let's add it the chain IDs 320 accountsChainIDsCombinations[account][chainID] = true 321 } 322 323 // check if adding current chain account balance to accumulated balance 324 // satisfies required amount 325 prevBalance := accumulatedBalance 326 accumulatedBalance.Add(prevBalance, value.ToInt()) 327 328 requiredAmount, success := new(big.Int).SetString(tokenRequirement.AmountInWei, 10) 329 if !success { 330 return tokenRequirementResponse, fmt.Errorf("amountInWeis value is incorrect: %s", tokenRequirement.AmountInWei) 331 } 332 333 if accumulatedBalance.Cmp(requiredAmount) != -1 { 334 tokenRequirementResponse.Satisfied = true 335 return tokenRequirementResponse, nil 336 } 337 } 338 } 339 340 case protobuf.CommunityTokenType_ENS: 341 342 for _, account := range accounts { 343 ownedENSNames, err := p.getOwnedENS([]gethcommon.Address{account}) 344 if err != nil { 345 return tokenRequirementResponse, err 346 } 347 348 if _, exists := accountsChainIDsCombinations[account]; !exists { 349 accountsChainIDsCombinations[account] = make(map[uint64]bool) 350 } 351 352 if !strings.HasPrefix(tokenRequirement.EnsPattern, "*.") { 353 for _, ownedENS := range ownedENSNames { 354 if ownedENS == tokenRequirement.EnsPattern { 355 accountsChainIDsCombinations[account][walletcommon.EthereumMainnet] = true 356 tokenRequirementResponse.Satisfied = true 357 return tokenRequirementResponse, nil 358 } 359 } 360 } else { 361 parentName := tokenRequirement.EnsPattern[2:] 362 for _, ownedENS := range ownedENSNames { 363 if strings.HasSuffix(ownedENS, parentName) { 364 accountsChainIDsCombinations[account][walletcommon.EthereumMainnet] = true 365 tokenRequirementResponse.Satisfied = true 366 return tokenRequirementResponse, nil 367 } 368 } 369 } 370 } 371 372 } 373 374 return tokenRequirementResponse, nil 375 } 376 377 func (p *DefaultPermissionChecker) checkPermissions(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool, 378 getOwnedERC721Tokens ownedERC721TokensGetter, getBalancesByChain balancesByChainGetter) (*CheckPermissionsResponse, error) { 379 380 response := &CheckPermissionsResponse{ 381 Satisfied: false, 382 Permissions: make(map[string]*PermissionTokenCriteriaResult), 383 ValidCombinations: make([]*AccountChainIDsCombination, 0), 384 } 385 386 if permissionsParsedData == nil { 387 response.Satisfied = true 388 return response, nil 389 } 390 391 erc721TokenRequirements := permissionsParsedData.Erc721TokenRequirements 392 393 erc20ChainIDsMap := permissionsParsedData.Erc20ChainIDsMap 394 erc721ChainIDsMap := permissionsParsedData.Erc721ChainIDsMap 395 396 erc20TokenAddresses := permissionsParsedData.Erc20TokenAddresses 397 398 accounts := make([]gethcommon.Address, 0) 399 400 // TODO: move outside in order not to convert it 401 for _, accountAndChainIDs := range accountsAndChainIDs { 402 accounts = append(accounts, accountAndChainIDs.Address) 403 } 404 405 chainIDsForERC20 := calculateChainIDsSet(accountsAndChainIDs, erc20ChainIDsMap) 406 chainIDsForERC721 := calculateChainIDsSet(accountsAndChainIDs, erc721ChainIDsMap) 407 408 // if there are no chain IDs that match token criteria chain IDs 409 // we aren't able to check balances on selected networks 410 if len(erc20ChainIDsMap) > 0 && len(chainIDsForERC20) == 0 { 411 response.NetworksNotSupported = true 412 return response, nil 413 } 414 415 ownedERC20TokenBalances := make(map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big, 0) 416 if len(chainIDsForERC20) > 0 { 417 // this only returns balances for the networks we're actually interested in 418 balances, err := getBalancesByChain(context.Background(), accounts, erc20TokenAddresses, chainIDsForERC20) 419 if err != nil { 420 return nil, err 421 } 422 ownedERC20TokenBalances = balances 423 } 424 425 ownedERC721Tokens := make(CollectiblesByChain) 426 if len(chainIDsForERC721) > 0 { 427 collectibles, err := getOwnedERC721Tokens(accounts, erc721TokenRequirements, chainIDsForERC721) 428 if err != nil { 429 return nil, err 430 } 431 ownedERC721Tokens = collectibles 432 } 433 434 accountsChainIDsCombinations := make(map[gethcommon.Address]map[uint64]bool) 435 436 for _, tokenPermission := range permissionsParsedData.Permissions { 437 permissionRequirementsMet := true 438 response.Permissions[tokenPermission.Id] = &PermissionTokenCriteriaResult{Role: tokenPermission.Type} 439 440 // There can be multiple token requirements per permission. 441 // If only one is not met, the entire permission is marked 442 // as not fulfilled 443 for _, tokenRequirement := range tokenPermission.TokenCriteria { 444 tokenRequirementResponse, err := p.checkTokenRequirement(tokenRequirement, accounts, ownedERC20TokenBalances, ownedERC721Tokens, accountsChainIDsCombinations) 445 if err != nil { 446 p.logger.Error("failed to check token requirement", zap.Error(err)) 447 } 448 449 if !tokenRequirementResponse.Satisfied { 450 permissionRequirementsMet = false 451 } 452 453 response.Permissions[tokenPermission.Id].TokenRequirements = append(response.Permissions[tokenPermission.Id].TokenRequirements, tokenRequirementResponse) 454 response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, tokenRequirementResponse.Satisfied) 455 } 456 response.Permissions[tokenPermission.Id].ID = tokenPermission.Id 457 458 // multiple permissions are treated as logical OR, meaning 459 // if only one of them is fulfilled, the user gets permission 460 // to join and we can stop early 461 if shortcircuit && permissionRequirementsMet { 462 break 463 } 464 } 465 466 // attach valid account and chainID combinations to response 467 for account, chainIDs := range accountsChainIDsCombinations { 468 combination := &AccountChainIDsCombination{ 469 Address: account, 470 } 471 for chainID := range chainIDs { 472 combination.ChainIDs = append(combination.ChainIDs, chainID) 473 } 474 response.ValidCombinations = append(response.ValidCombinations, combination) 475 } 476 477 response.calculateSatisfied() 478 479 return response, nil 480 } 481 482 type balancesByOwnerAndContractAddressGetter = func(ctx context.Context, chainID walletcommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (map[gethcommon.Address][]thirdparty.TokenBalance, error) 483 484 func (p *DefaultPermissionChecker) handlePermissionsCheck(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool, 485 getBalancesByOwnerAndContractAddress balancesByOwnerAndContractAddressGetter, 486 getBalancesByChain balancesByChainGetter) (*CheckPermissionsResponse, error) { 487 488 var getOwnedERC721Tokens ownedERC721TokensGetter = func(walletAddresses []gethcommon.Address, tokenRequirements map[uint64]map[string]*protobuf.TokenCriteria, chainIDs []uint64) (CollectiblesByChain, error) { 489 return p.getOwnedERC721Tokens(walletAddresses, tokenRequirements, chainIDs, getBalancesByOwnerAndContractAddress) 490 } 491 492 return p.checkPermissions(permissionsParsedData, accountsAndChainIDs, shortcircuit, getOwnedERC721Tokens, getBalancesByChain) 493 } 494 495 func (p *DefaultPermissionChecker) CheckCachedPermissions(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) { 496 return p.handlePermissionsCheck(permissionsParsedData, accountsAndChainIDs, shortcircuit, p.collectiblesManager.FetchCachedBalancesByOwnerAndContractAddress, p.tokenManager.GetCachedBalancesByChain) 497 } 498 499 // CheckPermissions will retrieve balances and check whether the user has 500 // permission to join the community, if shortcircuit is true, it will stop as soon 501 // as we know the answer 502 func (p *DefaultPermissionChecker) CheckPermissions(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) { 503 return p.handlePermissionsCheck(permissionsParsedData, accountsAndChainIDs, shortcircuit, p.collectiblesManager.FetchBalancesByOwnerAndContractAddress, p.tokenManager.GetBalancesByChain) 504 } 505 506 type CollectiblesOwners = map[walletcommon.ChainID]map[gethcommon.Address]*thirdparty.CollectibleContractOwnership 507 508 // Same as CheckPermissions but relies on already provided collectibles owners 509 func (p *DefaultPermissionChecker) CheckPermissionsWithPreFetchedData(permissionsParsedData *PreParsedCommunityPermissionsData, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool, collectiblesOwners CollectiblesOwners) (*CheckPermissionsResponse, error) { 510 var getCollectiblesBalances collectiblesBalancesGetter = func(ctx context.Context, chainID walletcommon.ChainID, ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) { 511 ret := make(thirdparty.TokenBalancesPerContractAddress) 512 513 collectiblesByChain, ok := collectiblesOwners[chainID] 514 if !ok { 515 return nil, errors.New("no data available for chainID") 516 } 517 518 for _, contractAddress := range contractAddresses { 519 ownership, ok := collectiblesByChain[contractAddress] 520 if !ok { 521 return nil, errors.New("no data available for collectible") 522 } 523 524 for _, nftOwner := range ownership.Owners { 525 if nftOwner.OwnerAddress == ownerAddress { 526 ret[contractAddress] = nftOwner.TokenBalances 527 break 528 } 529 } 530 } 531 532 return ret, nil 533 } 534 535 var getOwnedERC721Tokens ownedERC721TokensGetter = func(walletAddresses []gethcommon.Address, tokenRequirements map[uint64]map[string]*protobuf.TokenCriteria, chainIDs []uint64) (CollectiblesByChain, error) { 536 return p.getOwnedERC721Tokens(walletAddresses, tokenRequirements, chainIDs, getCollectiblesBalances) 537 } 538 539 return p.checkPermissions(permissionsParsedData, accountsAndChainIDs, shortcircuit, getOwnedERC721Tokens, p.tokenManager.GetBalancesByChain) 540 } 541 542 func preParsedPermissionsData(permissions []*CommunityTokenPermission) *PreParsedPermissionsData { 543 erc20TokenRequirements, erc721TokenRequirements, _ := ExtractTokenCriteria(permissions) 544 545 erc20ChainIDsMap := make(map[uint64]bool) 546 erc721ChainIDsMap := make(map[uint64]bool) 547 548 erc20TokenAddresses := make([]gethcommon.Address, 0) 549 550 // figure out chain IDs we're interested in 551 for chainID, tokens := range erc20TokenRequirements { 552 erc20ChainIDsMap[chainID] = true 553 for contractAddress := range tokens { 554 erc20TokenAddresses = append(erc20TokenAddresses, gethcommon.HexToAddress(contractAddress)) 555 } 556 } 557 558 for chainID := range erc721TokenRequirements { 559 erc721ChainIDsMap[chainID] = true 560 } 561 562 return &PreParsedPermissionsData{ 563 Erc721TokenRequirements: erc721TokenRequirements, 564 Erc20TokenAddresses: erc20TokenAddresses, 565 Erc20ChainIDsMap: erc20ChainIDsMap, 566 Erc721ChainIDsMap: erc721ChainIDsMap, 567 } 568 } 569 570 func preParsedCommunityPermissionsData(permissions []*CommunityTokenPermission) *PreParsedCommunityPermissionsData { 571 if len(permissions) == 0 { 572 return nil 573 } 574 575 return &PreParsedCommunityPermissionsData{ 576 Permissions: permissions, 577 PreParsedPermissionsData: preParsedPermissionsData(permissions), 578 } 579 } 580 581 func PreParsePermissionsData(permissions map[string]*CommunityTokenPermission) (map[protobuf.CommunityTokenPermission_Type]*PreParsedCommunityPermissionsData, map[string]*PreParsedCommunityPermissionsData) { 582 becomeMemberPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_BECOME_MEMBER) 583 becomeAdminPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_BECOME_ADMIN) 584 becomeTokenMasterPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER) 585 586 viewOnlyPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) 587 viewAndPostPermissions := TokenPermissionsByType(permissions, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) 588 channelPermissions := append(viewAndPostPermissions, viewOnlyPermissions...) 589 590 communityPermissionsPreParsedData := make(map[protobuf.CommunityTokenPermission_Type]*PreParsedCommunityPermissionsData) 591 communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_MEMBER] = preParsedCommunityPermissionsData(becomeMemberPermissions) 592 communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_ADMIN] = preParsedCommunityPermissionsData(becomeAdminPermissions) 593 communityPermissionsPreParsedData[protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER] = preParsedCommunityPermissionsData(becomeTokenMasterPermissions) 594 595 channelPermissionsPreParsedData := make(map[string]*PreParsedCommunityPermissionsData) 596 for _, channelPermission := range channelPermissions { 597 channelPermissionsPreParsedData[channelPermission.Id] = preParsedCommunityPermissionsData([]*CommunityTokenPermission{channelPermission}) 598 } 599 600 return communityPermissionsPreParsedData, channelPermissionsPreParsedData 601 } 602 603 func CollectibleAddressesFromPreParsedPermissionsData(communityPermissions map[protobuf.CommunityTokenPermission_Type]*PreParsedCommunityPermissionsData, channelPermissions map[string]*PreParsedCommunityPermissionsData) map[walletcommon.ChainID]map[gethcommon.Address]struct{} { 604 ret := make(map[walletcommon.ChainID]map[gethcommon.Address]struct{}) 605 606 allPermissionsData := []*PreParsedCommunityPermissionsData{} 607 for _, permissionsData := range communityPermissions { 608 if permissionsData != nil { 609 allPermissionsData = append(allPermissionsData, permissionsData) 610 } 611 } 612 for _, permissionsData := range channelPermissions { 613 if permissionsData != nil { 614 allPermissionsData = append(allPermissionsData, permissionsData) 615 } 616 } 617 618 for _, data := range allPermissionsData { 619 for chainID, contractAddresses := range data.Erc721TokenRequirements { 620 if ret[walletcommon.ChainID(chainID)] == nil { 621 ret[walletcommon.ChainID(chainID)] = make(map[gethcommon.Address]struct{}) 622 } 623 624 for contractAddress := range contractAddresses { 625 ret[walletcommon.ChainID(chainID)][gethcommon.HexToAddress(contractAddress)] = struct{}{} 626 } 627 } 628 } 629 630 return ret 631 }