From e4b8814bfa31a6ca6b624f85aa849fe31095a356 Mon Sep 17 00:00:00 2001 From: Pascal Precht <445106+0x-r4bbit@users.noreply.github.com> Date: Tue, 28 Feb 2023 16:23:09 +0100 Subject: [PATCH] feat(Communities): allow for creating community permissions This commit is the first of implementing community permissions. **It is not implementing the complete feature**, rather does it introduce the first pieces, such that we can get code reviewed and merged before it grows too big. To review these features, please make sure to 1. Enable wallet (Settings -> Advanced -> Wallet toggle) 2. Enable community permissions (Settings -> Advanced -> Community Permissions toggle) You'll have to restart the app after doing so. The commit introduces the following: **UI, API calls and view models to CRUD community permissions** After creating a community, the user can go to the community settings and create new token permissions. The user can also update and delete existing permissions. **Asset and collectible view models** To create community token permissions, users have to select the token criteria. This commit introduces the `assetsModel` for ERC20 tokens and `collectiblesModel` for `ERC721` tokens. The latter only supports custom minted community tokens at this point. **This commit requires:** https://github.com/status-im/status-go/pull/3207 --- src/app/core/notifications/details.nim | 8 +- .../notifications/notifications_manager.nim | 32 ++- src/app/global/global_events.nim | 14 +- .../modules/main/chat_section/controller.nim | 75 ++++++- .../main/chat_section/io_interface.nim | 30 +++ src/app/modules/main/chat_section/module.nim | 186 +++++++++++++++++- src/app/modules/main/chat_section/view.nim | 79 ++++++++ src/app/modules/main/controller.nim | 23 +++ src/app/modules/main/io_interface.nim | 13 +- src/app/modules/main/module.nim | 26 ++- .../shared_models/token_criteria_item.nim | 53 +++++ .../shared_models/token_criteria_model.nim | 87 ++++++++ .../modules/shared_models/token_list_item.nim | 56 ++++++ .../shared_models/token_list_model.nim | 92 +++++++++ .../token_permission_chat_list_item.nim | 25 +++ .../token_permission_chat_list_model.nim | 59 ++++++ .../shared_models/token_permission_item.nim | 64 ++++++ .../shared_models/token_permissions_model.nim | 123 ++++++++++++ .../service/community/dto/community.nim | 93 ++++++++- src/app_service/service/community/service.nim | 129 ++++++++++++ .../service/community_tokens/service.nim | 7 + src/app_service/service/token/service.nim | 40 ++++ .../service/wallet_account/dto.nim | 7 + .../service/wallet_account/service.nim | 12 +- src/backend/communities.nim | 23 +++ .../Chat/controls/community/HoldingTypes.qml | 2 +- .../CommunityPermissionsSettingsPanel.qml | 16 +- .../Chat/stores/CommunitiesStore.qml | 126 +----------- ui/app/AppLayouts/Chat/stores/RootStore.qml | 4 + .../communities/CommunityPermissionsView.qml | 2 +- vendor/status-go | 2 +- 31 files changed, 1364 insertions(+), 144 deletions(-) create mode 100644 src/app/modules/shared_models/token_criteria_item.nim create mode 100644 src/app/modules/shared_models/token_criteria_model.nim create mode 100644 src/app/modules/shared_models/token_list_item.nim create mode 100644 src/app/modules/shared_models/token_list_model.nim create mode 100644 src/app/modules/shared_models/token_permission_chat_list_item.nim create mode 100644 src/app/modules/shared_models/token_permission_chat_list_model.nim create mode 100644 src/app/modules/shared_models/token_permission_item.nim create mode 100644 src/app/modules/shared_models/token_permissions_model.nim diff --git a/src/app/core/notifications/details.nim b/src/app/core/notifications/details.nim index cb7b625a06..ff25b282da 100644 --- a/src/app/core/notifications/details.nim +++ b/src/app/core/notifications/details.nim @@ -16,7 +16,13 @@ type NewMessage, NewMessageWithPersonalMention, NewMessageWithGlobalMention, - IdentityVerificationRequest + IdentityVerificationRequest, + CommunityTokenPermissionCreated, + CommunityTokenPermissionUpdated, + CommunityTokenPermissionDeleted, + CommunityTokenPermissionCreationFailed, + CommunityTokenPermissionUpdateFailed, + CommunityTokenPermissionDeletionFailed NotificationDetails* = object notificationType*: NotificationType # the default value is `UnknownNotification` diff --git a/src/app/core/notifications/notifications_manager.nim b/src/app/core/notifications/notifications_manager.nim index 9cb0d2c801..36cc866d12 100644 --- a/src/app/core/notifications/notifications_manager.nim +++ b/src/app/core/notifications/notifications_manager.nim @@ -85,6 +85,12 @@ QtObject: self, "onMeMentionedIconBadgeNotification(int)", 2) signalConnect(singletonInstance.globalEvents, "showAcceptedContactRequest(QString, QString, QString)", self, "onShowAcceptedContactRequest(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionCreatedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionCreatedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionUpdatedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionUpdatedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionDeletedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionDeletedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionCreationFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionCreationFailedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", 2) self.notificationSetUp = true @@ -140,6 +146,30 @@ QtObject: messageId: messageId) self.processNotification(title, message, details) + proc onShowCommunityTokenPermissionCreatedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionCreated, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionUpdatedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionUpdated, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionDeletedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionDeleted, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionCreationFailedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionCreationFailed, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionUpdateFailedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionUpdateFailed, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionDeletionFailedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionDeletionFailed, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + proc onShowNewContactRequestNotification*(self: NotificationsManager, title: string, message: string, sectionId: string) {.slot.} = let details = NotificationDetails(notificationType: NotificationType.NewContactRequest, sectionId: sectionId) @@ -306,4 +336,4 @@ QtObject: # In all other cases (TestNotification, AcceptedContactRequest, JoinCommunityRequest, MyRequestToJoinCommunityAccepted, # MyRequestToJoinCommunityRejected) else: - self.notificationCheck(title, message, details, "") \ No newline at end of file + self.notificationCheck(title, message, details, "") diff --git a/src/app/global/global_events.nim b/src/app/global/global_events.nim index 1a3bcc8333..2a67404c5c 100644 --- a/src/app/global/global_events.nim +++ b/src/app/global/global_events.nim @@ -15,6 +15,18 @@ QtObject: proc showTestNotification*(self: GlobalEvents, title: string, message: string) {.signal.} + proc showCommunityTokenPermissionCreatedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionUpdatedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionDeletedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionCreationFailedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionUpdateFailedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionDeletionFailedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + proc showMessageNotification*(self: GlobalEvents, title: string, message: string, sectionId: string, isCommunitySection: bool, isSectionActive: bool, chatId: string, isChatActive: bool, messageId: string, notificationType: int, isOneToOne: bool, isGroupChat: bool) {.signal.} @@ -34,4 +46,4 @@ QtObject: proc showAcceptedContactRequest*(self: GlobalEvents, title: string, message: string, sectionId: string) {.signal.} - proc meMentionedIconBadgeNotification*(self: GlobalEvents, allMentions: int) {.signal.} \ No newline at end of file + proc meMentionedIconBadgeNotification*(self: GlobalEvents, allMentions: int) {.signal.} diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 49a39cc1f4..37a8fe957e 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -10,6 +10,9 @@ import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/gif/service as gif_service import ../../../../app_service/service/mailservers/service as mailservers_service +import ../../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../../app_service/service/token/service as token_service +import ../../../../app_service/service/community_tokens/service as community_tokens_service import ../../../../app_service/service/visual_identity/service as procs_from_visual_identity_service import ../../../core/signals/types @@ -33,12 +36,18 @@ type messageService: message_service.Service gifService: gif_service.Service mailserversService: mailservers_service.Service + walletAccountService: wallet_account_service.Service + tokenService: token_service.Service + communityTokensService: community_tokens_service.Service proc newController*(delegate: io_interface.AccessInterface, sectionId: string, isCommunity: bool, events: EventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, contactService: contact_service.Service, chatService: chat_service.Service, communityService: community_service.Service, messageService: message_service.Service, gifService: gif_service.Service, - mailserversService: mailservers_service.Service): Controller = + mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service): Controller = result = Controller() result.delegate = delegate result.sectionId = sectionId @@ -53,6 +62,9 @@ proc newController*(delegate: io_interface.AccessInterface, sectionId: string, i result.messageService = messageService result.gifService = gifService result.mailserversService = mailserversService + result.walletAccountService = walletAccountService + result.tokenService = tokenService + result.communityTokensService = communityTokensService proc delete*(self: Controller) = discard @@ -202,6 +214,40 @@ proc init*(self: Controller) = if (args.communityId == self.sectionId): self.delegate.onCategoryUnmuted(args.categoryId) + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionCreated(args.communityId, args.tokenPermission) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATION_FAILED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionCreationFailed(args.communityId) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionUpdated(args.communityId, args.tokenPermission) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionUpdateFailed(args.communityId) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED) do(e: Args): + let args = CommunityTokenPermissionRemovedArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionDeleted(args.communityId, args.permissionId) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionDeletionFailed(args.communityId) + + self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e: Args): + self.delegate.onWalletAccountTokensRebuilt() + + self.events.on(SIGNAL_CONTACT_NICKNAME_CHANGED) do(e: Args): var args = ContactArgs(e) self.delegate.onContactDetailsUpdated(args.contactId) @@ -258,8 +304,11 @@ proc getMySectionId*(self: Controller): string = proc isCommunity*(self: Controller): bool = return self.isCommunitySection +proc getCommunityByIdFromAllCommunities*(self: Controller, communityId: string): CommunityDto = + return self.communityService.getCommunityByIdFromAllCommunities(communityId) + proc getMyCommunity*(self: Controller): CommunityDto = - return self.communityService.getCommunityById(self.sectionId) + return self.getCommunityByIdFromAllCommunities(self.sectionId) proc getCommunityById*(self: Controller, communityId: string): CommunityDto = return self.communityService.getCommunityById(communityId) @@ -502,3 +551,25 @@ proc getColorHash*(self: Controller, pubkey: string): ColorHashDto = proc getColorId*(self: Controller, pubkey: string): int = procs_from_visual_identity_service.colorIdOf(pubkey) + +proc createOrEditCommunityTokenPermission*(self: Controller, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + self.communityService.createOrEditCommunityTokenPermission(communityId, tokenPermission) + +proc deleteCommunityTokenPermission*(self: Controller, communityId: string, permissionId: string) = + self.communityService.deleteCommunityTokenPermission(communityId, permissionId) + +proc allAccountsTokenBalance*(self: Controller, symbol: string): float64 = + return self.walletAccountService.allAccountsTokenBalance(symbol) + +proc getTokenList*(self: Controller): seq[TokenDto] = + return self.tokenService.getTokenList() + +proc getContractAddressesForToken*(self: Controller, symbol: string): Table[int, string] = + var contractAddresses = self.tokenService.getContractAddressesForToken(symbol) + let communityToken = self.communityTokensService.getCommunityTokenBySymbol(self.getMySectionId(), symbol) + if communityToken.address != "": + contractAddresses[communityToken.chainId] = communityToken.address + return contractAddresses + +proc getCommunityTokenList*(self: Controller): seq[CommunityTokenDto] = + return self.communityTokensService.getCommunityTokens(self.getMySectionId()) diff --git a/src/app/modules/main/chat_section/io_interface.nim b/src/app/modules/main/chat_section/io_interface.nim index c9c5bb4a80..78ca0b22c9 100644 --- a/src/app/modules/main/chat_section/io_interface.nim +++ b/src/app/modules/main/chat_section/io_interface.nim @@ -8,10 +8,13 @@ import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/gif/service as gif_service import ../../../../app_service/service/mailservers/service as mailservers_service +import ../../../../app_service/service/token/service as token_service import model as chats_model import ../../../core/eventemitter +import ../../shared_models/token_list_item + type AccessInterface* {.pure inheritable.} = ref object of RootObj @@ -325,3 +328,30 @@ method switchToChannel*(self: AccessInterface, channelName: string) = method joinSpectatedCommunity*(self: AccessInterface) = raise newException(ValueError, "No implementation available") + +method createOrEditCommunityTokenPermission*(self: AccessInterface, communityId: string, permissionId: string, permissionType: int, tokenCriteriaJson: string, isPrivate: bool) {.base.} = + raise newException(ValueError, "No implementation available") + +method deleteCommunityTokenPermission*(self: AccessInterface, communityId: string, permissionId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionCreated*(self: AccessInterface, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionCreationFailed*(self: AccessInterface, communityId: string) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionUpdated*(self: AccessInterface, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionUpdateFailed*(self: AccessInterface, communityId: string) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionDeleted*(self: AccessInterface, communityId: string, permissionId: string) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionDeletionFailed*(self: AccessInterface, communityId: string) = + raise newException(ValueError, "No implementation available") + +method onWalletAccountTokensRebuilt*(self: AccessInterface) = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 57c40cbb65..6df419914a 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -7,6 +7,11 @@ import model as chats_model import item as chat_item import ../../shared_models/user_item as user_item import ../../shared_models/user_model as user_model +import ../../shared_models/token_permissions_model +import ../../shared_models/token_permission_item +import ../../shared_models/token_criteria_item +import ../../shared_models/token_criteria_model +import ../../shared_models/token_list_item import chat_content/module as chat_content_module import chat_content/users/module as users_module @@ -24,8 +29,12 @@ import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/mailservers/service as mailservers_service import ../../../../app_service/service/gif/service as gif_service +import ../../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../../app_service/service/token/service as token_service +import ../../../../app_service/service/community_tokens/service as community_tokens_service import ../../../../app_service/service/visual_identity/service as visual_identity import ../../../../app_service/service/contacts/dto/contacts as contacts_dto +import ../../../../app_service/service/community/dto/community as community_dto export io_interface @@ -55,6 +64,8 @@ proc buildChatSectionUI(self: Module, gifService: gif_service.Service, mailserversService: mailservers_service.Service) +proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPermissionDto): TokenPermissionItem + proc newModule*( delegate: delegate_interface.AccessInterface, events: EventEmitter, @@ -68,12 +79,15 @@ proc newModule*( communityService: community_service.Service, messageService: message_service.Service, gifService: gif_service.Service, - mailserversService: mailservers_service.Service + mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service ): Module = result = Module() result.delegate = delegate result.controller = controller.newController(result, sectionId, isCommunity, events, settingsService, nodeConfigurationService, - contactService, chatService, communityService, messageService, gifService, mailserversService) + contactService, chatService, communityService, messageService, gifService, mailserversService, walletAccountService, tokenService, communityTokensService) result.view = view.newView(result) result.viewVariant = newQVariant(result.view) result.moduleLoaded = false @@ -288,6 +302,67 @@ proc initContactRequestsModel(self: Module) = self.view.contactRequestsModel().addItems(contactsWhoAddedMe) +proc rebuildCommunityTokenPermissionsModel(self: Module) = + let community = self.controller.getMyCommunity() + var tokenPermissionsItems: seq[TokenPermissionItem] = @[] + var allTokenRequirementsMet = false + + for id, tokenPermission in community.tokenPermissions: + # TODO: for startes we only deal with "become member" permissions + if tokenPermission.`type` == TokenPermissionType.BecomeMember: + let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission) + + # multiple permissions of the same type act as logical OR + # so if at least one of them is fulfilled we can mark the view + # as all lights green + if tokenPermissionItem.tokenCriteriaMet: + allTokenRequirementsMet = true + + tokenPermissionsItems.add(tokenPermissionItem) + + self.view.tokenPermissionsModel().setItems(tokenPermissionsItems) + self.view.setAllTokenRequirementsMet(allTokenRequirementsMet) + +proc initCommunityTokenPermissionsModel(self: Module) = + self.rebuildCommunityTokenPermissionsModel() + +proc buildTokenList(self: Module) = + + var tokenListItems: seq[TokenListItem] + var collectiblesListItems: seq[TokenListItem] + + let erc20Tokens = self.controller.getTokenList() + let communityTokens = self.controller.getCommunityTokenList() + + for token in erc20Tokens: + let tokenListItem = initTokenListItem( + key = token.symbol, + name = token.name, + symbol = token.symbol, + color = token.color, + image = "", + category = ord(TokenListItemCategory.General) + ) + + tokenListItems.add(tokenListItem) + + for token in communityTokens: + let tokenListItem = initTokenListItem( + key = token.symbol, + name = token.name, + symbol = token.symbol, + color = "", # community tokens don't have `color` + image = token.image, + category = ord(TokenListItemCategory.Community) + ) + collectiblesListItems.add(tokenListItem) + + self.view.setTokenListItems(tokenListItems) + self.view.setCollectiblesListItems(collectiblesListItems) + +method onWalletAccountTokensRebuilt*(self: Module) = + self.rebuildCommunityTokenPermissionsModel() + proc convertPubKeysToJson(self: Module, pubKeys: string): seq[string] = return map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr) @@ -327,6 +402,8 @@ method load*( self.initContactRequestsModel() else: self.usersModule.load() + self.initCommunityTokenPermissionsModel() + self.buildTokenList() let activeChatId = self.controller.getActiveChatId() let isCurrentSectionActive = self.controller.getIsCurrentSectionActive() @@ -664,6 +741,49 @@ method onChatMuted*(self: Module, chatId: string) = method onChatUnmuted*(self: Module, chatId: string) = self.view.chatsModel().changeMutedOnItemById(chatId, muted=false) +method onCommunityTokenPermissionDeleted*(self: Module, communityId: string, permissionId: string) = + self.view.tokenPermissionsModel().removeItemWithId(permissionId) + singletonInstance.globalEvents.showCommunityTokenPermissionDeletedNotification(communityId, "Community permission deleted", "A token permission has been removed") + +method onCommunityTokenPermissionCreated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + if tokenPermission.`type` == TokenPermissionType.BecomeMember: + let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission) + self.view.tokenPermissionsModel.addItem(tokenPermissionItem) + if tokenPermissionItem.tokenCriteriaMet: + self.view.setAllTokenRequirementsMet(true) + singletonInstance.globalEvents.showCommunityTokenPermissionCreatedNotification(communityId, "Community permission created", "A token permission has been added") + +method onCommunityTokenPermissionUpdated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + if tokenPermission.`type` == TokenPermissionType.BecomeMember: + let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission) + self.view.tokenPermissionsModel.updateItem(tokenPermission.id, tokenPermissionItem) + if tokenPermissionItem.tokenCriteriaMet: + self.view.setAllTokenRequirementsMet(true) + return + + # we now need to check whether any other permission criteria where met. + let community = self.controller.getMyCommunity() + for id, permission in community.tokenPermissions: + if id != tokenPermission.id: + for tc in permission.tokenCriteria: + let balance = self.controller.allAccountsTokenBalance(tc.symbol) + let amount = tc.amount.parseFloat + let tokenCriteriaMet = balance >= amount + if tokenCriteriaMet: + return + + self.view.setAllTokenRequirementsMet(false) + singletonInstance.globalEvents.showCommunityTokenPermissionUpdatedNotification(communityId, "Community permission updated", "A token permission has been updated") + +method onCommunityTokenPermissionCreationFailed*(self: Module, communityId: string) = + singletonInstance.globalEvents.showCommunityTokenPermissionCreationFailedNotification(communityId, "Failed to create community permission", "Something went wrong") + +method onCommunityTokenPermissionUpdateFailed*(self: Module, communityId: string) = + singletonInstance.globalEvents.showCommunityTokenPermissionUpdateFailedNotification(communityId, "Failed to update community permission", "Something went wrong") + +method onCommunityTokenPermissionDeletionFailed*(self: Module, communityId: string) = + singletonInstance.globalEvents.showCommunityTokenPermissionDeletionFailedNotification(communityId, "Failed to delete community permission", "Something went wrong") + method onMarkAllMessagesRead*(self: Module, chatId: string) = self.updateBadgeNotifications(chatId, hasUnreadMessages=false, unviewedMentionsCount=0) let chatDetails = self.controller.getChatDetails(chatId) @@ -995,3 +1115,65 @@ method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto]) method joinSpectatedCommunity*(self: Module) = if self.usersModule != nil: self.usersModule.updateMembersList() + +method createOrEditCommunityTokenPermission*(self: Module, communityId: string, permissionId: string, permissionType: int, tokenCriteriaJson: string, isPrivate: bool) = + var tokenPermission = CommunityTokenPermissionDto() + tokenPermission.id = permissionId + tokenPermission.isPrivate = isPrivate + tokenPermission.`type` = TokenPermissionType(permissionType) + + let tokenCriteriaJsonObj = tokenCriteriaJson.parseJson + for tokenCriteria in tokenCriteriaJsonObj: + + let viewAmount = tokenCriteria{"amount"}.getFloat + var tokenCriteriaDto = tokenCriteria.toTokenCriteriaDto + let contractAddresses = self.controller.getContractAddressesForToken(tokenCriteriaDto.symbol) + if contractAddresses.len == 0 and tokenCriteriaDto.`type` != TokenCriteriaType.ENS: + if permissionId == "": + self.onCommunityTokenPermissionCreationFailed(communityId) + return + self.onCommunityTokenPermissionUpdateFailed(communityId) + return + + tokenCriteriaDto.amount = viewAmount.formatBiggestFloat(ffDecimal) + tokenCriteriaDto.contractAddresses = contractAddresses + tokenPermission.tokenCriteria.add(tokenCriteriaDto) + + self.controller.createOrEditCommunityTokenPermission(communityId, tokenPermission) + +method deleteCommunityTokenPermission*(self: Module, communityId: string, permissionId: string) = + self.controller.deleteCommunityTokenPermission(communityId, permissionId) + +proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPermissionDto): TokenPermissionItem = + var tokenCriteriaItems: seq[TokenCriteriaItem] = @[] + var allTokenCriteriaMet = true + + for tc in tokenPermission.tokenCriteria: + + let balance = self.controller.allAccountsTokenBalance(tc.symbol) + let amount = tc.amount.parseFloat + let tokenCriteriaMet = balance >= amount + let tokenCriteriaItem = initTokenCriteriaItem( + tc.symbol, + tc.name, + amount, + tc.`type`.int, + tc.ensPattern, + tokenCriteriaMet + ) + if not tokenCriteriaMet: + allTokenCriteriaMet = false + + tokenCriteriaItems.add(tokenCriteriaItem) + + let tokenPermissionItem = initTokenPermissionItem( + tokenPermission.id, + tokenPermission.`type`.int, + tokenCriteriaItems, + @[], # TODO: handle chat list items + tokenPermission.isPrivate, + allTokenCriteriaMet + ) + + return tokenPermissionItem + diff --git a/src/app/modules/main/chat_section/view.nim b/src/app/modules/main/chat_section/view.nim index 2dfde6f6be..fe5d017ca7 100644 --- a/src/app/modules/main/chat_section/view.nim +++ b/src/app/modules/main/chat_section/view.nim @@ -2,6 +2,11 @@ import NimQml, json, sequtils import model as chats_model import item, active_item import ../../shared_models/user_model as user_model +import ../../shared_models/token_permissions_model +import ../../shared_models/token_permission_item +import ../../shared_models/token_list_model +import ../../shared_models/token_list_item +import ../../../../app_service/service/token/dto import io_interface QtObject: @@ -20,6 +25,13 @@ QtObject: editCategoryChannelsModel: chats_model.Model editCategoryChannelsVariant: QVariant loadingHistoryMessagesInProgress: bool + tokenPermissionsModel: TokenPermissionsModel + tokenPermissionsVariant: QVariant + tokenListModel: TokenListModel + tokenListModelVariant: QVariant + collectiblesListModel: TokenListModel + collectiblesListModelVariant: QVariant + allTokenRequirementsMet: bool proc delete*(self: View) = self.model.delete @@ -32,6 +44,13 @@ QtObject: self.listOfMyContactsVariant.delete self.editCategoryChannelsModel.delete self.editCategoryChannelsVariant.delete + self.tokenPermissionsModel.delete + self.tokenPermissionsVariant.delete + self.tokenListModel.delete + self.tokenListModelVariant.delete + self.collectiblesListModel.delete + self.collectiblesListModelVariant.delete + self.QObject.delete proc newView*(delegate: io_interface.AccessInterface): View = @@ -49,6 +68,12 @@ QtObject: result.listOfMyContacts = user_model.newModel() result.listOfMyContactsVariant = newQVariant(result.listOfMyContacts) result.loadingHistoryMessagesInProgress = false + result.tokenPermissionsModel = newTokenPermissionsModel() + result.tokenPermissionsVariant = newQVariant(result.tokenPermissionsModel) + result.tokenListModel = newTokenListModel() + result.tokenListModelVariant = newQVariant(result.tokenListModel) + result.collectiblesListModel = newTokenListModel() + result.collectiblesListModelVariant = newQVariant(result.collectiblesListModel) proc load*(self: View) = self.delegate.viewDidLoad() @@ -316,3 +341,57 @@ QtObject: proc downloadMessages*(self: View, chatId: string, filePath: string) {.slot.} = self.delegate.downloadMessages(chatId, filePath) + + proc tokenListModel*(self: View): TokenListModel = + result = self.tokenListModel + + proc getTokenListModel(self: View): QVariant{.slot.} = + return self.tokenListModelVariant + + QtProperty[QVariant] tokenList: + read = getTokenListModel + + proc setTokenListItems*(self: View, tokenListItems: seq[TokenListItem]) = + self.tokenListModel.setItems(tokenListItems) + + proc collectiblesListModel*(self: View): TokenListModel = + result = self.collectiblesListModel + + proc getCollectiblesListModel(self: View): QVariant{.slot.} = + return self.collectiblesListModelVariant + + QtProperty[QVariant] collectiblesModel: + read = getCollectiblesListModel + + proc setCollectiblesListItems*(self: View, tokenListItems: seq[TokenListItem]) = + self.collectiblesListModel.setItems(tokenListItems) + + proc tokenPermissionsModel*(self: View): TokenPermissionsModel = + result = self.tokenPermissionsModel + + proc getTokenPermissionsModel(self: View): QVariant{.slot.} = + return self.tokenPermissionsVariant + + QtProperty[QVariant] permissionsModel: + read = getTokenPermissionsModel + + proc createOrEditCommunityTokenPermission*(self: View, communityId: string, permissionId: string, permissionType: int, tokenCriteriaJson: string, isPrivate: bool) {.slot.} = + self.delegate.createOrEditCommunityTokenPermission(communityId, permissionId, permissionType, tokenCriteriaJson, isPrivate) + + proc deleteCommunityTokenPermission*(self: View, communityId: string, permissionId: string) {.slot.} = + self.delegate.deleteCommunityTokenPermission(communityId, permissionId) + + proc getAllTokenRequirementsMet*(self: View): bool {.slot.} = + return self.allTokenRequirementsMet + + proc allTokenRequirementsMetChanged*(self: View) {.signal.} + + proc setAllTokenRequirementsMet*(self: View, value: bool) = + if (value == self.allTokenRequirementsMet): + return + self.allTokenRequirementsMet = value + self.allTokenRequirementsMetChanged() + + QtProperty[bool] allTokenRequirementsMet: + read = getAllTokenRequirementsMet + notify = allTokenRequirementsMetChanged diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index fa4078cd93..7b5b814017 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -18,6 +18,8 @@ import ../../../app_service/service/mailservers/service as mailservers_service import ../../../app_service/service/privacy/service as privacy_service import ../../../app_service/service/node/service as node_service import ../../../app_service/service/community_tokens/service as community_tokens_service +import ../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../app_service/service/token/service as token_service import ../shared_models/section_item, io_interface import ../shared_modules/keycard_popup/io_interface as keycard_shared_module @@ -46,6 +48,8 @@ type communityTokensService: community_tokens_service.Service activeSectionId: string authenticateUserFlowRequestedBy: string + walletAccountService: wallet_account_service.Service + tokenService: token_service.Service # Forward declaration proc setActiveSection*(self: Controller, sectionId: string, skipSavingInSettings: bool = false) @@ -64,6 +68,8 @@ proc newController*(delegate: io_interface.AccessInterface, mailserversService: mailservers_service.Service, nodeService: node_service.Service, communityTokensService: community_tokens_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service ): Controller = result = Controller() @@ -81,6 +87,8 @@ proc newController*(delegate: io_interface.AccessInterface, result.nodeService = nodeService result.mailserversService = mailserversService result.communityTokensService = communityTokensService + result.walletAccountService = walletAccountService + result.tokenService = tokenService proc delete*(self: Controller) = discard @@ -103,6 +111,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService ) self.events.on(SIGNAL_CHATS_LOADING_FAILED) do(e:Args): @@ -135,6 +146,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = args.fromUserAction ) @@ -151,6 +165,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = args.fromUserAction ) @@ -171,6 +188,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = true ) @@ -189,6 +209,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = false ) diff --git a/src/app/modules/main/io_interface.nim b/src/app/modules/main/io_interface.nim index daec4a1c42..41bb1a8b9b 100644 --- a/src/app/modules/main/io_interface.nim +++ b/src/app/modules/main/io_interface.nim @@ -8,7 +8,10 @@ import ../../../app_service/service/community/service as community_service import ../../../app_service/service/message/service as message_service import ../../../app_service/service/gif/service as gif_service import ../../../app_service/service/mailservers/service as mailservers_service -import ../../../app_service/service/community_tokens/service as token_service +import ../../../app_service/service/community_tokens/service as community_token_service +import ../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../app_service/service/token/service as token_service +import ../../../app_service/service/community_tokens/service as community_tokens_service from ../../../app_service/common/types import StatusType import ../../global/app_signals @@ -80,7 +83,10 @@ method onChatsLoaded*( communityService: community_service.Service, messageService: message_service.Service, gifService: gif_service.Service, - mailserversService: mailservers_service.Service) + mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service) {.base.} = raise newException(ValueError, "No implementation available") @@ -121,6 +127,9 @@ method communityJoined*(self: AccessInterface, community: CommunityDto, events: messageService: message_service.Service, gifService: gif_service.Service, mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service, setActive: bool = false,) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index a0083a3282..04a1480f9d 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -157,7 +157,9 @@ proc newModule*[T]( privacyService, mailserversService, nodeService, - communityTokensService + communityTokensService, + walletAccountService, + tokenService ) result.moduleLoaded = false @@ -512,6 +514,9 @@ method onChatsLoaded*[T]( messageService: message_service.Service, gifService: gif_service.Service, mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service ) = var activeSection: SectionItem var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection() @@ -531,7 +536,10 @@ method onChatsLoaded*[T]( communityService, messageService, gifService, - mailserversService + mailserversService, + walletAccountService, + tokenService, + communityTokensService ) let channelGroupItem = self.createChannelGroupItem(channelGroup) self.view.model().addItem(channelGroupItem) @@ -768,6 +776,9 @@ method communityJoined*[T]( messageService: message_service.Service, gifService: gif_service.Service, mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service, setActive: bool = false, ) = var firstCommunityJoined = false @@ -785,7 +796,10 @@ method communityJoined*[T]( communityService, messageService, gifService, - mailserversService + mailserversService, + walletAccountService, + tokenService, + communityTokensService ) let channelGroup = community.toChannelGroupDto() self.channelGroupModules[community.id].load(channelGroup, events, settingsService, nodeConfigurationService, contactsService, @@ -960,6 +974,12 @@ method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle: string, details: NotificationDetails) = if(details.notificationType == NotificationType.NewMessage or details.notificationType == NotificationType.NewMessageWithPersonalMention or + details.notificationType == NotificationType.CommunityTokenPermissionCreated or + details.notificationType == NotificationType.CommunityTokenPermissionUpdated or + details.notificationType == NotificationType.CommunityTokenPermissionDeleted or + details.notificationType == NotificationType.CommunityTokenPermissionCreationFailed or + details.notificationType == NotificationType.CommunityTokenPermissionUpdateFailed or + details.notificationType == NotificationType.CommunityTokenPermissionDeletionFailed or details.notificationType == NotificationType.NewMessageWithGlobalMention): self.displayEphemeralNotification(title, subTitle, "", false, EphemeralNotificationType.Default.int, "", details) diff --git a/src/app/modules/shared_models/token_criteria_item.nim b/src/app/modules/shared_models/token_criteria_item.nim new file mode 100644 index 0000000000..04cf30d547 --- /dev/null +++ b/src/app/modules/shared_models/token_criteria_item.nim @@ -0,0 +1,53 @@ +import strformat + +type + TokenCriteriaItem* = object + symbol*: string + name*: string + amount*: float64 + `type`*: int + ensPattern*: string + criteriaMet*: bool + +proc initTokenCriteriaItem*( + symbol: string, + name: string, + amount: float64, + `type`: int, + ensPattern: string, + criteriaMet: bool +): TokenCriteriaItem = + result.symbol = symbol + result.name = name + result.`type` = `type` + result.ensPattern = ensPattern + result.amount = amount + result.criteriaMet = criteriaMet + +proc `$`*(self: TokenCriteriaItem): string = + result = fmt"""TokenCriteriaItem( + symbol: {self.symbol}, + name: {self.name}, + amount: {self.amount}, + type: {self.type}, + ensPattern: {self.ensPattern}, + criteriaMet: {self.criteriaMet} + ]""" + +proc getType*(self: TokenCriteriaItem): int = + return self.`type` + +proc getSymbol*(self: TokenCriteriaItem): string = + return self.symbol + +proc getName*(self: TokenCriteriaItem): string = + return self.name + +proc getAmount*(self: TokenCriteriaItem): float64 = + return self.amount + +proc getEnsPattern*(self: TokenCriteriaItem): string = + return self.ensPattern + +proc getCriteriaMet*(self: TokenCriteriaItem): bool = + return self.criteriaMet diff --git a/src/app/modules/shared_models/token_criteria_model.nim b/src/app/modules/shared_models/token_criteria_model.nim new file mode 100644 index 0000000000..334fd71ff4 --- /dev/null +++ b/src/app/modules/shared_models/token_criteria_model.nim @@ -0,0 +1,87 @@ +import NimQml, Tables +import token_criteria_item +import ../../../app_service/service/community/dto/community + +type + ModelRole {.pure.} = enum + Key = UserRole + 1 + Type + Symbol + ShortName + Name + Amount + CriteriaMet + +QtObject: + type TokenCriteriaModel* = ref object of QAbstractListModel + items*: seq[TokenCriteriaItem] + + proc setup(self: TokenCriteriaModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenCriteriaModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenCriteriaModel*(): TokenCriteriaModel = + new(result, delete) + result.setup + + method roleNames(self: TokenCriteriaModel): Table[int, string] = + { + ModelRole.Key.int:"key", + ModelRole.Type.int:"type", + ModelRole.Symbol.int:"symbol", + ModelRole.ShortName.int:"shortName", + ModelRole.Name.int:"name", + ModelRole.Amount.int:"amount", + ModelRole.CriteriaMet.int:"available", + }.toTable + + proc countChanged(self: TokenCriteriaModel) {.signal.} + proc getCount(self: TokenCriteriaModel): int {.slot.} = + self.items.len + QtProperty[int] count: + read = getCount + notify = countChanged + + method rowCount(self: TokenCriteriaModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenCriteriaModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Key: + + if item.getType() == ord(TokenCriteriaType.ENS): + result = newQVariant(item.getEnsPattern()) + else: + result = newQVariant(item.getSymbol()) + of ModelRole.Type: + result = newQVariant(item.getType()) + of ModelRole.Symbol: + result = newQVariant(item.getSymbol()) + of ModelRole.ShortName: + result = newQVariant(item.getSymbol()) + of ModelRole.Name: + result = newQVariant(item.getSymbol()) + of ModelRole.Amount: + result = newQVariant(item.getAmount()) + of ModelRole.CriteriaMet: + result = newQVariant(item.getCriteriaMet()) + + proc getItems*(self: TokenCriteriaModel): seq[TokenCriteriaItem] = + return self.items + + proc addItem*(self: TokenCriteriaModel, item: TokenCriteriaItem) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len) + self.items.add(item) + self.endInsertRows() + self.countChanged() diff --git a/src/app/modules/shared_models/token_list_item.nim b/src/app/modules/shared_models/token_list_item.nim new file mode 100644 index 0000000000..22a452ddfb --- /dev/null +++ b/src/app/modules/shared_models/token_list_item.nim @@ -0,0 +1,56 @@ +import strformat + +type TokenListItemCategory* {.pure.}= enum + Community = 0, + Own = 1, + General = 2 + +type + TokenListItem* = object + key*: string + name*: string + symbol*: string + color*: string + image*: string + category*: int + +proc initTokenListItem*( + key: string, + name: string, + symbol: string, + color: string, + image: string, + category: int +): TokenListItem = + result.key = key + result.symbol = symbol + result.name = name + result.color = color + result.image = image + result.category = category + +proc `$`*(self: TokenListItem): string = + result = fmt"""TokenListItem( + key: {self.key}, + name: {self.name}, + color: {self.color}, + symbol: {self.symbol} + ]""" + +proc getKey*(self: TokenListItem): string = + return self.key + +proc getSymbol*(self: TokenListItem): string = + return self.symbol + +proc getName*(self: TokenListItem): string = + return self.name + +proc getColor*(self: TokenListItem): string = + return self.color + +proc getImage*(self: TokenListItem): string = + return self.image + +proc getCategory*(self: TokenListItem): int = + return self.category diff --git a/src/app/modules/shared_models/token_list_model.nim b/src/app/modules/shared_models/token_list_model.nim new file mode 100644 index 0000000000..ada91c330d --- /dev/null +++ b/src/app/modules/shared_models/token_list_model.nim @@ -0,0 +1,92 @@ +import NimQml, Tables +import token_list_item + +type + ModelRole {.pure.} = enum + Key = UserRole + 1 + Name + Shortname + Symbol + Color + Image + Category + +QtObject: + type TokenListModel* = ref object of QAbstractListModel + items*: seq[TokenListItem] + + proc setup(self: TokenListModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenListModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenListModel*(): TokenListModel = + new(result, delete) + result.setup + + proc countChanged(self: TokenListModel) {.signal.} + + proc getCount(self: TokenListModel): int {.slot.} = + self.items.len + + QtProperty[int] count: + read = getCount + notify = countChanged + + proc setItems*(self: TokenlistModel, items: seq[TokenListItem]) = + self.beginResetModel() + self.items = items + self.endResetModel() + self.countChanged() + + proc addItems*(self: TokenlistModel, items: seq[TokenListItem]) = + if(items.len == 0): + return + + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + let first = self.items.len + let last = first + items.len - 1 + self.beginInsertRows(parentModelIndex, first, last) + self.items.add(items) + self.endInsertRows() + self.countChanged() + + method roleNames(self: TokenListModel): Table[int, string] = + { + ModelRole.Key.int:"key", + ModelRole.Name.int:"name", + ModelRole.Symbol.int:"symbol", + ModelRole.Shortname.int:"shortName", + ModelRole.Color.int:"color", + ModelRole.Image.int:"iconSource", + ModelRole.Category.int:"category", + }.toTable + + method rowCount(self: TokenlistModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenListModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Key: + result = newQVariant(item.getKey()) + of ModelRole.Name: + result = newQVariant(item.getName()) + of ModelRole.Symbol: + result = newQVariant(item.getSymbol()) + of ModelRole.Shortname: + result = newQVariant(item.getSymbol()) + of ModelRole.Color: + result = newQVariant(item.getColor()) + of ModelRole.Image: + result = newQVariant(item.getImage()) + of ModelRole.Category: + result = newQVariant(item.getCategory()) diff --git a/src/app/modules/shared_models/token_permission_chat_list_item.nim b/src/app/modules/shared_models/token_permission_chat_list_item.nim new file mode 100644 index 0000000000..0eb4f362b5 --- /dev/null +++ b/src/app/modules/shared_models/token_permission_chat_list_item.nim @@ -0,0 +1,25 @@ +import strformat + +type + TokenPermissionChatListItem* = object + key: string + name: string + +proc `$`*(self: TokenPermissionChatListItem): string = + result = fmt"""TokenPermissionChatListItem( + key: {self.key}, + name: {self.name}, + ]""" + +proc initTokenPermissionChatListItem*( + key: string, + name: string, +): TokenPermissionChatListItem = + result.key = key + result.name = name + +proc getKey*(self: TokenPermissionChatListItem): string = + return self.key + +proc getName*(self: TokenPermissionChatListItem): string = + return self.name diff --git a/src/app/modules/shared_models/token_permission_chat_list_model.nim b/src/app/modules/shared_models/token_permission_chat_list_model.nim new file mode 100644 index 0000000000..def062502b --- /dev/null +++ b/src/app/modules/shared_models/token_permission_chat_list_model.nim @@ -0,0 +1,59 @@ +import NimQml, Tables +import token_permission_chat_list_item + +type + ModelRole {.pure.} = enum + Key = UserRole + 1 + Name + +QtObject: + type TokenPermissionChatListModel* = ref object of QAbstractListModel + items*: seq[TokenPermissionChatListItem] + + proc setup(self: TokenPermissionChatListModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenPermissionChatListModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenPermissionChatListModel*(): TokenPermissionChatListModel = + new(result, delete) + result.setup + + method roleNames(self: TokenPermissionChatListModel): Table[int, string] = + { + ModelRole.Key.int:"key", + ModelRole.Name.int:"name", + }.toTable + + proc countChanged(self: TokenPermissionChatListModel) {.signal.} + proc getCount(self: TokenPermissionChatListModel): int {.slot.} = + self.items.len + QtProperty[int] count: + read = getCount + notify = countChanged + + method rowCount(self: TokenPermissionChatListModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenPermissionChatListModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Key: + result = newQVariant(item.getKey()) + of ModelRole.Name: + result = newQVariant(item.getName()) + + proc addItem*(self: TokenPermissionChatListModel, item: TokenPermissionChatListItem) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len) + self.items.add(item) + self.endInsertRows() + self.countChanged() diff --git a/src/app/modules/shared_models/token_permission_item.nim b/src/app/modules/shared_models/token_permission_item.nim new file mode 100644 index 0000000000..be325f080b --- /dev/null +++ b/src/app/modules/shared_models/token_permission_item.nim @@ -0,0 +1,64 @@ +import strformat +import ../../../app_service/service/community/dto/community +import ../../../app_service/service/chat/dto/chat +import token_criteria_model +import token_criteria_item +import token_permission_chat_list_model +import token_permission_chat_list_item + +type + TokenPermissionItem* = object + id*: string + `type`*: int + tokenCriteria*: TokenCriteriaModel + chatList*: TokenPermissionChatListModel + isPrivate*: bool + tokenCriteriaMet*: bool + +proc `$`*(self: TokenPermissionItem): string = + result = fmt"""TokenPermissionItem( + id: {self.id}, + type: {self.type}, + ]""" + +proc initTokenPermissionItem*( + id: string, + `type`: int, + tokenCriteria: seq[TokenCriteriaItem], + chatList: seq[TokenPermissionChatListItem], + isPrivate: bool, + tokenCriteriaMet: bool +): TokenPermissionItem = + result.id = id + result.`type` = `type` + result.tokenCriteria = newTokenCriteriaModel() + result.chatList = newTokenPermissionChatListModel() + result.isPrivate = isPrivate + result.tokenCriteriaMet = tokenCriteriaMet + + for tcItem in tokenCriteria: + result.tokenCriteria.addItem(tcItem) + + for clItem in chatList: + result.chatList.addItem(clItem) + +proc addTokenCriteria*(self: TokenPermissionItem, tokenCriteria: TokenCriteriaItem) = + self.tokenCriteria.addItem(tokenCriteria) + +proc getId*(self: TokenPermissionItem): string = + return self.id + +proc getType*(self: TokenPermissionItem): int = + return self.`type` + +proc getTokenCriteria*(self: TokenPermissionItem): TokenCriteriaModel = + return self.tokenCriteria + +proc getChatList*(self: TokenPermissionItem): TokenPermissionChatListModel = + return self.chatList + +proc getIsPrivate*(self: TokenPermissionItem): bool = + return self.isPrivate + +proc getTokenCriteriaMet*(self: TokenPermissionItem): bool = + return self.tokenCriteriaMet diff --git a/src/app/modules/shared_models/token_permissions_model.nim b/src/app/modules/shared_models/token_permissions_model.nim new file mode 100644 index 0000000000..f47f5cd63e --- /dev/null +++ b/src/app/modules/shared_models/token_permissions_model.nim @@ -0,0 +1,123 @@ +import NimQml, Tables +import token_permission_item + +type + ModelRole {.pure.} = enum + Id = UserRole + 1 + Key + Type + TokenCriteria + ChatList + IsPrivate + +QtObject: + type TokenPermissionsModel* = ref object of QAbstractListModel + items*: seq[TokenPermissionItem] + + proc setup(self: TokenPermissionsModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenPermissionsModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenPermissionsModel*(): TokenPermissionsModel = + new(result, delete) + result.setup + + method roleNames(self: TokenPermissionsModel): Table[int, string] = + { + ModelRole.Id.int:"id", + ModelRole.Key.int:"key", + ModelRole.Type.int:"permissionType", + ModelRole.TokenCriteria.int:"holdingsListModel", + ModelRole.ChatList.int:"channelsModel", + ModelRole.IsPrivate.int:"isPrivate", + }.toTable + + proc countChanged(self: TokenPermissionsModel) {.signal.} + proc getCount(self: TokenPermissionsModel): int {.slot.} = + self.items.len + QtProperty[int] count: + read = getCount + notify = countChanged + + method rowCount(self: TokenPermissionsModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenPermissionsModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Id: + result = newQVariant(item.getId()) + of ModelRole.Key: + result = newQVariant(item.getId()) + of ModelRole.Type: + result = newQVariant(item.getType()) + of ModelRole.TokenCriteria: + result = newQVariant(item.getTokenCriteria()) + of ModelRole.ChatList: + result = newQVariant(item.getChatList()) + of ModelRole.IsPrivate: + result = newQVariant(item.getIsPrivate()) + + proc addItem*(self: TokenPermissionsModel, item: TokenPermissionItem) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len) + self.items.add(item) + self.endInsertRows() + self.countChanged() + + proc setItems*(self: TokenPermissionsModel, items: seq[TokenPermissionItem]) = + self.beginResetModel() + self.items = items + self.endResetModel() + self.countChanged() + + proc getItems*(self: TokenPermissionsModel): seq[TokenPermissionItem] = + return self.items + + proc findIndexById(self: TokenPermissionsModel, id: string): int = + for i in 0 ..< self.items.len: + if(self.items[i].getId() == id): + return i + return -1 + + proc removeItemWithId*(self: TokenPermissionsModel, permissionId: string) = + let idx = self.findIndexById(permissionId) + if(idx == -1): + return + + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + self.beginRemoveRows(parentModelIndex, idx, idx) + self.items.delete(idx) + self.endRemoveRows() + self.countChanged() + + proc updateItem*(self: TokenPermissionsModel, permissionId: string, item: TokenPermissionItem) = + let idx = self.findIndexById(permissionId) + if(idx == -1): + return + + self.items[idx].id = permissionId + self.items[idx].`type` = item.`type` + self.items[idx].tokenCriteria = item.tokenCriteria + self.items[idx].isPrivate = item.isPrivate + + let index = self.createIndex(idx, 0, nil) + self.dataChanged(index, index, @[ + ModelRole.Id.int, + ModelRole.Key.int, + ModelRole.Type.int, + ModelRole.TokenCriteria.int, + ModelRole.IsPrivate.int + ]) + diff --git a/src/app_service/service/community/dto/community.nim b/src/app_service/service/community/dto/community.nim index 82a3a1bf1b..4036883dba 100644 --- a/src/app_service/service/community/dto/community.nim +++ b/src/app_service/service/community/dto/community.nim @@ -1,6 +1,6 @@ {.used.} -import json, sequtils, sugar, tables +import json, sequtils, sugar, tables, strutils, json_serialization import ../../../../backend/communities include ../../../common/json_utils @@ -29,6 +29,34 @@ type CommunitySettingsDto* = object type CommunityAdminSettingsDto* = object pinMessageAllMembersEnabled*: bool +type TokenPermissionType* {.pure.}= enum + Unknown = 0, + BecomeAdmin = 1, + BecomeMember = 2 + +type TokenCriteriaType* {.pure.}= enum + Unknown = 0, + ERC20 = 1, + ERC721 = 2, + ENS = 3 + +type TokenCriteriaDto* = object + contractAddresses* {.serializedFieldName("contract_addresses").}: Table[int, string] + `type`* {.serializedFieldName("type").}: TokenCriteriaType + symbol* {.serializedFieldName("symbol").}: string + name* {.serializedFieldName("name").}: string + amount* {.serializedFieldName("amount").}: string + decimals* {.serializedFieldName("decimals").}: int + tokenIds* {.serializedFieldName("tokenIds").}: seq[string] + ensPattern* {.serializedFieldName("ens_pattern").}: string + +type CommunityTokenPermissionDto* = object + id*: string + `type`*: TokenPermissionType + tokenCriteria*: seq[TokenCriteriaDto] + chatIds*: seq[string] + isPrivate*: bool + type CommunityDto* = object id*: string admin*: bool @@ -60,6 +88,7 @@ type CommunityDto* = object declinedRequestsToJoin*: seq[CommunityMembershipRequestDto] encrypted*: bool canceledRequestsToJoin*: seq[CommunityMembershipRequestDto] + tokenPermissions*: Table[string, CommunityTokenPermissionDto] type CuratedCommunity* = object available*: bool @@ -132,6 +161,62 @@ proc toDiscordImportTaskProgress*(jsonObj: JsonNode): DiscordImportTaskProgress let importError = error.toDiscordImportError() result.errors.add(importError) +proc toTokenCriteriaDto*(jsonObj: JsonNode): TokenCriteriaDto = + result = TokenCriteriaDto() + discard jsonObj.getProp("amount", result.amount) + discard jsonObj.getProp("decimals", result.decimals) + discard jsonObj.getProp("symbol", result.symbol) + discard jsonObj.getProp("name", result.name) + discard jsonObj.getProp("ens_pattern", result.ensPattern) + + var typeInt: int + discard jsonObj.getProp("type", typeInt) + if (typeInt >= ord(low(TokenCriteriaType)) and typeInt <= ord(high(TokenCriteriaType))): + result.`type` = TokenCriteriaType(typeInt) + + var contractAddressesObj: JsonNode + if(jsonObj.getProp("contractAddresses", contractAddressesObj) and contractAddressesObj.kind == JObject): + result.contractAddresses = initTable[int, string]() + for chainId, contractAddress in contractAddressesObj: + result.contractAddresses[parseInt(chainId)] = contractAddress.getStr + + var tokenIdsObj: JsonNode + if(jsonObj.getProp("tokenIds", tokenIdsObj) and tokenIdsObj.kind == JArray): + for tokenId in tokenIdsObj: + result.tokenIds.add(tokenId.getStr) + + # When `toTokenCriteriaDto` is called with data coming from + # the front-end, there's a key field we have to account for + if jsonObj.hasKey("key"): + if result.`type` == TokenCriteriaType.ENS: + discard jsonObj.getProp("key", result.ensPattern) + else: + discard jsonObj.getProp("key", result.symbol) + +proc toCommunityTokenPermissionDto*(jsonObj: JsonNode): CommunityTokenPermissionDto = + result = CommunityTokenPermissionDto() + discard jsonObj.getProp("id", result.id) + discard jsonObj.getProp("isPrivate", result.isPrivate) + var tokenPermissionTypeInt: int + discard jsonObj.getProp("type", tokenPermissionTypeInt) + if (tokenPermissionTypeInt >= ord(low(TokenPermissionType)) or tokenPermissionTypeInt <= ord(high(TokenPermissionType))): + result.`type` = TokenPermissionType(tokenPermissionTypeInt) + + var tokenCriteriaObj: JsonNode + if(jsonObj.getProp("token_criteria", tokenCriteriaObj)): + for tokenCriteria in tokenCriteriaObj: + result.tokenCriteria.add(tokenCriteria.toTokenCriteriaDto) + + var chatIdsObj: JsonNode + if(jsonObj.getProp("chatIds", chatIdsObj) and chatIdsObj.kind == JArray): + for chatId in chatIdsObj: + result.chatIds.add(chatId.getStr) + + # When `toTokenPermissionDto` is called with data coming from + # the front-end, there's a key field we have to account for + if jsonObj.hasKey("key"): + discard jsonObj.getProp("key", result.id) + proc toCommunityDto*(jsonObj: JsonNode): CommunityDto = result = CommunityDto() discard jsonObj.getProp("id", result.id) @@ -165,6 +250,12 @@ proc toCommunityDto*(jsonObj: JsonNode): CommunityDto = if(jsonObj.getProp("permissions", permissionObj)): result.permissions = toPermission(permissionObj) + var tokenPermissionsObj: JsonNode + if(jsonObj.getProp("tokenPermissions", tokenPermissionsObj) and tokenPermissionsObj.kind == JObject): + result.tokenPermissions = initTable[string, CommunityTokenPermissionDto]() + for tokenPermissionId, tokenPermission in tokenPermissionsObj: + result.tokenPermissions[tokenPermissionId] = toCommunityTokenPermissionDto(tokenPermission) + var adminSettingsObj: JsonNode if(jsonObj.getProp("adminSettings", adminSettingsObj)): result.adminSettings = toCommunityAdminSettingsDto(adminSettingsObj) diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index 05744d1ef3..814f08a1aa 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -1,4 +1,5 @@ import NimQml, Tables, json, sequtils, std/sets, std/algorithm, strformat, strutils, chronicles, json_serialization, sugar +import json_serialization/std/tables as ser_tables import ./dto/community as community_dto @@ -84,6 +85,15 @@ type communityId*: string categoryId*: string + CommunityTokenPermissionArgs* = ref object of Args + communityId*: string + tokenPermission*: CommunityTokenPermissionDto + error*: string + + CommunityTokenPermissionRemovedArgs* = ref object of Args + communityId*: string + permissionId*: string + DiscordCategoriesAndChannelsArgs* = ref object of Args categories*: seq[DiscordCategoryDto] channels*: seq[DiscordChannelDto] @@ -140,11 +150,20 @@ const SIGNAL_COMMUNITY_HISTORY_ARCHIVES_DOWNLOAD_FINISHED* = "communityHistoryAr const SIGNAL_DISCORD_CATEGORIES_AND_CHANNELS_EXTRACTED* = "discordCategoriesAndChannelsExtracted" const SIGNAL_DISCORD_COMMUNITY_IMPORT_FINISHED* = "discordCommunityImportFinished" const SIGNAL_DISCORD_COMMUNITY_IMPORT_PROGRESS* = "discordCommunityImportProgress" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED* = "communityTokenPermissionCreated" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATION_FAILED* = "communityTokenPermissionCreationFailed" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED* = "communityTokenPermissionUpdated" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED* = "communityTokenPermissionUpdateFailed" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED* = "communityTokenPermissionDeleted" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED* = "communityTokenPermissionDeletionFailed" const SIGNAL_CURATED_COMMUNITIES_LOADING* = "curatedCommunitiesLoading" const SIGNAL_CURATED_COMMUNITIES_LOADED* = "curatedCommunitiesLoaded" const SIGNAL_CURATED_COMMUNITIES_LOADING_FAILED* = "curatedCommunitiesLoadingFailed" +const TOKEN_PERMISSIONS_ADDED = "tokenPermissionsAdded" +const TOKEN_PERMISSIONS_MODIFIED = "tokenPermissionsModified" + QtObject: type Service* = ref object of QObject @@ -319,6 +338,14 @@ QtObject: return idx return -1 + proc findIndexBySymbol(symbol: string, tokenCriteria: seq[TokenCriteriaDto]): int = + var idx = -1 + for tc in tokenCriteria: + inc idx + if(tc.symbol == symbol): + return idx + return -1 + proc findIndexById(id: string, categories: seq[Category]): int = var idx = -1 for category in categories: @@ -487,6 +514,49 @@ QtObject: self.allCommunities[community.id] = community self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community])) + # tokenPermission was added + if community.tokenPermissions.len > prev_community.tokenPermissions.len: + for id, tokenPermission in community.tokenPermissions: + if not prev_community.tokenPermissions.hasKey(id): + self.allCommunities[community.id].tokenPermissions[id] = tokenPermission + + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED, + CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: tokenPermission)) + elif community.tokenPermissions.len < prev_community.tokenPermissions.len: + for id, prvTokenPermission in prev_community.tokenPermissions: + if not community.tokenPermissions.hasKey(id): + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED, + CommunityTokenPermissionRemovedArgs(communityId: community.id, permissionId: id)) + + else: + for id, tokenPermission in community.tokenPermissions: + if not prev_community.tokenPermissions.hasKey(id): + continue + + let prevTokenPermission = prev_community.tokenPermissions[id] + + var permissionUpdated = false + + if tokenPermission.tokenCriteria.len != prevTokenPermission.tokenCriteria.len or + tokenPermission.isPrivate != prevTokenPermission.isPrivate or + tokenPermission.`type` != prevTokenPermission.`type`: + + permissionUpdated = true + + for tc in tokenPermission.tokenCriteria: + let index = findIndexBySymbol(tc.symbol, prevTokenPermission.tokenCriteria) + if index == -1: + continue + + let prevTc = prevTokenPermission.tokenCriteria[index] + if tc.amount != prevTc.amount or tc.ensPattern != prevTc.ensPattern: + permissionUpdated = true + break + + if permissionUpdated: + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED, + CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: tokenPermission)) + if(not self.joinedCommunities.hasKey(community.id)): if (community.joined and community.isMember): self.joinedCommunities[community.id] = community @@ -568,6 +638,13 @@ QtObject: proc getCuratedCommunities*(self: Service): seq[CuratedCommunity] = return toSeq(self.curatedCommunities.values) + proc getCommunityByIdFromAllCommunities*(self: Service, communityId: string): CommunityDto = + if(not self.allCommunities.hasKey(communityId)): + error "error: requested community doesn't exists", communityId + return + + return self.allCommunities[communityId] + proc getCommunityById*(self: Service, communityId: string): CommunityDto = if(not self.joinedCommunities.hasKey(communityId)): error "error: requested community doesn't exists", communityId @@ -1557,3 +1634,55 @@ QtObject: except Exception as e: error "Error extracting discord channels and categories", msg = e.msg + proc createOrEditCommunityTokenPermission*(self: Service, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + try: + let editing = tokenPermission.id != "" + + var response: RpcResponse[JsonNode] + + if editing: + response = status_go.editCommunityTokenPermission(communityId, tokenPermission.id, int(tokenPermission.`type`), Json.encode(tokenPermission.tokenCriteria), tokenPermission.isPrivate) + else: + response = status_go.createCommunityTokenPermission(communityId, int(tokenPermission.`type`), Json.encode(tokenPermission.tokenCriteria), tokenPermission.isPrivate) + + if response.result != nil and response.result.kind != JNull: + var changesField = TOKEN_PERMISSIONS_ADDED + if editing: + changesField = TOKEN_PERMISSIONS_MODIFIED + + for permissionId, permission in response.result["communityChanges"].getElems()[0][changesField].pairs(): + let p = permission.toCommunityTokenPermissionDto() + self.allCommunities[communityId].tokenPermissions[permissionId] = p + self.joinedCommunities[communityId].tokenPermissions[permissionId] = p + + var signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED + if editing: + signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED + + self.events.emit(signal, CommunityTokenPermissionArgs(communityId: communityId, tokenPermission: p)) + return + + var signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATION_FAILED + if editing: + signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED + + self.events.emit(signal, CommunityTokenPermissionArgs(communityId: communityId, tokenPermission: tokenPermission)) + except Exception as e: + error "Error creating/editing community token permission", msg = e.msg + + proc deleteCommunityTokenPermission*(self: Service, communityId: string, permissionId: string) = + try: + let response = status_go.deleteCommunityTokenPermission(communityId, permissionId) + if response.result != nil and response.result.kind != JNull: + for permissionId in response.result["communityChanges"].getElems()[0]["tokenPermissionsRemoved"].getElems(): + if self.allCommunities[communityId].tokenPermissions.hasKey(permissionId.getStr()): + self.allCommunities[communityId].tokenPermissions.del(permissionId.getStr()) + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED, + CommunityTokenPermissionRemovedArgs(communityId: communityId, permissionId: permissionId.getStr)) + return + var tokenPermission = CommunityTokenPermissionDto() + tokenPermission.id = permissionId + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED, + CommunityTokenPermissionArgs(communityId: communityId, tokenPermission: tokenPermission)) + except Exception as e: + error "Error deleting community token permission", msg = e.msg diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim index e5a7ed652e..15e77a079d 100644 --- a/src/app_service/service/community_tokens/service.nim +++ b/src/app_service/service/community_tokens/service.nim @@ -125,3 +125,10 @@ QtObject: return parseCommunityTokens(response) except RpcException: error "Error getting community tokens", message = getCurrentExceptionMsg() + + proc getCommunityTokenBySymbol*(self: Service, communityId: string, symbol: string): CommunityTokenDto = + let communityTokens = self.getCommunityTokens(communityId) + for token in communityTokens: + if token.symbol == symbol: + return token + diff --git a/src/app_service/service/token/service.nim b/src/app_service/service/token/service.nim index 5bcdceae1e..a5a9d71c83 100644 --- a/src/app_service/service/token/service.nim +++ b/src/app_service/service/token/service.nim @@ -47,6 +47,8 @@ QtObject: threadpool: ThreadPool networkService: network_service.Service tokens: Table[int, seq[TokenDto]] + tokenList: seq[TokenDto] + tokensToAddressesMap: Table[string, Table[int, string]] priceCache: TimedCache[float64] proc updateCachedTokenPrice(self: Service, crypto: string, fiat: string, price: float64) @@ -67,6 +69,8 @@ QtObject: result.networkService = networkService result.tokens = initTable[int, seq[TokenDto]]() result.priceCache = newTimedCache[float64]() + result.tokenList = @[] + result.tokensToAddressesMap = initTable[string, Table[int, string]]() proc loadData*(self: Service) = if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()): @@ -92,6 +96,32 @@ QtObject: self.tokens[network.chainId] = default_tokens.filter( proc(x: TokenDto): bool = x.chainId == network.chainId ) + + let nativeToken = newTokenDto( + name = network.nativeCurrencyName, + chainId = network.chainId, + address = Address.fromHex("0x0000000000000000000000000000000000000000"), + symbol = network.nativeCurrencySymbol, + decimals = network.nativeCurrencyDecimals, + hasIcon = false, + pegSymbol = "" + ) + + if not self.tokensToAddressesMap.hasKey(network.nativeCurrencySymbol): + self.tokenList.add(nativeToken) + self.tokensToAddressesMap[nativeToken.symbol] = initTable[int, string]() + + if not self.tokensToAddressesMap[nativeToken.symbol].hasKey(nativeToken.chainId): + self.tokensToAddressesMap[nativeToken.symbol][nativeToken.chainId] = $nativeToken.address + + for token in default_tokens: + if not self.tokensToAddressesMap.hasKey(token.symbol): + self.tokenList.add(token) + self.tokensToAddressesMap[token.symbol] = initTable[int, string]() + + if not self.tokensToAddressesMap[token.symbol].hasKey(token.chainId): + self.tokensToAddressesMap[token.symbol][token.chainId] = $token.address + except Exception as e: error "Tokens init error", errDesription = e.msg @@ -99,6 +129,16 @@ QtObject: signalConnect(singletonInstance.localAccountSensitiveSettings, "isWalletEnabledChanged()", self, "onIsWalletEnabledChanged()", 2) self.loadData() + proc getTokenList*(self: Service): seq[TokenDto] = + return self.tokenList + + proc hasContractAddressesForToken*(self: Service, symbol: string): bool = + return self.tokensToAddressesMap.hasKey(symbol) + + proc getContractAddressesForToken*(self: Service, symbol: string): Table[int, string] = + if self.hasContractAddressesForToken(symbol): + return self.tokensToAddressesMap[symbol] + proc onIsWalletEnabledChanged*(self: Service) {.slot.} = self.loadData() diff --git a/src/app_service/service/wallet_account/dto.nim b/src/app_service/service/wallet_account/dto.nim index 41dc93fbcb..8dca054482 100644 --- a/src/app_service/service/wallet_account/dto.nim +++ b/src/app_service/service/wallet_account/dto.nim @@ -147,6 +147,13 @@ proc getAddress*(self: WalletTokenDto): string = return "" +proc getTotalBalanceOfSupportedChains*(self: WalletTokenDto): float64 = + var sum = 0.0 + for chainId, balanceDto in self.balancesPerChain: + sum += balanceDto.balance + + return sum + proc getBalances*(self: WalletTokenDto, chainIds: seq[int]): seq[BalanceDto] = for chainId in chainIds: if self.balancesPerChain.hasKey(chainId): diff --git a/src/app_service/service/wallet_account/service.nim b/src/app_service/service/wallet_account/service.nim index 564cfd378f..7c2af84ff2 100644 --- a/src/app_service/service/wallet_account/service.nim +++ b/src/app_service/service/wallet_account/service.nim @@ -828,4 +828,14 @@ QtObject: if keycardsState.len == 0: return let data = KeycardActivityArgs(success: true) - self.events.emit(SIGNAL_KEYCARDS_SYNCHRONIZED, data) \ No newline at end of file + self.events.emit(SIGNAL_KEYCARDS_SYNCHRONIZED, data) + + proc allAccountsTokenBalance*(self: Service, symbol: string): float64 = + var totalTokenBalance = 0.0 + for walletAccount in self.getWalletAccounts: + if walletAccount.walletType != WalletTypeWatch: + for token in walletAccount.tokens: + if token.symbol == symbol: + totalTokenBalance += token.getTotalBalanceOfSupportedChains() + + return totalTokenBalance diff --git a/src/backend/communities.nim b/src/backend/communities.nim index aac27dac50..89eebce7e0 100644 --- a/src/backend/communities.nim +++ b/src/backend/communities.nim @@ -174,6 +174,29 @@ proc requestImportDiscordCommunity*( "encrypted": encrypted, }]) +proc createCommunityTokenPermission*(communityId: string, permissionType: int, tokenCriteria: string, isPrivate: bool): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("createCommunityTokenPermission".prefix, %*[{ + "communityId": communityId, + "type": permissionType, + "tokenCriteria": parseJson(tokenCriteria), + "isPrivate": isPrivate + }]) + +proc editCommunityTokenPermission*(communityId: string, permissionId: string, permissionType: int, tokenCriteria: string, isPrivate: bool): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("editCommunityTokenPermission".prefix, %*[{ + "communityId": communityId, + "permissionId": permissionId, + "type": permissionType, + "tokenCriteria": parseJson(tokenCriteria), + "isPrivate": isPrivate + }]) + +proc deleteCommunityTokenPermission*(communityId: string, permissionId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("deleteCommunityTokenPermission".prefix, %*[{ + "communityId": communityId, + "permissionId": permissionId + }]) + proc requestCancelDiscordCommunityImport*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = result = callPrivateRPC("requestCancelDiscordCommunityImport".prefix, %*[communityId]) diff --git a/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml b/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml index 9b01f1bce5..ca3796d12c 100644 --- a/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml +++ b/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml @@ -2,7 +2,7 @@ import QtQml 2.14 QtObject { enum Type { - Asset, Collectible, Ens + Unknown, Asset, Collectible, Ens } enum Mode { diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml index c2f4597de6..262a55770c 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml @@ -36,16 +36,16 @@ SettingsPageLayout { readonly property string newPermissionViewState: "NEW_PERMISSION" readonly property string permissionsViewState: "PERMISSIONS" readonly property string editPermissionViewState: "EDIT_PERMISSION" - readonly property bool permissionsExist: store.permissionsModel.count > 0 + readonly property bool permissionsExist: permissionsModel.count > 0 signal saveChanges signal resetChanges property string permissionKeyToEdit - property ListModel holdingsToEditModel: ListModel {} + property var holdingsToEditModel property int permissionTypeToEdit: PermissionTypes.Type.None - property ListModel channelsToEditModel: ListModel {} + property var channelsToEditModel property bool isPrivateToEditValue: false onPermissionsExistChanged: { @@ -55,7 +55,7 @@ SettingsPageLayout { } } - readonly property string initialState: root.store.permissionsModel.count > 0 + readonly property string initialState: root.rootStore.permissionsModel.count > 0 ? d.permissionsViewState : d.welcomeViewState function initializeData() { @@ -169,7 +169,7 @@ SettingsPageLayout { channelsTracker.revision communityNewPermissionView.dirtyValues.permissionType communityNewPermissionView.dirtyValues.isPrivate - const model = root.store.permissionsModel + const model = root.rootStore.permissionsModel const count = model.rowCount() for (let i = 0; i < count; i++) { @@ -273,7 +273,7 @@ SettingsPageLayout { store: root.store function setInitialValuesFromIndex(index) { - const item = ModelUtils.get(root.store.permissionsModel, index) + const item = ModelUtils.get(root.rootStore.permissionsModel, index) d.holdingsToEditModel = item.holdingsListModel d.channelsToEditModel = item.channelsListModel @@ -284,7 +284,7 @@ SettingsPageLayout { onEditPermissionRequested: { setInitialValuesFromIndex(index) d.permissionKeyToEdit = ModelUtils.get( - root.store.permissionsModel, index, "key") + root.rootStore.permissionsModel, index, "key") root.state = d.editPermissionViewState } @@ -294,7 +294,7 @@ SettingsPageLayout { } onRemovePermissionRequested: { - const key = ModelUtils.get(root.store.permissionsModel, index, "key") + const key = ModelUtils.get(root.rootStore.permissionsModel, index, "key") root.store.removePermission(key) } } diff --git a/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml b/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml index 05580cb864..ec4ddcac67 100644 --- a/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml +++ b/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml @@ -7,11 +7,12 @@ import StatusQ.Core.Utils 0.1 QtObject { id: root + property var mainModuleInst: mainModule + property var communitiesModuleInst: communitiesModule readonly property bool isOwner: false property var mintingModuleInst: mintingModule ?? null - property var permissionsModel: ListModel {} // Backend permissions list object model assignment. Please check the current expected data in qml defined in `createPermissions` method property var permissionConflict: QtObject { // Backend conflicts object model assignment. Now mocked data. property bool exists: false property string holdings: qsTr("1 ETH") @@ -20,112 +21,8 @@ QtObject { } - // TODO: Replace to real data, now dummy model - property var assetsModel: ListModel { - Component.onCompleted: { - append([ - { - key: "socks", - iconSource: "qrc:imports/assets/png/tokens/SOCKS.png", - name: "Unisocks", - shortName: "SOCKS", - category: TokenCategories.Category.Community - }, - { - key: "zrx", - iconSource: "qrc:imports/assets/png/tokens/ZRX.png", - name: "Ox", - shortName: "ZRX", - category: TokenCategories.Category.Own - }, - { - key: "1inch", - iconSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png", - name: "1inch", - shortName: "ZRX", - category: TokenCategories.Category.Own - }, - { - key: "Aave", - iconSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png", - name: "Aave", - shortName: "AAVE", - category: TokenCategories.Category.Own - }, - { - key: "Amp", - iconSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png", - name: "Amp", - shortName: "AMP", - category: TokenCategories.Category.Own - } - ]) - } - } - - // TODO: Replace to real data, now dummy model - property var collectiblesModel: ListModel { - Component.onCompleted: { - append([ - { - key: "Anniversary", - iconSource: "qrc:imports/assets/png/collectibles/Anniversary.png", - name: "Anniversary", - category: TokenCategories.Category.Community - }, - { - key: "CryptoKitties", - iconSource: "qrc:imports/assets/png/collectibles/CryptoKitties.png", - name: "CryptoKitties", - category: TokenCategories.Category.Own, - subItems: [ - { - key: "Kitty1", - iconSource: "qrc:imports/assets/png/collectibles/Furbeard.png", - imageSource: "qrc:imports/assets/png/collectibles/FurbeardBig.png", - name: "Furbeard" - }, - { - key: "Kitty2", - iconSource: "qrc:imports/assets/png/collectibles/Magicat.png", - imageSource: "qrc:imports/assets/png/collectibles/MagicatBig.png", - name: "Magicat" - }, - { - key: "Kitty3", - iconSource: "qrc:imports/assets/png/collectibles/HappyMeow.png", - imageSource: "qrc:imports/assets/png/collectibles/HappyMeowBig.png", - name: "Happy Meow" - }, - { - key: "Kitty4", - iconSource: "qrc:imports/assets/png/collectibles/Furbeard.png", - imageSource: "qrc:imports/assets/png/collectibles/FurbeardBig.png", - name: "Furbeard-2" - }, - { - key: "Kitty5", - iconSource: "qrc:imports/assets/png/collectibles/Magicat.png", - imageSource: "qrc:imports/assets/png/collectibles/MagicatBig.png", - name: "Magicat-3" - } - ] - }, - { - key: "SuperRare", - iconSource: "qrc:imports/assets/png/collectibles/SuperRare.png", - name: "SuperRare", - category: TokenCategories.Category.Own - }, - { - key: "Custom", - iconSource: "qrc:imports/assets/png/collectibles/SNT.png", - name: "Custom Collectible", - category: TokenCategories.Category.General - } - ]) - } - } + property var assetsModel: chatCommunitySectionModule.tokenList + property var collectiblesModel: chatCommunitySectionModule.collectiblesModel // TODO: Replace to real data, now dummy model property var channelsModel: ListModel { @@ -151,29 +48,20 @@ QtObject { } function createPermission(holdings, permissionType, isPrivate, channels, index = null) { - // TO BE REPLACED: It shold just be a call to the backend sharing - // `holdings`, `permissions`, `channels` and `isPrivate` properties. - const permissionEntry = d.createPermissionEntry( holdings, permissionType, isPrivate, channels) - - permissionEntry.key = "" + d.keyCounter++ - root.permissionsModel.append(permissionEntry) + chatCommunitySectionModule.createOrEditCommunityTokenPermission(root.mainModuleInst.activeSection.id, "", permissionEntry.permissionType, JSON.stringify(permissionEntry.holdingsListModel), permissionEntry.isPrivate) } function editPermission(key, holdings, permissionType, channels, isPrivate) { - // TO BE REPLACED: Call to backend - const permissionEntry = d.createPermissionEntry( holdings, permissionType, isPrivate, channels) - const index = ModelUtils.indexOf(root.permissionsModel, "key", key) - root.permissionsModel.set(index, permissionEntry) + chatCommunitySectionModule.createOrEditCommunityTokenPermission(root.mainModuleInst.activeSection.id, key, permissionEntry.permissionType, JSON.stringify(permissionEntry.holdingsListModel), permissionEntry.isPrivate) } function removePermission(key) { - const index = ModelUtils.indexOf(root.permissionsModel, "key", key) - root.permissionsModel.remove(index) + chatCommunitySectionModule.deleteCommunityTokenPermission(root.mainModuleInst.activeSection.id, key) } // Minting tokens: diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 9a85fa7533..b1ed05770c 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -165,6 +165,10 @@ QtObject { stickersModule: stickersModuleInst } + property var permissionsModel: chatCommunitySectionModule.permissionsModel + property var assetsModel: chatCommunitySectionModule.tokenList + property var collectiblesModel: chatCommunitySectionModule.collectiblesModel + function sendSticker(channelId, hash, replyTo, pack, url) { stickersModuleInst.send(channelId, hash, replyTo, pack, url) } diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml index 80bd52cdfe..3275240581 100644 --- a/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml @@ -53,7 +53,7 @@ StatusScrollView { } Repeater { - model: root.store.permissionsModel + model: root.rootStore.permissionsModel delegate: PermissionItem { Layout.preferredWidth: root.viewWidth diff --git a/vendor/status-go b/vendor/status-go index 7c7c3a1f13..596660c110 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 7c7c3a1f1335ea166b7839217d9ec37b74ad0191 +Subproject commit 596660c1106a5d991784a1372ba69a9f1e9b1f7e