diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index da76d7ee51..057f435b3d 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -21,6 +21,8 @@ import ../../../core/signals/types import ../../../core/eventemitter import ../../../core/unique_event_emitter +import backend/collectibles_types + type Controller* = ref object of RootObj delegate: io_interface.AccessInterface @@ -730,9 +732,11 @@ proc getTokenDecimals*(self: Controller, symbol: string): int = return asset.decimals return 0 -proc getContractAddressesForToken*(self: Controller, symbol: string): Table[int, string] = +# find addresses by tokenKey from UI +# tokenKey can be: symbol for ERC20, or chain+address[+tokenId] for ERC721 +proc getContractAddressesForToken*(self: Controller, tokenKey: string): Table[int, string] = var contractAddresses = initTable[int, string]() - let token = self.tokenService.findTokenBySymbol(symbol) + let token = self.tokenService.findTokenBySymbol(tokenKey) if token != nil: for addrPerChain in token.addressPerChainId: # depending on areTestNetworksEnabled (in getNetworkByChainId), contractAddresses will @@ -741,9 +745,15 @@ proc getContractAddressesForToken*(self: Controller, symbol: string): Table[int, if network == nil: continue contractAddresses[addrPerChain.chainId] = addrPerChain.address - let communityToken = self.communityService.getCommunityTokenBySymbol(self.getMySectionId(), symbol) + let communityToken = self.communityService.getCommunityTokenBySymbol(self.getMySectionId(), tokenKey) if communityToken.address != "": contractAddresses[communityToken.chainId] = communityToken.address + if contractAddresses.len == 0: + try: + let chainAndAddress = toContractID(tokenKey) + contractAddresses[chainAndAddress.chainID] = chainAndAddress.address + except Exception: + discard return contractAddresses proc getCommunityTokenList*(self: Controller): seq[CommunityTokenDto] = diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 19aff6eb25..b75ba88350 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -1010,7 +1010,8 @@ proc updateTokenPermissionModel*(self: Module, permissions: Table[string, CheckP tokenCriteriaItem.amount, tokenCriteriaItem.`type`, tokenCriteriaItem.ensPattern, - criteriaResult.criteria[index] + criteriaResult.criteria[index], + tokenCriteriaItem.addresses ) if criteriaResult.criteria[index] == false: @@ -1530,11 +1531,12 @@ method createOrEditCommunityTokenPermission*(self: Module, communityId: string, let tokenCriteriaJsonObj = tokenCriteriaJson.parseJson for tokenCriteria in tokenCriteriaJsonObj: + let tokenKey = tokenCriteria{"key"}.getStr() var tokenCriteriaDto = tokenCriteria.toTokenCriteriaDto if tokenCriteriaDto.`type` == TokenType.ERC20: - tokenCriteriaDto.decimals = self.controller.getTokenDecimals(tokenCriteriaDto.symbol) + tokenCriteriaDto.decimals = self.controller.getTokenDecimals(tokenKey) - let contractAddresses = self.controller.getContractAddressesForToken(tokenCriteriaDto.symbol) + let contractAddresses = self.controller.getContractAddressesForToken(tokenKey) if contractAddresses.len == 0 and tokenCriteriaDto.`type` != TokenType.ENS: if permissionId == "": self.onCommunityTokenPermissionCreationFailed(communityId) diff --git a/src/app/modules/main/communities/controller.nim b/src/app/modules/main/communities/controller.nim index bb67319234..ed6c1e4e88 100644 --- a/src/app/modules/main/communities/controller.nim +++ b/src/app/modules/main/communities/controller.nim @@ -433,6 +433,12 @@ proc getKeypairByKeyUid*(self: Controller, keyUid: string): KeypairDto = proc getKeypairs*(self: Controller): seq[KeypairDto] = return self.walletAccountService.getKeypairs() +proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] = + return self.walletAccountService.getWalletAccounts() + +proc getEnabledChainIds*(self: Controller): seq[int] = + return self.walletAccountService.getEnabledChainIds() + proc disconnectKeycardReponseSignal(self: Controller) = self.events.disconnect(self.connectionKeycardResponse) diff --git a/src/app/modules/main/communities/module.nim b/src/app/modules/main/communities/module.nim index c8907184a0..f01ee5475e 100644 --- a/src/app/modules/main/communities/module.nim +++ b/src/app/modules/main/communities/module.nim @@ -549,8 +549,9 @@ method requestCancelDiscordChannelImport*(self: Module, discordChannelId: string proc createCommunityTokenItem(self: Module, token: CommunityTokensMetadataDto, communityId: string, supply: string, infiniteSupply: bool, privilegesLevel: int): TokenListItem = let communityTokenDecimals = if token.tokenType == TokenType.ERC20: 18 else: 0 + let key = if token.tokenType == TokenType.ERC721: token.getContractIdFromFirstAddress() else: token.symbol result = initTokenListItem( - key = token.symbol, + key = key, name = token.name, symbol = token.symbol, color = "", # community tokens don't have `color` @@ -911,7 +912,8 @@ proc applyPermissionResponse*(self: Module, communityId: string, permissions: Ta tokenCriteriaItem.amount, tokenCriteriaItem.`type`, tokenCriteriaItem.ensPattern, - criteriaResult.criteria[index] + criteriaResult.criteria[index], + tokenCriteriaItem.addresses ) if criteriaResult.criteria[index] == false: diff --git a/src/app/modules/shared_models/collectibles_entry.nim b/src/app/modules/shared_models/collectibles_entry.nim index 0ff80109d8..b828ff6d34 100644 --- a/src/app/modules/shared_models/collectibles_entry.nim +++ b/src/app/modules/shared_models/collectibles_entry.nim @@ -64,7 +64,7 @@ QtObject: proc hasCollectibleData(self: CollectiblesEntry): bool = return self.data != nil and isSome(self.data.collectibleData) - proc getCollectibleData(self: CollectiblesEntry): backend.CollectibleData = + proc getCollectibleData*(self: CollectiblesEntry): backend.CollectibleData = return self.data.collectibleData.get() proc hasCollectionData(self: CollectiblesEntry): bool = diff --git a/src/app/modules/shared_models/collectibles_model.nim b/src/app/modules/shared_models/collectibles_model.nim index 5b53b3951d..59e415a0a9 100644 --- a/src/app/modules/shared_models/collectibles_model.nim +++ b/src/app/modules/shared_models/collectibles_model.nim @@ -18,6 +18,7 @@ type CollectionUid CollectionName CollectionSlug + CollectionImageUrl IsLoading Ownership # Community-related roles @@ -139,6 +140,7 @@ QtObject: CollectibleRole.CollectionUid.int:"collectionUid", CollectibleRole.CollectionName.int:"collectionName", CollectibleRole.CollectionSlug.int:"collectionSlug", + CollectibleRole.CollectionImageUrl.int:"collectionImageUrl", CollectibleRole.IsLoading.int:"isLoading", CollectibleRole.Ownership.int:"ownership", CollectibleRole.CommunityId.int:"communityId", @@ -153,9 +155,7 @@ QtObject: if (index.row < 0 or index.row >= self.getCount()): return - let enumRole = role.CollectibleRole - if index.row < self.items.len: let item = self.items[index.row] case enumRole: @@ -183,6 +183,8 @@ QtObject: result = newQVariant(item.getCollectionName()) of CollectibleRole.CollectionSlug: result = newQVariant(item.getCollectionSlug()) + of CollectibleRole.CollectionImageUrl: + result = newQVariant(item.getCollectionImageURL()) of CollectibleRole.IsLoading: result = newQVariant(false) of CollectibleRole.Ownership: @@ -195,6 +197,10 @@ QtObject: result = newQVariant(item.getTokenType()) of CollectibleRole.Soulbound: result = newQVariant(item.getSoulbound()) + else: + result = newQVariant() + else: + result = newQVariant() proc resetCollectibleItems(self: Model, newItems: seq[CollectiblesEntry] = @[]) = self.beginResetModel() diff --git a/src/app/modules/shared_models/token_criteria_item.nim b/src/app/modules/shared_models/token_criteria_item.nim index daf4fc4cea..6ba9629ece 100644 --- a/src/app/modules/shared_models/token_criteria_item.nim +++ b/src/app/modules/shared_models/token_criteria_item.nim @@ -1,4 +1,5 @@ -import stew/shims/strformat +import stew/shims/strformat, tables +import backend/collectibles_types type TokenCriteriaItem* = object @@ -8,6 +9,7 @@ type `type`*: int ensPattern*: string criteriaMet*: bool + addresses*: Table[int, string] proc initTokenCriteriaItem*( symbol: string, @@ -15,7 +17,8 @@ proc initTokenCriteriaItem*( amount: string, `type`: int, ensPattern: string, - criteriaMet: bool + criteriaMet: bool, + addresses: Table[int, string] ): TokenCriteriaItem = result.symbol = symbol result.name = name @@ -23,6 +26,7 @@ proc initTokenCriteriaItem*( result.ensPattern = ensPattern result.amount = amount result.criteriaMet = criteriaMet + result.addresses = addresses proc `$`*(self: TokenCriteriaItem): string = result = fmt"""TokenCriteriaItem( @@ -31,7 +35,8 @@ proc `$`*(self: TokenCriteriaItem): string = amount: {self.amount}, type: {self.type}, ensPattern: {self.ensPattern}, - criteriaMet: {self.criteriaMet} + criteriaMet: {self.criteriaMet}, + addresses: {self.addresses} ]""" proc getType*(self: TokenCriteriaItem): int = @@ -51,3 +56,12 @@ proc getEnsPattern*(self: TokenCriteriaItem): string = proc getCriteriaMet*(self: TokenCriteriaItem): bool = return self.criteriaMet + +proc getAddresses*(self: TokenCriteriaItem): Table[int, string] = + return self.addresses + +proc getContractIdFromFirstAddress*(self: TokenCriteriaItem): string = + for chainID, address in self.addresses: + let contractId = ContractID(chainID: chainID, address: address) + return contractId.toString() + return "" diff --git a/src/app/modules/shared_models/token_criteria_model.nim b/src/app/modules/shared_models/token_criteria_model.nim index 0fc82c5fd3..91205a5d7e 100644 --- a/src/app/modules/shared_models/token_criteria_model.nim +++ b/src/app/modules/shared_models/token_criteria_model.nim @@ -61,6 +61,8 @@ QtObject: of ModelRole.Key: if item.getType() == ord(TokenType.ENS): result = newQVariant(item.getEnsPattern()) + elif item.getType() == ord(TokenType.ERC721): + result = newQVariant(item.getContractIdFromFirstAddress()) else: result = newQVariant(item.getSymbol()) of ModelRole.Type: diff --git a/src/app/modules/shared_models/token_permission_item.nim b/src/app/modules/shared_models/token_permission_item.nim index e068977fc7..9d139e24e7 100644 --- a/src/app/modules/shared_models/token_permission_item.nim +++ b/src/app/modules/shared_models/token_permission_item.nim @@ -80,7 +80,8 @@ proc buildTokenPermissionItem*(tokenPermission: CommunityTokenPermissionDto, cha tc.amountInWei, tc.`type`.int, tc.ensPattern, - false # tokenCriteriaMet will be updated by a call to checkPermissionsToJoin + false, # tokenCriteriaMet will be updated by a call to checkPermissionsToJoin + tc.contractAddresses ) tokenCriteriaItems.add(tokenCriteriaItem) diff --git a/src/app/modules/shared_modules/collectibles/controller.nim b/src/app/modules/shared_modules/collectibles/controller.nim index a123ea83b6..82e67847ef 100644 --- a/src/app/modules/shared_modules/collectibles/controller.nim +++ b/src/app/modules/shared_modules/collectibles/controller.nim @@ -138,7 +138,6 @@ QtObject: else: offset = self.tempItems.len self.fetchFromStart = false - let response = backend_collectibles.getOwnedCollectiblesAsync(self.requestId, self.chainIds, self.addresses, self.filter, offset, FETCH_BATCH_COUNT_DEFAULT, self.dataType, self.fetchCriteria) if response.error != nil: self.model.setIsFetching(false) diff --git a/src/app_service/service/community/dto/community.nim b/src/app_service/service/community/dto/community.nim index 16ad0a0032..211108f318 100644 --- a/src/app_service/service/community/dto/community.nim +++ b/src/app_service/service/community/dto/community.nim @@ -10,6 +10,8 @@ import ../../chat/dto/chat import ../../shared_urls/dto/url_data import ../../../../app_service/common/types +import backend/collectibles_types + type RequestToJoinType* {.pure.}= enum Pending = 1, Declined = 2, @@ -291,7 +293,11 @@ proc toTokenCriteriaDto*(jsonObj: JsonNode): TokenCriteriaDto = if result.`type` == TokenType.ENS: discard jsonObj.getProp("key", result.ensPattern) else: - discard jsonObj.getProp("key", result.symbol) + var tmpSymbol = "" + discard jsonObj.getProp("key", tmpSymbol) + if not isContractID(tmpSymbol): + # overwrite only if key does not contain contractID + result.symbol = tmpSymbol proc toCommunityTokenPermissionDto*(jsonObj: JsonNode): CommunityTokenPermissionDto = result = CommunityTokenPermissionDto() @@ -652,3 +658,9 @@ proc findOwner*(self: CommunityDto): ChatMember = if member.role == MemberRole.Owner: return member raise newException(ValueError, "No owner found in members list") + +proc getContractIdFromFirstAddress*(self: CommunityTokensMetadataDto): string = + for chainID, address in self.addresses: + let contractId = ContractID(chainID: chainID, address: address) + return contractId.toString() + return "" \ No newline at end of file diff --git a/src/backend/collectibles_types.nim b/src/backend/collectibles_types.nim index 593e105c28..4333578f31 100644 --- a/src/backend/collectibles_types.nim +++ b/src/backend/collectibles_types.nim @@ -151,6 +151,13 @@ proc toContractID*(t: string): ContractID = var parts = t.split("+") return ContractID(chainID: parts[0].parseInt(), address: parts[1]) +proc isContractID*(t: string): bool = + try: + discard toContractID(t) + return true + except Exception: + return false + # CollectibleUniqueID proc `$`*(self: CollectibleUniqueID): string = return fmt"""CollectibleUniqueID( diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 237e390795..b99456326e 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -49,39 +49,56 @@ QtObject { } } - readonly property var tmp: SortFilterProxyModel { - id: tmpSfpm - + readonly property var communityCollectiblesModelWithCollectionRoles: SortFilterProxyModel { sourceModel: communitiesModuleInst.collectiblesModel - proxyRoles: ExpressionRole { - function collectibleIcon(icon) { - return !!icon ? icon : Style.png("tokens/DEFAULT-TOKEN") - } + proxyRoles: [ + ExpressionRole { + function collectibleIcon(icon) { + return !!icon ? icon : Style.png("tokens/DEFAULT-TOKEN") + } name: "iconSource" expression: collectibleIcon(model.icon) - } + }, + ExpressionRole { + name: "collectionUid" + expression: model.key + }, + ExpressionRole { + function collectibleIcon(icon) { + return !!icon ? icon : Style.png("tokens/DEFAULT-TOKEN") + } + name: "collectionImageUrl" + expression: collectibleIcon(model.icon) + } + ] } - readonly property var tmp2: ObjectProxyModel { + readonly property var walletCollectiblesModel: ObjectProxyModel { sourceModel: WalletStore.RootStore.collectiblesStore.allCollectiblesModel delegate: QtObject { - readonly property string key: model.symbol + readonly property string key: model.symbol ?? "" readonly property string shortName: model.collectionName ? model.collectionName : model.collectionUid ? model.collectionUid : "" readonly property string symbol: shortName - readonly property int category: 1 + readonly property string name: shortName + readonly property int category: 1 // Own } - exposedRoles: ["key", "symbol", "shortName"] + exposedRoles: ["key", "symbol", "shortName", "name", "category"] expectedRoles: ["symbol", "collectionName", "collectionUid"] } - readonly property var tmp3: SortFilterProxyModel { - id: tmpSfpm2 + readonly property var walletCollectiblesGroupingModel: GroupingModel { + sourceModel: walletCollectiblesModel - sourceModel: tmp2 + groupingRoleName: "collectionUid" + submodelRoleName: "subnames" + } + + readonly property var walletNonCommunityCollectiblesModel: SortFilterProxyModel { + sourceModel: walletCollectiblesGroupingModel filters: ValueFilter { roleName: "communityId" @@ -89,10 +106,8 @@ QtObject { } } - property var r: RolesRenamingModel { - id: renaming - - sourceModel: tmpSfpm2 + property var walletCollectiblesWithIconSourceModel: RolesRenamingModel { + sourceModel: walletNonCommunityCollectiblesModel mapping: RoleRename { from: "mediaUrl" @@ -101,13 +116,12 @@ QtObject { } property var collectiblesModel: ConcatModel { - sources: [ SourceModel { - model: tmpSfpm + model: communityCollectiblesModelWithCollectionRoles }, SourceModel { - model: renaming + model: walletCollectiblesWithIconSourceModel } ] } diff --git a/ui/app/AppLayouts/Communities/controls/ExtendedDropdownContent.qml b/ui/app/AppLayouts/Communities/controls/ExtendedDropdownContent.qml index 1c6d5ba96a..c0ffeee4cd 100644 --- a/ui/app/AppLayouts/Communities/controls/ExtendedDropdownContent.qml +++ b/ui/app/AppLayouts/Communities/controls/ExtendedDropdownContent.qml @@ -127,12 +127,20 @@ Item { AnyOf { enabled: root.showAllTokensMode - /* - // this category is not used ValueFilter { roleName: "category" value: TokenCategories.Category.Own - }*/ + } + + ValueFilter { + roleName: "category" + value: TokenCategories.Category.General + } + + ValueFilter { + roleName: "category" + value: TokenCategories.Category.Community + } AllOf { ValueFilter { diff --git a/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml b/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml index 83e948f95c..1f0287dd00 100644 --- a/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml +++ b/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml @@ -95,7 +95,7 @@ StatusListView { name: model.name shortName: model.shortName ?? "" - iconSource: model.iconSource ?? "" + iconSource: model.iconSource ? model.iconSource : Style.png("tokens/DEFAULT-TOKEN") showSubItemsIcon: !!model.subItems && model.subItems.count > 0 selected: root.checkedKeys.includes(model.key) amount: { diff --git a/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml b/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml index 79f0092d2d..fc6f5a15e9 100644 --- a/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml +++ b/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml @@ -12,7 +12,20 @@ import utils 1.0 QtObject { function getTokenByKey(model, key) { - return Internal.PermissionUtils.getTokenByKey(model, key) + var item + // key format: + // chainId+address[+tokenId] - ERC721 + // symbol - ERC20 + // collectionUid model role keeps chainId+address for every ERC721 + // key model role keeps: symbol for ERC20, chainId+address for community ERC721 tokens, chainId+address+tokenId for ERC721 tokens from wallet + let collectionUid = PermissionsHelpers.getCollectionUidFromKey(key) + if(collectionUid !== "") { + item = ModelUtils.getByKey(model, "collectionUid", collectionUid) + } else { + item = Internal.PermissionUtils.getTokenByKey(model, key) + } + + return item } function getTokenNameByKey(model, key) { @@ -31,9 +44,10 @@ QtObject { function getTokenIconByKey(model, key) { const item = getTokenByKey(model, key) + const defaultIcon = Style.png("tokens/DEFAULT-TOKEN") if (item) - return item.iconSource ?? "" - return "" + return item.iconSource ? item.iconSource : defaultIcon + return defaultIcon } function getTokenDecimalsByKey(model, key) { @@ -113,4 +127,14 @@ QtObject { else return tMasterTokenSymbolTag + shortName.toUpperCase() } + + function getCollectionUidFromKey(key) { + const parts = key.split('+'); + if(parts.length === 2) + return key + else if(parts.length === 3) + return parts[0]+"+"+parts[1] + else + return "" + } } diff --git a/ui/app/AppLayouts/Communities/panels/PermissionsSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/PermissionsSettingsPanel.qml index 632df91d4a..b03801b7f3 100644 --- a/ui/app/AppLayouts/Communities/panels/PermissionsSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/PermissionsSettingsPanel.qml @@ -273,8 +273,7 @@ StackView { const holdings = dirtyValues.holdingsRequired ? ModelUtils.modelToArray( dirtyValues.selectedHoldingsModel, - ["key", "type", "amount"]) : [] - + ["key", "type", "amount", "symbol"]) : [] const channels = root.showChannelSelector ? ModelUtils.modelToArray( dirtyValues.selectedChannelsModel, ["key"]) : @@ -295,9 +294,8 @@ StackView { const holdings = dirtyValues.holdingsRequired ? ModelUtils.modelToArray( dirtyValues.selectedHoldingsModel, - ["key", "type", "amount"]) + ["key", "type", "amount", "symbol"]) : [] - const channels = ModelUtils.modelToArray( dirtyValues.selectedChannelsModel, ["key"]) diff --git a/ui/app/AppLayouts/Communities/views/EditPermissionView.qml b/ui/app/AppLayouts/Communities/views/EditPermissionView.qml index ad206c37b9..88de35933c 100644 --- a/ui/app/AppLayouts/Communities/views/EditPermissionView.qml +++ b/ui/app/AppLayouts/Communities/views/EditPermissionView.qml @@ -245,9 +245,10 @@ StatusScrollView { function addItem(type, item, amount) { const key = item.key + const symbol = item.symbol d.dirtyValues.selectedHoldingsModel.append( - { type, key, amount }) + { type, key, amount, symbol }) } function prepareUpdateIndex(key) { @@ -310,7 +311,7 @@ StatusScrollView { d.dirtyValues.selectedHoldingsModel.set( itemIndex, - { type: Constants.TokenType.ERC721, key, amount: String(amount) }) + { type: Constants.TokenType.ERC721, key, amount: String(amount), symbol: modelItem.symbol }) dropdown.close() } diff --git a/ui/app/AppLayouts/Communities/views/HoldingsSelectionModel.qml b/ui/app/AppLayouts/Communities/views/HoldingsSelectionModel.qml index 3154613976..152d706442 100644 --- a/ui/app/AppLayouts/Communities/views/HoldingsSelectionModel.qml +++ b/ui/app/AppLayouts/Communities/views/HoldingsSelectionModel.qml @@ -37,15 +37,18 @@ SortFilterProxyModel { return item.decimals } - function getText(type, key, amount) { + function getText(type, key, amount, defaultText) { const model = type === Constants.TokenType.ERC20 ? assetsModel : collectiblesModel - const item = PermissionsHelpers.getTokenByKey(model, key) - const name = getName(type, item, key) + let item = PermissionsHelpers.getTokenByKey(model, key) + let name = getName(type, item, key) const decimals = getDecimals(type, item) + if (name === "") + name = defaultText + return PermissionsHelpers.setHoldingsTextFormat( type, name, amount, decimals) } @@ -55,9 +58,9 @@ SortFilterProxyModel { expression: { _assetsChanges.revision _collectiblesChanges.revision - return getText(model.type, model.key, model.amount) + return getText(model.type, model.key, model.amount, model.symbol) } - expectedRoles: ["type", "key", "amount"] + expectedRoles: ["type", "key", "amount", "symbol", "shortName"] }, FastExpressionRole { name: "imageSource"