diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index e2f8d1c8cc..e4b20a7d62 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -225,7 +225,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.settingsService, result.walletAccountService, result.transactionService, result.networkService, result.tokenService) result.tokensService = tokens_service.newService(statusFoundation.events, statusFoundation.threadpool, - result.transactionService, result.tokenService, result.settingsService, result.walletAccountService) + result.transactionService, result.tokenService, result.settingsService, result.walletAccountService, result.activityCenterService, result.communityService) result.providerService = provider_service.newService(statusFoundation.events, statusFoundation.threadpool, result.ensService) result.networkConnectionService = network_connection_service.newService(statusFoundation.events, result.walletAccountService, result.networkService, result.nodeService) result.sharedUrlsService = shared_urls_service.newService(statusFoundation.events, statusFoundation.threadpool) diff --git a/src/app/modules/main/communities/module.nim b/src/app/modules/main/communities/module.nim index c74e7fd4b7..92c4bd3acb 100644 --- a/src/app/modules/main/communities/module.nim +++ b/src/app/modules/main/communities/module.nim @@ -910,4 +910,4 @@ method onCommunityMemberRevealedAccountsLoaded*(self: Module, communityId, membe if revealedAccount.isAirdropAddress: airdropAddress = revealedAccount.address - self.view.setMyRevealedAddressesForCurrentCommunity($(%*addresses), airdropAddress) + self.view.setMyRevealedAddressesForCurrentCommunity($(%*addresses), airdropAddress) \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/controller.nim b/src/app/modules/main/communities/tokens/controller.nim index c5e87f6fd1..cfca0ed08f 100644 --- a/src/app/modules/main/communities/tokens/controller.nim +++ b/src/app/modules/main/communities/tokens/controller.nim @@ -56,6 +56,9 @@ proc init*(self: Controller) = self.events.on(SIGNAL_COMPUTE_BURN_FEE) do(e:Args): let args = ComputeFeeArgs(e) self.communityTokensModule.onBurnFeeComputed(args.ethCurrency, args.fiatCurrency, args.errorCode, args.requestId) + self.events.on(SIGNAL_COMPUTE_SET_SIGNER_FEE) do(e:Args): + let args = ComputeFeeArgs(e) + self.communityTokensModule.onSetSignerFeeComputed(args.ethCurrency, args.fiatCurrency, args.errorCode, args.requestId) self.events.on(SIGNAL_COMPUTE_AIRDROP_FEE) do(e:Args): let args = AirdropFeesArgs(e) self.communityTokensModule.onAirdropFeesComputed(args) @@ -80,6 +83,18 @@ proc init*(self: Controller) = self.events.on(SIGNAL_AIRDROP_STATUS) do(e: Args): let args = AirdropArgs(e) self.communityTokensModule.onAirdropStateChanged(args.communityToken.communityId, args.communityToken.name, args.communityToken.chainId, args.transactionHash, args.status) + self.events.on(SIGNAL_OWNER_TOKEN_RECEIVED) do(e: Args): + let args = OwnerTokenReceivedArgs(e) + self.communityTokensModule.onOwnerTokenReceived(args.communityId, args.communityName, args.chainId, args.contractAddress) + self.events.on(SIGNAL_SET_SIGNER_STATUS) do(e: Args): + let args = SetSignerArgs(e) + self.communityTokensModule.onSetSignerStateChanged(args.communityId, args.chainId, args.transactionHash, args.status) + self.events.on(SIGNAL_COMMUNITY_LOST_OWNERSHIP) do(e: Args): + let args = CommunityIdArgs(e) + self.communityTokensModule.onLostOwnership(args.communityId) + self.events.on(SIGNAL_OWNER_TOKEN_OWNER_ADDRESS) do(e: Args): + let args = OwnerTokenOwnerAddressArgs(e) + self.communityTokensModule.onOwnerTokenOwnerAddress(args.chainId, args.contractAddress, args.address, args.addressName) proc deployContract*(self: Controller, communityId: string, addressFrom: string, password: string, deploymentParams: DeploymentParameters, tokenMetadata: CommunityTokensMetadataDto, tokenImageCropInfoJson: string, chainId: int) = self.communityTokensService.deployContract(communityId, addressFrom, password, deploymentParams, tokenMetadata, tokenImageCropInfoJson, chainId) @@ -106,6 +121,9 @@ proc selfDestructCollectibles*(self: Controller, communityId: string, password: proc burnTokens*(self: Controller, communityId: string, password: string, contractUniqueKey: string, amount: Uint256, addressFrom: string) = self.communityTokensService.burnTokens(communityId, password, contractUniqueKey, amount, addressFrom) +proc setSigner*(self: Controller, password: string, communityId: string, chainId: int, contractAddress: string, addressFrom: string) = + self.communityTokensService.setSigner(password, communityId, chainId, contractAddress, addressFrom) + proc authenticateUser*(self: Controller, keyUid = "") = let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_DEPLOY_COLLECTIBLES_COMMUNITY_TOKENS_MODULE_IDENTIFIER, keyUid: keyUid) self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) @@ -116,6 +134,9 @@ proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTo proc computeDeployFee*(self: Controller, chainId: int, accountAddress: string, tokenType: TokenType, requestId: string) = self.communityTokensService.computeDeployFee(chainId, accountAddress, tokenType, requestId) +proc computeSetSignerFee*(self: Controller, chainId: int, contractAddress: string, addressFrom: string, requestId: string) = + self.communityTokensService.computeSetSignerFee(chainId, contractAddress, addressFrom, requestId) + proc computeDeployOwnerContractsFee*(self: Controller, chainId: int, accountAddress: string, communityId: string, ownerDeploymentParams: DeploymentParameters, masterDeploymentParams: DeploymentParameters, requestId: string) = self.communityTokensService.computeDeployOwnerContractsFee(chainId, accountAddress, communityId, ownerDeploymentParams, masterDeploymentParams, requestId) @@ -138,4 +159,10 @@ proc getTokenMasterToken*(self: Controller, communityId: string): CommunityToken return self.communityTokensService.getTokenMasterToken(communityId) proc getCommunityById*(self: Controller, communityId: string): CommunityDto = - return self.communityService.getCommunityById(communityId) \ No newline at end of file + return self.communityService.getCommunityById(communityId) + +proc declineOwnership*(self: Controller, communityId: string) = + self.communityTokensService.declineOwnership(communityId) + +proc asyncGetOwnerTokenOwnerAddress*(self: Controller, chainId: int, contractAddress: string) = + self.communityTokensService.asyncGetOwnerTokenOwnerAddress(chainId, contractAddress) \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/io_interface.nim b/src/app/modules/main/communities/tokens/io_interface.nim index 3c076197b6..5a1efed941 100644 --- a/src/app/modules/main/communities/tokens/io_interface.nim +++ b/src/app/modules/main/communities/tokens/io_interface.nim @@ -24,6 +24,9 @@ method selfDestructCollectibles*(self: AccessInterface, communityId: string, col method burnTokens*(self: AccessInterface, communityId: string, contractUniqueKey: string, amount: string, addressFrom: string) {.base.} = raise newException(ValueError, "No implementation available") +method setSigner*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, addressFrom: string) {.base.} = + raise newException(ValueError, "No implementation available") + method deployCollectibles*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: string, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, imageCropInfoJson: string) {.base.} = raise newException(ValueError, "No implementation available") @@ -45,6 +48,9 @@ method resetTempValues*(self: AccessInterface) {.base.} = method computeDeployFee*(self: AccessInterface, communityId: string, chainId: int, accountAddress: string, tokenType: TokenType, isOwnerDeployment: bool, requestId: string) {.base.} = raise newException(ValueError, "No implementation available") +method computeSetSignerFee*(self: AccessInterface, chainId: int, contractAddress: string, addressFrom: string, requestId: string) {.base.} = + raise newException(ValueError, "No implementation available") + method computeSelfDestructFee*(self: AccessInterface, collectiblesToBurnJsonString: string, contractUniqueKey: string, addressFrom: string, requestId: string) {.base.} = raise newException(ValueError, "No implementation available") @@ -63,6 +69,9 @@ method onAirdropFeesComputed*(self: AccessInterface, args: AirdropFeesArgs) {.ba method onBurnFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode, responseId: string) {.base.} = raise newException(ValueError, "No implementation available") +method onSetSignerFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode, responseId: string) {.base.} = + raise newException(ValueError, "No implementation available") + method onCommunityTokenDeployStateChanged*(self: AccessInterface, communityId: string, chainId: int, transactionHash: string, deployState: DeployState) {.base.} = raise newException(ValueError, "No implementation available") @@ -83,3 +92,21 @@ method onAirdropStateChanged*(self: AccessInterface, communityId: string, tokenN method removeCommunityToken*(self: AccessInterface, communityId: string, chainId: int, address: string) {.base.} = raise newException(ValueError, "No implementation available") + +method onOwnerTokenReceived*(self: AccessInterface, communityId: string, communityName: string, chainId: int, contractAddress: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onSetSignerStateChanged*(self: AccessInterface, communityId: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) {.base.} = + raise newException(ValueError, "No implementation available") + +method onLostOwnership*(self: AccessInterface, communityId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method declineOwnership*(self: AccessInterface, communityId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onOwnerTokenOwnerAddress*(self: AccessInterface, chainId: int, contractAddress: string, address: string, addressName: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method asyncGetOwnerTokenDetails*(self: AccessInterface, communityId: string) {.base.} = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/models/token_model.nim b/src/app/modules/main/communities/tokens/models/token_model.nim index f4035925e0..71ff123135 100644 --- a/src/app/modules/main/communities/tokens/models/token_model.nim +++ b/src/app/modules/main/communities/tokens/models/token_model.nim @@ -129,6 +129,12 @@ QtObject: self.endResetModel() self.countChanged() + + proc getOwnerToken*(self: TokenModel): TokenItem = + for i in 0 ..< self.items.len: + if(self.items[i].tokenDto.privilegesLevel == PrivilegesLevel.Owner): + return self.items[i] + proc appendItem*(self: TokenModel, item: TokenItem) = let parentModelIndex = newQModelIndex() defer: parentModelIndex.delete diff --git a/src/app/modules/main/communities/tokens/module.nim b/src/app/modules/main/communities/tokens/module.nim index c346485a5a..ec5e8624bf 100644 --- a/src/app/modules/main/communities/tokens/module.nim +++ b/src/app/modules/main/communities/tokens/module.nim @@ -8,6 +8,7 @@ import ../../../../../app_service/service/community/dto/community import ../../../../../app_service/service/accounts/utils as utl import ../../../../../app_service/common/conversion import ../../../../../app_service/common/types +import ../../../../../app_service/common/utils as common_utils import ../../../../core/eventemitter import ../../../../global/global_singleton import ../../../shared_models/currency_amount @@ -24,6 +25,7 @@ type SelfDestruct = 3 Burn = 4 DeployOwnerToken = 5 + SetSigner = 6 type Module* = ref object of io_interface.AccessInterface @@ -48,6 +50,7 @@ type tempMasterDeploymentParams: DeploymentParameters tempOwnerTokenMetadata: CommunityTokensMetadataDto tempMasterTokenMetadata: CommunityTokensMetadataDto + tempOwnerTokenCommunity: CommunityDto proc newCommunityTokensModule*( parent: parent_interface.AccessInterface, @@ -160,6 +163,14 @@ method burnTokens*(self: Module, communityId: string, contractUniqueKey: string, self.tempContractAction = ContractAction.Burn self.authenticate() +method setSigner*(self: Module, communityId: string, chainId: int, contractAddress: string, addressFrom: string) = + self.tempCommunityId = communityId + self.tempChainId = chainId + self.tempContractAddress = contractAddress + self.tempAddressFrom = addressFrom + self.tempContractAction = ContractAction.SetSigner + self.authenticate() + method deployCollectibles*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: string, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, imageCropInfoJson: string) = let (ownerTokenAddress, masterTokenAddress, isDeployed) = self.getOwnerAndMasterTokensAddresses(communityId, chainId) @@ -255,6 +266,8 @@ method onUserAuthenticated*(self: Module, password: string) = self.tempOwnerDeploymentParams, self.tempOwnerTokenMetadata, self.tempMasterDeploymentParams, self.tempMasterTokenMetadata, self.tempTokenImageCropInfoJson, self.tempChainId) + elif self.tempContractAction == ContractAction.SetSigner: + self.controller.setSigner(password, self.tempCommunityId, self.tempChainId, self.tempContractAddress, self.tempAddressFrom) method onDeployFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode, responseId: string) = self.view.updateDeployFee(ethCurrency, fiatCurrency, errorCode.int, responseId) @@ -268,6 +281,9 @@ method onAirdropFeesComputed*(self: Module, args: AirdropFeesArgs) = method onBurnFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode, responseId: string) = self.view.updateBurnFee(ethCurrency, fiatCurrency, errorCode.int, responseId) +method onSetSignerFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode, responseId: string) = + self.view.updateSetSignerFee(ethCurrency, fiatCurrency, errorCode.int, responseId) + method computeDeployFee*(self: Module, communityId: string, chainId: int, accountAddress: string, tokenType: TokenType, isOwnerDeployment: bool, requestId: string) = if isOwnerDeployment: let (ownerDeploymentParams, masterDeploymentParams) = self.createOwnerAndMasterDeploymentParams(communityId) @@ -275,6 +291,9 @@ method computeDeployFee*(self: Module, communityId: string, chainId: int, accoun else: self.controller.computeDeployFee(chainId, accountAddress, tokenType, requestId) +method computeSetSignerFee*(self: Module, chainId: int, contractAddress: string, addressFrom: string, requestId: string) = + self.controller.computeSetSignerFee(chainId, contractAddress, addressFrom, requestId) + method computeSelfDestructFee*(self: Module, collectiblesToBurnJsonString: string, contractUniqueKey: string, addressFrom: string, requestId: string) = let walletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString) self.controller.computeSelfDestructFee(walletAndAmountList, contractUniqueKey, addressFrom, requestId) @@ -314,3 +333,45 @@ method onAirdropStateChanged*(self: Module, communityId: string, tokenName: stri let url = self.createUrl(chainId, transactionHash) let chainName = self.getChainName(chainId) self.view.emitAirdropStateChanged(communityId, tokenName, chainName, status.int, url) + +method onOwnerTokenReceived*(self: Module, communityId: string, communityName: string, chainId: int, contractAddress: string) = + self.view.emitOwnerTokenReceived(communityId, communityName, chainId, contractAddress) + +method onSetSignerStateChanged*(self: Module, communityId: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) = + let communityDto = self.controller.getCommunityById(communityId) + let communityName = communityDto.name + let url = self.createUrl(chainId, transactionHash) + self.view.emitSetSignerStateChanged(communityId, communityName, status.int, url) + +method onLostOwnership*(self: Module, communityId: string) = + let communityDto = self.controller.getCommunityById(communityId) + let communityName = communityDto.name + self.view.emitOwnershipLost(communityId, communityName) + +method declineOwnership*(self: Module, communityId: string) = + self.controller.declineOwnership(communityId) + +method asyncGetOwnerTokenDetails*(self: Module, communityId: string) = + self.tempOwnerTokenCommunity = self.controller.getCommunityById(communityId) + if self.tempOwnerTokenCommunity.id == "": + error "No community with id", communityId + return + let (chainId, contractAddress) = self.tempOwnerTokenCommunity.getOwnerTokenAddressFromPermissions() + self.controller.asyncGetOwnerTokenOwnerAddress(chainId, contractAddress) + +method onOwnerTokenOwnerAddress*(self: Module, chainId: int, contractAddress: string, address: string, addressName: string) = + let chainName = self.getChainName(chainId) + var symbol = "" + for tokenMetadata in self.tempOwnerTokenCommunity.communityTokensMetadata: + if tokenMetadata.addresses[chainId] == contractAddress: + symbol = tokenMetadata.symbol + break + let jsonObj = %* { + "symbol": symbol, + "chainName": chainName, + "accountName": addressName, + "accountAddress": address, + "chainId": chainId, + "contractAddress": contractAddress + } + self.view.setOwnerTokenDetails($jsonObj) \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/view.nim b/src/app/modules/main/communities/tokens/view.nim index 7ea4da9e4b..75153a3ab5 100644 --- a/src/app/modules/main/communities/tokens/view.nim +++ b/src/app/modules/main/communities/tokens/view.nim @@ -9,6 +9,7 @@ QtObject: type View* = ref object of QObject communityTokensModule: community_tokens_module_interface.AccessInterface + ownerTokenDetails: string proc load*(self: View) = discard @@ -45,26 +46,42 @@ QtObject: proc burnTokens*(self: View, communityId: string, contractUniqueKey: string, amount: string, addressFrom: string) {.slot.} = self.communityTokensModule.burnTokens(communityId, contractUniqueKey, amount, addressFrom) + proc setSigner*(self: View, communityId: string, chainId: int, contractAddress: string, addressFrom: string) {.slot.} = + self.communityTokensModule.setSigner(communityId, chainId, contractAddress, addressFrom) + proc deployFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.} proc selfDestructFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.} proc airdropFeesUpdated*(self: View, json: string) {.signal.} proc burnFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.} + proc setSignerFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.} + proc ownerTokenReceived*(self: View, communityId: string, communityName: string, chainId: int, contractAddress: string) {.signal.} + proc setSignerStateChanged*(self: View, communityId: string, communityName: string, status: int, url: string) {.signal.} + proc ownershipNodeLost*(self: View, communityId: string, communityName: string) {.signal.} proc computeDeployFee*(self: View, communityId: string, chainId: int, accountAddress: string, tokenType: int, isOwnerDeployment: bool, requestId: string) {.slot.} = self.communityTokensModule.computeDeployFee(communityId, chainId, accountAddress, intToEnum(tokenType, TokenType.Unknown), isOwnerDeployment, requestId) + proc computeSetSignerFee*(self: View, chainId: int, contractAddress: string, addressFrom: string, requestId: string) {.slot.} = + self.communityTokensModule.computeSetSignerFee(chainId, contractAddress, addressFrom, requestId) + proc computeSelfDestructFee*(self: View, collectiblesToBurnJsonString: string, contractUniqueKey: string, addressFrom: string, requestId: string) {.slot.} = self.communityTokensModule.computeSelfDestructFee(collectiblesToBurnJsonString, contractUniqueKey, addressFrom, requestId) proc computeBurnFee*(self: View, contractUniqueKey: string, amount: string, addressFrom: string, requestId: string) {.slot.} = self.communityTokensModule.computeBurnFee(contractUniqueKey, amount, addressFrom, requestId) + proc declineOwnership*(self: View, communityId: string) {.slot.} = + self.communityTokensModule.declineOwnership(communityId) + proc updateDeployFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int, responseId: string) = self.deployFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode, responseId) proc updateBurnFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int, responseId: string) = self.burnFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode, responseId) + proc updateSetSignerFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int, responseId: string) = + self.setSignerFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode, responseId) + proc updateSelfDestructFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int, responseId: string) = self.selfDestructFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode, responseId) @@ -93,4 +110,27 @@ QtObject: proc ownerTokenDeploymentStateChanged*(self: View, communityId: string, status: int, url: string) {.signal.} proc emitOwnerTokenDeploymentStateChanged*(self: View, communityId: string, status: int, url: string) = - self.ownerTokenDeploymentStateChanged(communityId, status, url) \ No newline at end of file + self.ownerTokenDeploymentStateChanged(communityId, status, url) + + proc emitOwnerTokenReceived*(self: View, communityId: string, communityName: string, chainId: int, contractAddress: string) = + self.ownerTokenReceived(communityId, communityName, chainId, contractAddress) + + proc emitSetSignerStateChanged*(self: View, communityId: string, communityName: string, status: int, url: string) = + self.setSignerStateChanged(communityId, communityName, status, url) + + proc emitOwnershipLost*(self: View, communityId: string, communityName: string) = + self.ownershipNodeLost(communityId, communityName) + + proc asyncGetOwnerTokenDetails*(self: View, communityId: string) {.slot.} = + self.communityTokensModule.asyncGetOwnerTokenDetails(communityId) + + proc ownerTokenDetailsChanged*(self: View) {.signal.} + proc getOwnerTokenDetails*(self: View): string {.slot.} = + return self.ownerTokenDetails + proc setOwnerTokenDetails*(self: View, ownerTokenDetails: string) = + self.ownerTokenDetails = ownerTokenDetails + self.ownerTokenDetailsChanged() + + QtProperty[string] ownerTokenDetails: + read = getOwnerTokenDetails + notify = ownerTokenDetailsChanged \ No newline at end of file diff --git a/src/app/modules/main/communities/view.nim b/src/app/modules/main/communities/view.nim index 506de7ba4b..02c60d0a4e 100644 --- a/src/app/modules/main/communities/view.nim +++ b/src/app/modules/main/communities/view.nim @@ -786,4 +786,5 @@ QtObject: proc sharedAddressesForAllNonKeycardKeypairsSigned(self: View) {.signal.} proc sendSharedAddressesForAllNonKeycardKeypairsSignedSignal*(self: View) = - self.sharedAddressesForAllNonKeycardKeypairsSigned() \ No newline at end of file + self.sharedAddressesForAllNonKeycardKeypairsSigned() + diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index de1f3eb5c7..73ad078561 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -194,6 +194,7 @@ proc init*(self: Controller) = self.communityTokensService, setActive = args.fromUserAction ) + self.delegate.onFinaliseOwnershipStatusChanged(args.isPendingOwnershipRequest, args.community.id) self.events.on(TOGGLE_SECTION) do(e:Args): let args = ToggleSectionArgs(e) @@ -342,6 +343,10 @@ proc init*(self: Controller) = self.getRemoteDestructedAmount(communityToken.chainId, communityToken.address)) self.delegate.onBurnStateChanged(communityToken.communityId, communityToken.chainId, communityToken.address, args.status) + self.events.on(SIGNAL_FINALISE_OWNERSHIP_STATUS) do(e: Args): + let args = FinaliseOwnershipStatusArgs(e) + self.delegate.onFinaliseOwnershipStatusChanged(args.isPending, args.communityId) + self.events.on(SIGNAL_REMOTE_DESTRUCT_STATUS) do(e: Args): let args = RemoteDestructArgs(e) let communityToken = args.communityToken diff --git a/src/app/modules/main/io_interface.nim b/src/app/modules/main/io_interface.nim index 1d32f218a0..5626892d3a 100644 --- a/src/app/modules/main/io_interface.nim +++ b/src/app/modules/main/io_interface.nim @@ -153,7 +153,7 @@ method communityJoined*(self: AccessInterface, community: CommunityDto, events: walletAccountService: wallet_account_service.Service, tokenService: token_service.Service, communityTokensService: community_tokens_service.Service, - setActive: bool = false,) {.base.} = + setActive: bool = false) {.base.} = raise newException(ValueError, "No implementation available") method communityEdited*(self: AccessInterface, community: CommunityDto) {.base.} = @@ -231,6 +231,9 @@ method getAppSearchModule*(self: AccessInterface): QVariant {.base.} = method getContactDetailsAsJson*(self: AccessInterface, publicKey: string, getVerificationRequest: bool, getOnlineStatus: bool): string {.base.} = raise newException(ValueError, "No implementation available") +method getOwnerTokenAsJson*(self: AccessInterface, communityId: string): string {.base.} = + raise newException(ValueError, "No implementation available") + method isEnsVerified*(self: AccessInterface, publicKey: string): bool {.base.} = raise newException(ValueError, "No implementation available") @@ -318,12 +321,15 @@ method onOwnerTokenDeployStateChanged*(self: AccessInterface, communityId: strin method onCommunityTokenSupplyChanged*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, supply: Uint256, remainingSupply: Uint256, destructedAmount: Uint256) {.base.} = raise newException(ValueError, "No implementation available") -method onCommunityTokenRemoved*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string) = +method onCommunityTokenRemoved*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string) {.base.} = raise newException(ValueError, "No implementation available") method onBurnStateChanged*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, burnState: ContractTransactionStatus) {.base.} = raise newException(ValueError, "No implementation available") +method onFinaliseOwnershipStatusChanged*(self: AccessInterface, isPending: bool, communityId: string) {.base.} = + raise newException(ValueError, "No implementation available") + method onRemoteDestructed*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, addresses: seq[string]) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 62cc71bb86..b75e816ae8 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -24,6 +24,7 @@ import activity_center/module as activity_center_module import communities/module as communities_module import node_section/module as node_section_module import communities/tokens/models/token_item +import communities/tokens/models/token_model import network_connection/module as network_connection_module import shared_urls/module as shared_urls_module @@ -66,6 +67,7 @@ import ../../../app_service/service/network_connection/service as network_connec import ../../../app_service/service/visual_identity/service as procs_from_visual_identity_service import ../../../app_service/common/types import ../../../app_service/common/social_links +import ../../../app_service/common/utils as common_utils import ../../core/notifications/details import ../../core/eventemitter @@ -956,7 +958,7 @@ method communityJoined*[T]( walletAccountService: wallet_account_service.Service, tokenService: token_service.Service, communityTokensService: community_tokens_service.Service, - setActive: bool = false, + setActive: bool = false ) = if self.channelGroupModules.contains(community.id): # The community is already spectated @@ -1079,6 +1081,22 @@ method getContactDetailsAsJson*[T](self: Module[T], publicKey: string, getVerifi } return $jsonObj +# used in FinaliseOwnershipPopup in UI +method getOwnerTokenAsJson*[T](self: Module[T], communityId: string): string = + let item = self.view.model().getItemById(communityId) + if item.id == "": + return + let tokensModel = item.communityTokens() + let ownerToken = tokensModel.getOwnerToken() + let jsonObj = %* { + "symbol": ownerToken.tokenDto.symbol, + "chainName": ownerToken.chainName, + "accountName": ownerToken.accountName, + "accountAddress": ownerToken.tokenDto.deployer, + "contractUniqueKey": common_utils.contractUniqueKey(ownerToken.tokenDto.chainId, ownerToken.tokenDto.address) + } + return $jsonObj + method isEnsVerified*[T](self: Module[T], publicKey: string): bool = return self.controller.getContact(publicKey).ensVerified @@ -1129,6 +1147,9 @@ method onCommunityTokenDeployStateChanged*[T](self: Module[T], communityId: stri if item.id != "": item.updateCommunityTokenDeployState(chainId, contractAddress, deployState) +method onFinaliseOwnershipStatusChanged*[T](self: Module[T], isPending: bool, communityId: string) = + self.view.model().updateIsPendingOwnershipRequest(communityId, isPending) + method onOwnerTokenDeployStateChanged*[T](self: Module[T], communityId: string, chainId: int, ownerContractAddress: string, masterContractAddress: string, deployState: DeployState, transactionHash: string) = let item = self.view.model().getItemById(communityId) if item.id != "": diff --git a/src/app/modules/main/view.nim b/src/app/modules/main/view.nim index 43625439e9..7db0bf7651 100644 --- a/src/app/modules/main/view.nim +++ b/src/app/modules/main/view.nim @@ -211,6 +211,9 @@ QtObject: proc getContactDetailsAsJson(self: View, publicKey: string, getVerificationRequest: bool, getOnlineStatus: bool): string {.slot.} = return self.delegate.getContactDetailsAsJson(publicKey, getVerificationRequest, getOnlineStatus) + proc getOwnerTokenAsJson(self: View, communityId: string): string {.slot.} = + return self.delegate.getOwnerTokenAsJson(communityId) + proc isEnsVerified(self:View, publicKey: string): bool {.slot.} = return self.delegate.isEnsVerified(publicKey) diff --git a/src/app/modules/shared_models/collectibles_model.nim b/src/app/modules/shared_models/collectibles_model.nim index 298a8ac6f1..99396567d5 100644 --- a/src/app/modules/shared_models/collectibles_model.nim +++ b/src/app/modules/shared_models/collectibles_model.nim @@ -1,9 +1,11 @@ -import NimQml, Tables, strutils, strformat, sequtils, stint +import NimQml, Tables, strutils, strformat, sequtils, stint, json import logging import ./collectibles_item, ./collectible_trait_model import web3/ethtypes as eth import backend/activity as backend_activity +import backend/community_tokens_types +import ../../../app_service/common/utils as common_utils type CollectibleRole* {.pure.} = enum @@ -379,4 +381,5 @@ QtObject: # Fallback, create uid from data, because it still might not be fetched if chainId > 0 and len(tokenAddress) > 0 and len(tokenId) > 0: return $chainId & "+" & tokenAddress & "+" & tokenId - return "" \ No newline at end of file + return "" + diff --git a/src/app/modules/shared_models/section_item.nim b/src/app/modules/shared_models/section_item.nim index 21e48fefb8..0f6ae4c6f7 100644 --- a/src/app/modules/shared_models/section_item.nim +++ b/src/app/modules/shared_models/section_item.nim @@ -60,6 +60,7 @@ type pubsubTopic: string pubsubTopicKey: string shardIndex: int + isPendingOwnershipRequest: bool proc initItem*( id: string, @@ -100,6 +101,7 @@ proc initItem*( pubsubTopic = "", pubsubTopicKey = "", shardIndex = -1, + isPendingOwnershipRequest: bool = false ): SectionItem = result.id = id result.sectionType = sectionType @@ -145,6 +147,7 @@ proc initItem*( result.pubsubTopic = pubsubTopic result.pubsubTopicKey = pubsubTopicKey result.shardIndex = shardIndex + result.isPendingOwnershipRequest = isPendingOwnershipRequest proc isEmpty*(self: SectionItem): bool = return self.id.len == 0 @@ -185,6 +188,7 @@ proc `$`*(self: SectionItem): string = declinedMemberRequests:{self.declinedMemberRequestsModel}, encrypted:{self.encrypted}, communityTokensModel:{self.communityTokensModel}, + isPendingOwnershipRequest:{self.isPendingOwnershipRequest} ]""" proc id*(self: SectionItem): string {.inline.} = @@ -325,6 +329,12 @@ proc pendingMemberRequests*(self: SectionItem): member_model.Model {.inline.} = proc declinedMemberRequests*(self: SectionItem): member_model.Model {.inline.} = self.declinedMemberRequestsModel +proc isPendingOwnershipRequest*(self: SectionItem): bool {.inline.} = + self.isPendingOwnershipRequest + +proc setIsPendingOwnershipRequest*(self: var SectionItem, isPending: bool) {.inline.} = + self.isPendingOwnershipRequest = isPending + proc pendingRequestsToJoin*(self: SectionItem): PendingRequestModel {.inline.} = self.pendingRequestsToJoinModel diff --git a/src/app/modules/shared_models/section_model.nim b/src/app/modules/shared_models/section_model.nim index 4ede12ee3b..af092f7418 100644 --- a/src/app/modules/shared_models/section_model.nim +++ b/src/app/modules/shared_models/section_model.nim @@ -46,6 +46,7 @@ type PubsubTopic PubsubTopicKey ShardIndex + IsPendingOwnershipRequest QtObject: type @@ -122,6 +123,7 @@ QtObject: ModelRole.PubsubTopic.int:"pubsubTopic", ModelRole.PubsubTopicKey.int:"pubsubTopicKey", ModelRole.ShardIndex.int:"shardIndex", + ModelRole.IsPendingOwnershipRequest.int:"isPendingOwnershipRequest", }.toTable method data(self: SectionModel, index: QModelIndex, role: int): QVariant = @@ -213,6 +215,8 @@ QtObject: result = newQVariant(item.pubsubTopicKey) of ModelRole.ShardIndex: result = newQVariant(item.shardIndex) + of ModelRole.IsPendingOwnershipRequest: + result = newQVariant(item.isPendingOwnershipRequest) proc itemExists*(self: SectionModel, id: string): bool = for it in self.items: @@ -371,6 +375,15 @@ QtObject: if item.sectionType == SectionType.Chat or item.sectionType == SectionType.Community: result += item.notificationsCount + proc updateIsPendingOwnershipRequest*(self: SectionModel, id: string, isPending: bool) = + for i in 0 ..< self.items.len: + if(self.items[i].id == id): + let index = self.createIndex(i, 0, nil) + defer: index.delete + self.items[i].setIsPendingOwnershipRequest(isPending) + self.dataChanged(index, index, @[ModelRole.IsPendingOwnershipRequest.int]) + return + proc updateNotifications*(self: SectionModel, id: string, hasNotification: bool, notificationsCount: int) = for i in 0 ..< self.items.len: if(self.items[i].id == id): diff --git a/src/app_service/service/activity_center/dto/notification.nim b/src/app_service/service/activity_center/dto/notification.nim index ef2cf9e878..718501836c 100644 --- a/src/app_service/service/activity_center/dto/notification.nim +++ b/src/app_service/service/activity_center/dto/notification.nim @@ -21,6 +21,11 @@ type ActivityCenterNotificationType* {.pure.}= enum ContactVerification = 10 ContactRemoved = 11 NewKeypairAddedToPairedDevice = 12 + OwnerTokenReceived = 13 + OwnershipReceived = 14 + OwnershipLost = 15 + SetSignerFailed = 16 + SetSignerDeclined = 17 type ActivityCenterGroup* {.pure.}= enum All = 0, @@ -152,7 +157,12 @@ proc activityCenterNotificationTypesByGroup*(group: ActivityCenterGroup) : seq[i ActivityCenterNotificationType.CommunityKicked.int, ActivityCenterNotificationType.ContactVerification.int, ActivityCenterNotificationType.ContactRemoved.int, - ActivityCenterNotificationType.NewKeypairAddedToPairedDevice.int + ActivityCenterNotificationType.NewKeypairAddedToPairedDevice.int, + ActivityCenterNotificationType.OwnerTokenReceived.int, + ActivityCenterNotificationType.OwnershipReceived.int, + ActivityCenterNotificationType.SetSignerFailed.int, + ActivityCenterNotificationType.SetSignerDeclined.int, + ActivityCenterNotificationType.OwnershipLost.int ] of ActivityCenterGroup.Mentions: return @[ActivityCenterNotificationType.Mention.int] diff --git a/src/app_service/service/activity_center/service.nim b/src/app_service/service/activity_center/service.nim index 6b822b78ff..d698449ea3 100644 --- a/src/app_service/service/activity_center/service.nim +++ b/src/app_service/service/activity_center/service.nim @@ -306,3 +306,20 @@ QtObject: except Exception as e: error "Error marking as dismissed", msg = e.msg result = e.msg + + proc deleteActivityCenterNotifications*(self: Service, notificationIds: seq[string]): string = + try: + discard backend.deleteActivityCenterNotifications(notificationIds) + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_REMOVED, RemoveActivityCenterNotificationsArgs( + notificationIds: notificationIds + )) + self.events.emit(SIGNAL_ACTIVITY_CENTER_NOTIFICATIONS_COUNT_MAY_HAVE_CHANGED, Args()) + except Exception as e: + error "Error deleting notifications", msg = e.msg + result = e.msg + + proc getNotificationForTypeAndCommunityId*(self: Service, notificationType: ActivityCenterNotificationType, communityId: string): ActivityCenterNotificationDto = + let acNotifications = self.getActivityCenterNotifications() + for acNotification in acNotifications: + if acNotification.notificationType == notificationType and acNotification.communityId == communityId: + return acNotification \ No newline at end of file diff --git a/src/app_service/service/community/dto/community.nim b/src/app_service/service/community/dto/community.nim index 2fdaab8bf0..0f7db52dd5 100644 --- a/src/app_service/service/community/dto/community.nim +++ b/src/app_service/service/community/dto/community.nim @@ -635,3 +635,15 @@ proc isAdmin*(self: CommunityDto): bool = proc isPrivilegedUser*(self: CommunityDto): bool = return self.isControlNode or self.isOwner or self.isTokenMaster or self.isAdmin + +proc getOwnerTokenAddressFromPermissions*(self: CommunityDto): (int, string) = + for _, tokenPermission in self.tokenPermissions.pairs: + if tokenPermission.`type` == TokenPermissionType.BecomeTokenOwner: + if len(tokenPermission.tokenCriteria) == 0: + return (0, "") + let addresses = tokenPermission.tokenCriteria[0].contractAddresses + # should be one address + for ch, add in addresses.pairs: + return (ch, add) + return (0, "") + \ No newline at end of file diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index 7ae8c128d8..43b00161a3 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -15,6 +15,7 @@ import ../../../app/core/eventemitter import ../../../app/core/[main] import ../../../app/core/tasks/[qt, threadpool] import ../../../backend/communities as status_go +import ../../../backend/community_tokens as tokens_backend import ../../../app_service/common/types import ../../../app_service/common/utils @@ -32,6 +33,7 @@ type communityId*: string # should be set when community is nil (i.e. error occured) error*: string fromUserAction*: bool + isPendingOwnershipRequest*: bool CommunitiesArgs* = ref object of Args communities*: seq[CommunityDto] @@ -241,6 +243,7 @@ const SIGNAL_CHECK_PERMISSIONS_TO_JOIN_RESPONSE* = "checkPermissionsToJoinRespon const SIGNAL_CHECK_PERMISSIONS_TO_JOIN_FAILED* = "checkPermissionsToJoinFailed" const SIGNAL_COMMUNITY_METRICS_UPDATED* = "communityMetricsUpdated" +const SIGNAL_COMMUNITY_LOST_OWNERSHIP* = "communityLostOwnership" const SIGNAL_COMMUNITY_SHARD_SET* = "communityShardSet" const SIGNAL_COMMUNITY_SHARD_SET_FAILED* = "communityShardSetFailed" @@ -550,6 +553,12 @@ QtObject: let prev_community = self.communities[community.id] + # ownership lost + if prev_community.isOwner and not community.isOwner: + self.events.emit(SIGNAL_COMMUNITY_LOST_OWNERSHIP, CommunityIdArgs(communityId: community.id)) + let response = tokens_backend.registerLostOwnershipNotification(community.id) + self.activityCenterService.parseActivityCenterResponse(response) + # If there's settings without `id` it means the original # signal didn't include actual communitySettings, hence we # assign the settings we already have, otherwise we risk our @@ -958,8 +967,10 @@ QtObject: self.communities[communityId] = updatedCommunity self.chatService.loadChannelGroupById(communityId) + let ownerTokenNotification = self.activityCenterService.getNotificationForTypeAndCommunityId(notification.ActivityCenterNotificationType.OwnerTokenReceived, communityId) + self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[updatedCommunity])) - self.events.emit(SIGNAL_COMMUNITY_SPECTATED, CommunityArgs(community: updatedCommunity, fromUserAction: true)) + self.events.emit(SIGNAL_COMMUNITY_SPECTATED, CommunityArgs(community: updatedCommunity, fromUserAction: true, isPendingOwnershipRequest: (ownerTokenNotification != nil))) for k, chat in updatedCommunity.chats: let fullChatId = communityId & chat.id diff --git a/src/app_service/service/community_tokens/async_tasks.nim b/src/app_service/service/community_tokens/async_tasks.nim index d4ab4ebe25..5dc8267e8f 100644 --- a/src/app_service/service/community_tokens/async_tasks.nim +++ b/src/app_service/service/community_tokens/async_tasks.nim @@ -3,7 +3,7 @@ include ../../common/json_utils import ../../../backend/eth import ../../../backend/community_tokens import ../../../backend/collectibles -import ../../../app/core/tasks/common +include ../../../app/core/tasks/common import ../../../app/core/tasks/qt import ../transaction/dto import ../community/dto/community @@ -87,6 +87,37 @@ const asyncGetDeployFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall. "requestId": arg.requestId, }) +type + AsyncSetSignerFeesArg = ref object of QObjectTaskArg + chainId: int + contractAddress: string + addressFrom: string + newSignerPubKey: string + requestId: string + +const asyncSetSignerFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncSetSignerFeesArg](argEncoded) + try: + var gasTable: Table[ContractTuple, int] # gas per contract + var feeTable: Table[int, SuggestedFeesDto] # fees for chain + let response = eth.suggestedFees(arg.chainId).result + feeTable[arg.chainId] = response.toSuggestedFeesDto() + let gasUsed = community_tokens.estimateSetSignerPubKey(arg.chainId, arg.contractAddress, arg.addressFrom, arg.newSignerPubKey).result.getInt + gasTable[(arg.chainId, "")] = gasUsed + arg.finish(%* { + "feeTable": tableToJsonArray(feeTable), + "gasTable": tableToJsonArray(gasTable), + "chainId": arg.chainId, + "addressFrom": arg.addressFrom, + "error": "", + "requestId": arg.requestId, + }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + "requestId": arg.requestId, + }) + type AsyncGetRemoteBurnFees = ref object of QObjectTaskArg chainId: int @@ -349,3 +380,27 @@ const getAllCommunityTokensTaskArg: Task = proc(argEncoded: string) {.gcsafe, ni "error": e.msg } arg.finish(output) + +type + GetOwnerTokenOwnerAddressArgs = ref object of QObjectTaskArg + chainId*: int + contractAddress*: string + +const getOwnerTokenOwnerAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[GetOwnerTokenOwnerAddressArgs](argEncoded) + try: + let response = tokens_backend.getOwnerTokenOwnerAddress(arg.chainId, arg.contractAddress) + let output = %* { + "chainId": arg.chainId, + "contractAddress": arg.contractAddress, + "address": response.result.getStr(), + "error": "" + } + arg.finish(output) + except Exception as e: + let output = %* { + "chainId": arg.chainId, + "contractAddress": arg.contractAddress, + "error": e.msg + } + arg.finish(output) \ No newline at end of file diff --git a/src/app_service/service/community_tokens/dto/community_token.nim b/src/app_service/service/community_tokens/dto/community_token.nim index c00396cbed..a6011499c9 100644 --- a/src/app_service/service/community_tokens/dto/community_token.nim +++ b/src/app_service/service/community_tokens/dto/community_token.nim @@ -1,22 +1,18 @@ import json, sequtils, stint, strutils, chronicles import ../../../../backend/response_type +import ../../../../backend/community_tokens_types include ../../../common/json_utils import ../../../common/conversion import ../../community/dto/community +export community_tokens_types + type DeployState* {.pure.} = enum Failed, InProgress, Deployed -# determines what is the type of the token: owner, master or normal community contract -type - PrivilegesLevel* {.pure.} = enum - Owner, - TokenMaster, - Community - type CommunityTokenDto* = object tokenType*: TokenType diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim index d627bbe246..746be421bf 100644 --- a/src/app_service/service/community_tokens/service.nim +++ b/src/app_service/service/community_tokens/service.nim @@ -2,14 +2,19 @@ import NimQml, Tables, chronicles, json, stint, strutils, sugar, sequtils import ../../../app/global/global_singleton import ../../../app/core/eventemitter import ../../../app/core/tasks/[qt, threadpool] +import ../../../app/core/signals/types + import ../../../app/modules/shared_models/currency_amount +import ../../../backend/collectibles as collectibles_backend import ../../../backend/communities as communities_backend import ../../../backend/community_tokens as tokens_backend import ../transaction/service as transaction_service import ../token/service as token_service import ../settings/service as settings_service import ../wallet_account/service as wallet_account_service +import ../activity_center/service as ac_service +import ../community/service as community_service import ../ens/utils as ens_utils import ../eth/dto/transaction from backend/collectibles_types import CollectibleOwner @@ -77,6 +82,13 @@ type contractAddress*: string chainId*: int +type + OwnerTokenOwnerAddressArgs* = ref object of Args + chainId*: int + contractAddress*: string + address*: string + addressName*: string + type RemoteDestructArgs* = ref object of Args communityToken*: CommunityTokenDto @@ -90,7 +102,6 @@ type transactionHash*: string status*: ContractTransactionStatus - type ComputeFeeArgs* = ref object of Args ethCurrency*: CurrencyAmount @@ -99,6 +110,13 @@ type contractUniqueKey*: string # used for minting requestId*: string +type + SetSignerArgs* = ref object of Args + transactionHash*: string + status*: ContractTransactionStatus + communityId*: string + chainId*: int + proc `%`*(self: ComputeFeeArgs): JsonNode = result = %* { "ethFee": self.ethCurrency.toJsonNode(), @@ -143,25 +161,58 @@ type communityTokens*: seq[CommunityTokenDto] communityTokenJsonItems*: JsonNode -# Signals which may be emitted by this service: -const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus" -const SIGNAL_COMMUNITY_TOKEN_DEPLOYMENT_STARTED* = "communityTokenDeploymentStarted" -const SIGNAL_COMPUTE_DEPLOY_FEE* = "computeDeployFee" -const SIGNAL_COMPUTE_SELF_DESTRUCT_FEE* = "computeSelfDestructFee" -const SIGNAL_COMPUTE_BURN_FEE* = "computeBurnFee" -const SIGNAL_COMPUTE_AIRDROP_FEE* = "computeAirdropFee" -const SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED* = "communityTokenOwnersFetched" -const SIGNAL_REMOTE_DESTRUCT_STATUS* = "communityTokenRemoteDestructStatus" -const SIGNAL_BURN_STATUS* = "communityTokenBurnStatus" -const SIGNAL_AIRDROP_STATUS* = "airdropStatus" -const SIGNAL_REMOVE_COMMUNITY_TOKEN_FAILED* = "removeCommunityTokenFailed" -const SIGNAL_COMMUNITY_TOKEN_REMOVED* = "communityTokenRemoved" -const SIGNAL_OWNER_TOKEN_DEPLOY_STATUS* = "ownerTokenDeployStatus" -const SIGNAL_OWNER_TOKEN_DEPLOYMENT_STARTED* = "ownerTokenDeploymentStarted" -const SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED* = "communityTokenDetailsLoaded" -const SIGNAL_ALL_COMMUNITY_TOKENS_LOADED* = "allCommunityTokensLoaded" +type + OwnerTokenReceivedArgs* = ref object of Args + communityId*: string + communityName*: string + chainId*: int + contractAddress*: string -const SIGNAL_DEPLOY_OWNER_TOKEN* = "deployOwnerToken" +type + FinaliseOwnershipStatusArgs* = ref object of Args + isPending*: bool + communityId*: string + +type ContractDetails* = object + chainId*: int + contractAddress*: string + communityId*: string + +proc `%`*(self: ContractDetails): JsonNode = + result = %* { + "chainId": self.chainId, + "contractAddress": self.contractAddress, + "communityId": self.communityId, + } + +proc toContractDetails*(jsonObj: JsonNode): ContractDetails = + result = ContractDetails() + discard jsonObj.getProp("chainId", result.chainId) + discard jsonObj.getProp("contractAddress", result.contractAddress) + discard jsonObj.getProp("communityId", result.communityId) + +# Signals which may be emitted by this service: +const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokens-communityTokenDeployStatus" +const SIGNAL_COMMUNITY_TOKEN_DEPLOYMENT_STARTED* = "communityTokens-communityTokenDeploymentStarted" +const SIGNAL_COMPUTE_DEPLOY_FEE* = "communityTokens-computeDeployFee" +const SIGNAL_COMPUTE_SET_SIGNER_FEE* = "communityTokens-computeSetSignerFee" +const SIGNAL_COMPUTE_SELF_DESTRUCT_FEE* = "communityTokens-computeSelfDestructFee" +const SIGNAL_COMPUTE_BURN_FEE* = "communityTokens-computeBurnFee" +const SIGNAL_COMPUTE_AIRDROP_FEE* = "communityTokens-computeAirdropFee" +const SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED* = "communityTokens-communityTokenOwnersFetched" +const SIGNAL_REMOTE_DESTRUCT_STATUS* = "communityTokens-communityTokenRemoteDestructStatus" +const SIGNAL_BURN_STATUS* = "communityTokens-communityTokenBurnStatus" +const SIGNAL_AIRDROP_STATUS* = "communityTokens-airdropStatus" +const SIGNAL_REMOVE_COMMUNITY_TOKEN_FAILED* = "communityTokens-removeCommunityTokenFailed" +const SIGNAL_COMMUNITY_TOKEN_REMOVED* = "communityTokens-communityTokenRemoved" +const SIGNAL_OWNER_TOKEN_DEPLOY_STATUS* = "communityTokens-ownerTokenDeployStatus" +const SIGNAL_OWNER_TOKEN_DEPLOYMENT_STARTED* = "communityTokens-ownerTokenDeploymentStarted" +const SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED* = "communityTokens-communityTokenDetailsLoaded" +const SIGNAL_ALL_COMMUNITY_TOKENS_LOADED* = "communityTokens-allCommunityTokensLoaded" +const SIGNAL_OWNER_TOKEN_RECEIVED* = "communityTokens-ownerTokenReceived" +const SIGNAL_SET_SIGNER_STATUS* = "communityTokens-setSignerStatus" +const SIGNAL_FINALISE_OWNERSHIP_STATUS* = "communityTokens-finaliseOwnershipStatus" +const SIGNAL_OWNER_TOKEN_OWNER_ADDRESS* = "communityTokens-ownerTokenOwnerAddress" QtObject: type @@ -172,6 +223,8 @@ QtObject: tokenService: token_service.Service settingsService: settings_service.Service walletAccountService: wallet_account_service.Service + acService: ac_service.Service + communityService: community_service.Service tokenOwnersTimer: QTimer tokenOwners1SecTimer: QTimer # used to update 1 sec after changes in owners @@ -186,6 +239,7 @@ QtObject: proc fetchAllTokenOwners*(self: Service) proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] proc getCommunityToken*(self: Service, chainId: int, address: string): CommunityTokenDto + proc findContractByUniqueId*(self: Service, contractUniqueKey: string): CommunityTokenDto proc delete*(self: Service) = delete(self.tokenOwnersTimer) @@ -198,7 +252,9 @@ QtObject: transactionService: transaction_service.Service, tokenService: token_service.Service, settingsService: settings_service.Service, - walletAccountService: wallet_account_service.Service + walletAccountService: wallet_account_service.Service, + acService: ac_service.Service, + communityService: community_service.Service ): Service = result = Service() result.QObject.setup @@ -208,6 +264,8 @@ QtObject: result.tokenService = tokenService result.settingsService = settingsService result.walletAccountService = walletAccountService + result.acService = acService + result.communityService = communityService result.tokenOwnersTimer = newQTimer() result.tokenOwnersTimer.setInterval(10*60*1000) signalConnect(result.tokenOwnersTimer, "timeout()", result, "onRefreshTransferableTokenOwners()", 2) @@ -216,10 +274,70 @@ QtObject: result.tokenOwners1SecTimer.setSingleShot(true) signalConnect(result.tokenOwners1SecTimer, "timeout()", result, "onFetchTempTokenOwners()", 2) + proc processReceivedCollectiblesWalletEvent(self: Service, jsonMessage: string) = + try: + let dataMessageJson = parseJson(jsonMessage) + let tokenDataPayload = fromJson(dataMessageJson, CommunityCollectiblesReceivedPayload) + for coll in tokenDataPayload.collectibles: + let privilegesLevel = coll.communityHeader.privilegesLevel + let communityId = coll.communityHeader.communityId + let community = self.communityService.getCommunityById(communityId) + if privilegesLevel == PrivilegesLevel.Owner and not community.isOwner(): + let communityName = coll.communityHeader.communityName + let chainId = coll.id.contractID.chainID + let contractAddress = coll.id.contractID.address + debug "received owner token", contractAddress=contractAddress, chainId=chainId + let tokenReceivedArgs = OwnerTokenReceivedArgs(communityId: communityId, communityName: communityName, chainId: chainId, contractAddress: contractAddress) + self.events.emit(SIGNAL_OWNER_TOKEN_RECEIVED, tokenReceivedArgs) + let finaliseStatusArgs = FinaliseOwnershipStatusArgs(isPending: true, communityId: communityId) + self.events.emit(SIGNAL_FINALISE_OWNERSHIP_STATUS, finaliseStatusArgs) + let response = tokens_backend.registerOwnerTokenReceivedNotification(communityId) + self.acService.parseActivityCenterResponse(response) + except Exception as e: + error "Error registering owner token received notification", msg=e.msg + + proc processSetSignerTransactionEvent(self: Service, transactionArgs: TransactionMinedArgs) = + try: + if not transactionArgs.success: + error "Signer not set" + let contractDetails = transactionArgs.data.parseJson().toContractDetails() + if transactionArgs.success: + # promoteSelfToControlNode will be moved to status-go in next phase + discard tokens_backend.promoteSelfToControlNode(contractDetails.communityId) + let finaliseStatusArgs = FinaliseOwnershipStatusArgs(isPending: false, communityId: contractDetails.communityId) + self.events.emit(SIGNAL_FINALISE_OWNERSHIP_STATUS, finaliseStatusArgs) + + let data = SetSignerArgs(status: if transactionArgs.success: ContractTransactionStatus.Completed else: ContractTransactionStatus.Failed, + chainId: transactionArgs.chainId, + transactionHash: transactionArgs.transactionHash, + communityId: contractDetails.communityId) + self.events.emit(SIGNAL_SET_SIGNER_STATUS, data) + let response = if transactionArgs.success: tokens_backend.registerReceivedOwnershipNotification(contractDetails.communityId) else: tokens_backend.registerSetSignerFailedNotification(contractDetails.communityId) + self.acService.parseActivityCenterResponse(response) + let notificationToSetRead = self.acService.getNotificationForTypeAndCommunityId(notification.ActivityCenterNotificationType.OwnerTokenReceived, contractDetails.communityId) + if notificationToSetRead != nil: + let markAsReadProps = MarkAsReadNotificationProperties( + notificationIds: @[notificationToSetRead.id], + communityId: contractDetails.communityId, + notificationTypes: @[notification.ActivityCenterNotificationType.OwnerTokenReceived] + ) + discard self.acService.markActivityCenterNotificationRead(notificationToSetRead.id, markAsReadProps) + except Exception as e: + error "Error processing set signer transaction", msg=e.msg + proc init*(self: Service) = self.fetchAllTokenOwners() self.tokenOwnersTimer.start() + self.events.on(SignalType.Wallet.event) do(e:Args): + var data = WalletSignal(e) + if data.eventType == collectibles_backend.eventCommunityCollectiblesReceived: + self.processReceivedCollectiblesWalletEvent(data.message) + + self.events.on(PendingTransactionTypeDto.SetSigner.event) do(e: Args): + let receivedData = TransactionMinedArgs(e) + self.processSetSignerTransactionEvent(receivedData) + self.events.on(PendingTransactionTypeDto.DeployCommunityToken.event) do(e: Args): var receivedData = TransactionMinedArgs(e) try: @@ -425,7 +543,7 @@ QtObject: debug "Deployment transaction hash ", transactionHash=transactionHash var ownerToken = self.createCommunityToken(ownerDeploymentParams, ownerTokenMetadata, chainId, temporaryOwnerContractAddress(transactionHash), communityId, addressFrom, PrivilegesLevel.Owner) - var masterToken = self.createCommunityToken(masterDeploymentParams, masterTokenMetadata, chainId, temporaryMasterContractAddress(transactionHash), communityId, addressFrom, PrivilegesLevel.TokenMaster) + var masterToken = self.createCommunityToken(masterDeploymentParams, masterTokenMetadata, chainId, temporaryMasterContractAddress(transactionHash), communityId, addressFrom, PrivilegesLevel.Master) var croppedImage = croppedImageJson.parseJson ownerToken.image = croppedImage{"imagePath"}.getStr @@ -673,6 +791,12 @@ QtObject: let price = self.tokenService.getTokenPrice(cryptoSymbol, currentCurrency) return cryptoBalance * price + proc findContractByUniqueId*(self: Service, contractUniqueKey: string): CommunityTokenDto = + let allTokens = self.getAllCommunityTokens() + for token in allTokens: + if common_utils.contractUniqueKey(token.chainId, token.address) == contractUniqueKey: + return token + proc computeDeployFee*(self: Service, chainId: int, accountAddress: string, tokenType: TokenType, requestId: string) = try: if tokenType != TokenType.ERC20 and tokenType != TokenType.ERC721: @@ -692,6 +816,24 @@ QtObject: #TODO: handle error - emit error signal error "Error loading fees", msg = e.msg + proc computeSetSignerFee*(self: Service, chainId: int, contractAddress: string, accountAddress: string, requestId: string) = + try: + let arg = AsyncSetSignerFeesArg( + tptr: cast[ByteAddress](asyncSetSignerFeesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onSetSignerFees", + chainId: chainId, + contractAddress: contractAddress, + addressFrom: accountAddress, + requestId: requestId, + newSignerPubKey: singletonInstance.userProfile.getPubKey() + ) + self.threadpool.start(arg) + except Exception as e: + #TODO: handle error - emit error signal + error "Error loading fees", msg = e.msg + + proc computeDeployOwnerContractsFee*(self: Service, chainId: int, accountAddress: string, communityId: string, ownerDeploymentParams: DeploymentParameters, masterDeploymentParams: DeploymentParameters, requestId: string) = try: let arg = AsyncDeployOwnerContractsFeesArg( @@ -711,12 +853,6 @@ QtObject: #TODO: handle error - emit error signal error "Error loading fees", msg = e.msg - proc findContractByUniqueId*(self: Service, contractUniqueKey: string): CommunityTokenDto = - let allTokens = self.getAllCommunityTokens() - for token in allTokens: - if common_utils.contractUniqueKey(token.chainId, token.address) == contractUniqueKey: - return token - proc getOwnerBalances(self: Service, contractOwners: seq[CommunityCollectibleOwner], ownerAddress: string): seq[CollectibleBalance] = for owner in contractOwners: if owner.collectibleOwner.address == ownerAddress: @@ -837,6 +973,35 @@ QtObject: except Exception as e: error "Burn error", msg = e.msg + proc setSigner*(self: Service, password: string, communityId: string, chainId: int, contractAddress: string, addressFrom: string) = + try: + let txData = self.buildTransactionDataDto(addressFrom, chainId, contractAddress) + debug "Set signer ", chainId=chainId, address=contractAddress + let signerPubKey = singletonInstance.userProfile.getPubKey() + let response = tokens_backend.setSignerPubKey(chainId, contractAddress, %txData, signerPubKey, common_utils.hashPassword(password)) + let transactionHash = response.result.getStr() + debug "Set signer transaction hash ", transactionHash=transactionHash + + let data = SetSignerArgs(status: ContractTransactionStatus.InProgress, + chainId: chainId, + transactionHash: transactionHash, + communityId: communityId) + + self.events.emit(SIGNAL_SET_SIGNER_STATUS, data) + + # observe transaction state + let contractDetails = ContractDetails(chainId: chainId, contractAddress: contractAddress, communityId: communityId) + self.transactionService.watchTransaction( + transactionHash, + addressFrom, + contractAddress, + $PendingTransactionTypeDto.SetSigner, + $(%contractDetails), + chainId, + ) + except Exception as e: + error "Set signer error", msg = e.msg + proc computeBurnFee*(self: Service, contractUniqueKey: string, amount: Uint256, addressFrom: string, requestId: string) = try: let contract = self.findContractByUniqueId(contractUniqueKey) @@ -946,6 +1111,9 @@ QtObject: proc onDeployFees*(self:Service, response: string) {.slot.} = self.parseFeeResponseAndEmitSignal(response, SIGNAL_COMPUTE_DEPLOY_FEE) + proc onSetSignerFees*(self: Service, response: string) {.slot.} = + self.parseFeeResponseAndEmitSignal(response, SIGNAL_COMPUTE_SET_SIGNER_FEE) + proc onAirdropFees*(self:Service, response: string) {.slot.} = var wholeEthCostForChainWallet: Table[ChainWalletTuple, float] var ethValuesForContracts: Table[ContractTuple, float] @@ -1101,5 +1269,47 @@ QtObject: proc getTokenMasterToken*(self: Service, communityId: string): CommunityTokenDto = let communityTokens = self.getCommunityTokens(communityId) for token in communityTokens: - if token.privilegesLevel == PrivilegesLevel.TokenMaster: - return token \ No newline at end of file + if token.privilegesLevel == PrivilegesLevel.Master: + return token + + proc declineOwnership*(self: Service, communityId: string) = + let notification = self.acService.getNotificationForTypeAndCommunityId(notification.ActivityCenterNotificationType.OwnerTokenReceived, communityId) + if notification != nil: + discard self.acService.deleteActivityCenterNotifications(@[notification.id]) + try: + let response = tokens_backend.registerSetSignerDeclinedNotification(communityId) + self.acService.parseActivityCenterResponse(response) + except Exception as e: + error "Error registering decline set signer notification", msg=e.msg + let finaliseStatusArgs = FinaliseOwnershipStatusArgs(isPending: false, communityId: communityId) + self.events.emit(SIGNAL_FINALISE_OWNERSHIP_STATUS, finaliseStatusArgs) + + proc asyncGetOwnerTokenOwnerAddress*(self: Service, chainId: int, contractAddress: string) = + let arg = GetOwnerTokenOwnerAddressArgs( + tptr: cast[ByteAddress](getOwnerTokenOwnerAddressTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onGetOwnerTokenOwner", + chainId: chainId, + contractAddress: contractAddress + ) + self.threadpool.start(arg) + + proc onGetOwnerTokenOwner*(self:Service, response: string) {.slot.} = + var ownerTokenArgs = OwnerTokenOwnerAddressArgs() + try: + let responseJson = response.parseJson() + ownerTokenArgs.chainId = responseJson{"chainId"}.getInt + ownerTokenArgs.contractAddress = responseJson{"contractAddress"}.getStr + let errorMsg = responseJson["error"].getStr + if errorMsg != "": + error "can't get owner token owner address", errorMsg + else: + ownerTokenArgs.address = responseJson{"address"}.getStr + let acc = self.walletAccountService.getAccountByAddress(ownerTokenArgs.address) + if acc == nil: + error "getAccountByAddress result is nil" + else: + ownerTokenArgs.addressName = acc.name + except Exception: + error "can't get owner token owner address", message = getCurrentExceptionMsg() + self.events.emit(SIGNAL_OWNER_TOKEN_OWNER_ADDRESS, ownerTokenArgs) \ No newline at end of file diff --git a/src/app_service/service/transaction/dto.nim b/src/app_service/service/transaction/dto.nim index 07daf7be7b..0422408566 100644 --- a/src/app_service/service/transaction/dto.nim +++ b/src/app_service/service/transaction/dto.nim @@ -31,6 +31,7 @@ type RemoteDestructCollectible = "RemoteDestructCollectible" BurnCommunityToken = "BurnCommunityToken" DeployOwnerToken = "DeployOwnerToken" + SetSigner = "SetSigner" proc event*(self:PendingTransactionTypeDto):string = result = "transaction:" & $self diff --git a/src/backend/backend.nim b/src/backend/backend.nim index 034905ad59..51549ec851 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -170,6 +170,9 @@ rpc(acceptActivityCenterNotifications, "wakuext"): rpc(dismissActivityCenterNotifications, "wakuext"): ids: seq[string] +rpc(deleteActivityCenterNotifications, "wakuext"): + ids: seq[string] + rpc(hasUnseenActivityCenterNotifications, "wakuext"): discard diff --git a/src/backend/community_tokens.nim b/src/backend/community_tokens.nim index 68dcb78780..bf44206dc7 100644 --- a/src/backend/community_tokens.nim +++ b/src/backend/community_tokens.nim @@ -72,6 +72,10 @@ proc estimateBurn*(chainId: int, contractAddress: string, fromAddress: string, a let payload = %* [chainId, contractAddress, fromAddress, amount.toString(10)] return core.callPrivateRPC("communitytokens_estimateBurn", payload) +proc estimateSetSignerPubKey*(chainId: int, contractAddress: string, fromAddress: string, newSignerPubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [chainId, contractAddress, fromAddress, newSignerPubkey] + return core.callPrivateRPC("communitytokens_estimateSetSignerPubKey", payload) + proc remainingSupply*(chainId: int, contractAddress: string): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [chainId, contractAddress] return core.callPrivateRPC("communitytokens_remainingSupply", payload) @@ -106,4 +110,36 @@ proc getOwnerTokenContractAddressFromHash*(chainId: int, transactionHash: string proc createCommunityTokenDeploymentSignature*(chainId: int, addressFrom: string, signerAddress: string): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %*[chainId, addressFrom, signerAddress] - return core.callPrivateRPC("wakuext_createCommunityTokenDeploymentSignature", payload) \ No newline at end of file + return core.callPrivateRPC("wakuext_createCommunityTokenDeploymentSignature", payload) + +proc setSignerPubKey*(chainId: int, contractAddress: string, txData: JsonNode, newSignerPubKey: string, hashedPassword: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [chainId, contractAddress, txData, hashedPassword, newSignerPubKey] + return core.callPrivateRPC("communitytokens_setSignerPubKey", payload) + +proc registerOwnerTokenReceivedNotification*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[communityId] + return core.callPrivateRPC("wakuext_registerOwnerTokenReceivedNotification", payload) + +proc registerReceivedOwnershipNotification*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[communityId] + return core.callPrivateRPC("wakuext_registerReceivedOwnershipNotification", payload) + +proc registerSetSignerFailedNotification*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[communityId] + return core.callPrivateRPC("wakuext_registerSetSignerFailedNotification", payload) + +proc registerSetSignerDeclinedNotification*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[communityId] + return core.callPrivateRPC("wakuext_registerSetSignerDeclinedNotification", payload) + +proc registerLostOwnershipNotification*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[communityId] + return core.callPrivateRPC("wakuext_registerLostOwnershipNotification", payload) + +proc promoteSelfToControlNode*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[communityId] + return core.callPrivateRPC("wakuext_promoteSelfToControlNode", payload) + +proc getOwnerTokenOwnerAddress*(chainId: int, contractAddress: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[chainId, contractAddress] + return core.callPrivateRPC("communitytokens_ownerTokenOwnerAddress", payload) \ No newline at end of file diff --git a/storybook/pages/CommunitiesPortalLayoutPage.qml b/storybook/pages/CommunitiesPortalLayoutPage.qml index 32e0fff6a1..579fe53014 100644 --- a/storybook/pages/CommunitiesPortalLayoutPage.qml +++ b/storybook/pages/CommunitiesPortalLayoutPage.qml @@ -11,6 +11,7 @@ import Models 1.0 import utils 1.0 import mainui 1.0 +import shared.stores 1.0 SplitView { id: root @@ -19,6 +20,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } SplitView { diff --git a/storybook/pages/CommunitiesViewPage.qml b/storybook/pages/CommunitiesViewPage.qml index bfdd90bb40..a761eb50e3 100644 --- a/storybook/pages/CommunitiesViewPage.qml +++ b/storybook/pages/CommunitiesViewPage.qml @@ -9,6 +9,7 @@ import utils 1.0 import Storybook 1.0 import Models 1.0 +import shared.stores 1.0 SplitView { id: root @@ -20,6 +21,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } ListModel { diff --git a/storybook/pages/EditSettingsPanelPage.qml b/storybook/pages/EditSettingsPanelPage.qml index 3dcf4d2215..ee981ee8c8 100644 --- a/storybook/pages/EditSettingsPanelPage.qml +++ b/storybook/pages/EditSettingsPanelPage.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import mainui 1.0 +import shared.stores 1.0 import AppLayouts.Communities.panels 1.0 import Storybook 1.0 @@ -14,6 +15,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } EditSettingsPanel { diff --git a/storybook/pages/OverviewSettingsPanelPage.qml b/storybook/pages/OverviewSettingsPanelPage.qml index 594ca1b3d0..920eeda800 100644 --- a/storybook/pages/OverviewSettingsPanelPage.qml +++ b/storybook/pages/OverviewSettingsPanelPage.qml @@ -26,7 +26,6 @@ SplitView { shardIndex: communityEditor.shardIndex isPendingOwnershipRequest: pendingOwnershipSwitch.checked - finaliseOwnershipTransferPopup: undefined } Pane { diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index 87057d7d83..eddb817967 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts 1.14 import utils 1.0 import shared.views 1.0 +import shared.stores 1.0 import mainui 1.0 import StatusQ 0.1 @@ -106,6 +107,7 @@ SplitView { } } } + communityTokensStore: CommunityTokensStore {} } SplitView { diff --git a/storybook/pages/ProfileShowcaseAccountsPanelPage.qml b/storybook/pages/ProfileShowcaseAccountsPanelPage.qml index 20a8d0d5bd..20dde7a3da 100644 --- a/storybook/pages/ProfileShowcaseAccountsPanelPage.qml +++ b/storybook/pages/ProfileShowcaseAccountsPanelPage.qml @@ -6,6 +6,7 @@ import StatusQ.Core.Utils 0.1 as CoreUtils import mainui 1.0 import AppLayouts.Profile.panels 1.0 +import shared.stores 1.0 import utils 1.0 @@ -22,6 +23,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } readonly property string currentWallet: "0xcdc2ea3b6ba8fed3a3402f8db8b2fab53e7b7420" diff --git a/storybook/pages/ProfileShowcaseAssetsPanelPage.qml b/storybook/pages/ProfileShowcaseAssetsPanelPage.qml index 7a6ae784ac..03778f2ae9 100644 --- a/storybook/pages/ProfileShowcaseAssetsPanelPage.qml +++ b/storybook/pages/ProfileShowcaseAssetsPanelPage.qml @@ -6,6 +6,7 @@ import StatusQ.Core.Utils 0.1 as CoreUtils import mainui 1.0 import AppLayouts.Profile.panels 1.0 +import shared.stores 1.0 import utils 1.0 @@ -22,6 +23,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } ListModel { diff --git a/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml b/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml index b7e40828cb..553a14ed1c 100644 --- a/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml +++ b/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml @@ -6,6 +6,7 @@ import StatusQ.Core.Utils 0.1 as CoreUtils import mainui 1.0 import AppLayouts.Profile.panels 1.0 +import shared.stores 1.0 import utils 1.0 @@ -22,6 +23,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } ListModel { diff --git a/storybook/pages/ProfileShowcaseCommunitiesPanelPage.qml b/storybook/pages/ProfileShowcaseCommunitiesPanelPage.qml index 64d24ba7e4..92a61e4ed0 100644 --- a/storybook/pages/ProfileShowcaseCommunitiesPanelPage.qml +++ b/storybook/pages/ProfileShowcaseCommunitiesPanelPage.qml @@ -6,6 +6,7 @@ import StatusQ.Core.Utils 0.1 as CoreUtils import mainui 1.0 import AppLayouts.Profile.panels 1.0 +import shared.stores 1.0 import utils 1.0 @@ -22,6 +23,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } ListModel { diff --git a/storybook/pages/ProfileSocialLinksPanelPage.qml b/storybook/pages/ProfileSocialLinksPanelPage.qml index 57b4e0dd55..e8895c6a6e 100644 --- a/storybook/pages/ProfileSocialLinksPanelPage.qml +++ b/storybook/pages/ProfileSocialLinksPanelPage.qml @@ -6,6 +6,7 @@ import StatusQ.Core.Utils 0.1 as CoreUtils import mainui 1.0 import AppLayouts.Profile.panels 1.0 +import shared.stores 1.0 import utils 1.0 @@ -21,6 +22,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } ListModel { diff --git a/storybook/pages/SyncingEnterCodePage.qml b/storybook/pages/SyncingEnterCodePage.qml index 9f3ad441ae..f68b299aaf 100644 --- a/storybook/pages/SyncingEnterCodePage.qml +++ b/storybook/pages/SyncingEnterCodePage.qml @@ -6,6 +6,7 @@ import Storybook 1.0 import mainui 1.0 import shared.views 1.0 +import shared.stores 1.0 SplitView { id: root @@ -16,6 +17,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } Item { diff --git a/storybook/pages/SyncingViewPage.qml b/storybook/pages/SyncingViewPage.qml index 160caa849d..03ea9c7b01 100644 --- a/storybook/pages/SyncingViewPage.qml +++ b/storybook/pages/SyncingViewPage.qml @@ -7,6 +7,7 @@ import Storybook 1.0 import mainui 1.0 import utils 1.0 import AppLayouts.Profile.views 1.0 +import shared.stores 1.0 SplitView { id: root @@ -17,6 +18,7 @@ SplitView { Popups { popupParent: root rootStore: QtObject {} + communityTokensStore: CommunityTokensStore {} } SyncingView { diff --git a/storybook/stubs/shared/stores/CommunityTokensStore.qml b/storybook/stubs/shared/stores/CommunityTokensStore.qml new file mode 100644 index 0000000000..52d5a7a7f7 --- /dev/null +++ b/storybook/stubs/shared/stores/CommunityTokensStore.qml @@ -0,0 +1,4 @@ +import QtQuick 2.15 + +QtObject { +} diff --git a/storybook/stubs/shared/stores/qmldir b/storybook/stubs/shared/stores/qmldir index a911c315da..31da34a8d2 100644 --- a/storybook/stubs/shared/stores/qmldir +++ b/storybook/stubs/shared/stores/qmldir @@ -1,3 +1,4 @@ +CommunityTokensStore 1.0 CommunityTokensStore.qml CurrenciesStore 1.0 CurrenciesStore.qml NetworkConnectionStore 1.0 NetworkConnectionStore.qml singleton RootStore 1.0 RootStore.qml diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index 99a7191be4..d910267a4c 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -43,7 +43,6 @@ StackLayout { signal openAppSearch() // Community transfer ownership related props/signals: - // TODO: Backend integrations: property bool isPendingOwnershipRequest: sectionItemModel.isPendingOwnershipRequest onCurrentIndexChanged: { diff --git a/ui/app/AppLayouts/Communities/helpers/SetSignerFeesSubscriber.qml b/ui/app/AppLayouts/Communities/helpers/SetSignerFeesSubscriber.qml index 379f54af6c..52c8b9e675 100644 --- a/ui/app/AppLayouts/Communities/helpers/SetSignerFeesSubscriber.qml +++ b/ui/app/AppLayouts/Communities/helpers/SetSignerFeesSubscriber.qml @@ -8,7 +8,8 @@ import QtQuick 2.15 SingleFeeSubscriber { id: root - required property string tokenKey + required property int chainId + required property string contractAddress required property string accountAddress required property bool enabled } diff --git a/ui/app/AppLayouts/Communities/helpers/TransactionFeesBroker.qml b/ui/app/AppLayouts/Communities/helpers/TransactionFeesBroker.qml index 3feb453756..a292ed4fd8 100644 --- a/ui/app/AppLayouts/Communities/helpers/TransactionFeesBroker.qml +++ b/ui/app/AppLayouts/Communities/helpers/TransactionFeesBroker.qml @@ -102,10 +102,12 @@ QtObject { required property SetSignerFeesSubscriber subscriber readonly property var requestArgs: ({ type: TransactionFeesBroker.FeeType.SetSigner, - tokenKey: subscriber.tokenKey, + chainId: subscriber.chainId, + contractAddress: subscriber.contractAddress, accountAddress: subscriber.accountAddress }) - isReady: !!subscriber.tokenKey && + isReady: !!subscriber.chainId && + !!subscriber.contractAddress && !!subscriber.accountAddress && subscriber.enabled @@ -142,6 +144,10 @@ QtObject { function onBurnFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) { d.feesBroker.response(responseId, { ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode }) } + + function onSetSignerFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) { + d.feesBroker.response(responseId, { ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode }) + } } function computeFee(topic) { @@ -190,7 +196,7 @@ QtObject { } function computeSetSignerFee(args, topic) { - communityTokensStore.computeSetSignerFee(args.tokenKey, args.accountAddress, topic) + communityTokensStore.computeSetSignerFee(args.chainId, args.contractAddress, args.accountAddress, topic) } } diff --git a/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipDeclinePopup.qml b/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipDeclinePopup.qml index ac9e99fbe2..e9b47fd975 100644 --- a/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipDeclinePopup.qml +++ b/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipDeclinePopup.qml @@ -14,6 +14,7 @@ StatusDialog { // Community related props: required property string communityName + required property string communityId signal cancelClicked signal declineClicked diff --git a/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipPopup.qml b/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipPopup.qml index c34dd23dea..959ac10f5b 100644 --- a/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/FinaliseOwnershipPopup.qml @@ -107,10 +107,7 @@ StatusDialog { text: qsTr("Make this device the control node and update smart contract") enabled: d.ackCheck && !root.isFeeLoading && root.feeErrorText === "" - onClicked: { - root.finaliseOwnershipClicked() - close() - } + onClicked: root.finaliseOwnershipClicked() } PropertyChanges { target: rejectBtn; visible: false } PropertyChanges { diff --git a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml index b77c2fe61a..7fe5ae2232 100644 --- a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml @@ -823,6 +823,14 @@ StatusSectionLayout { Constants.ephemeralNotificationType.normal, url) } } + + function onOwnershipLost(communityId, communityName) { + if (root.community.id !== communityId) + return + let type = Constants.ephemeralNotificationType.normal + Global.displayToastMessage(qsTr("Your device is no longer the control node for Doodles. + Your ownership and admin rights for %1 have been transferred to the new owner.").arg(communityName), "", "", false, type, "") + } } Connections { diff --git a/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml index 9c6bf03154..15a6804340 100644 --- a/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml @@ -20,4 +20,4 @@ QtObject { function getDetailedCollectible(chainId, contractAddress, tokenId) { walletSection.collectibleDetailsController.getDetailedCollectible(chainId, contractAddress, tokenId) } -} \ No newline at end of file +} diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index fd6e46683f..95e8c1d29e 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -298,8 +298,8 @@ QtObject { openPopup(finaliseOwnershipPopup, { communityId: communityId }) } - function openDeclineOwnershipPopup(communityName) { - openPopup(declineOwnershipPopup, { communityName: communityName }) + function openDeclineOwnershipPopup(communityId, communityName) { + openPopup(declineOwnershipPopup, { communityName: communityName, communityId: communityId }) } readonly property list _components: [ @@ -829,18 +829,17 @@ QtObject { id: finalisePopup property string communityId - readonly property var ownerToken: { - let jsonObj = root.rootStore.mainModuleInst.getOwnerTokenAsJson(finalisePopup.communityId) - return JSON.parse(jsonObj) - } + readonly property var ownerTokenDetails: root.communityTokensStore.ownerTokenDetails readonly property var communityData : root.communitiesStore.getCommunityDetailsAsJson(communityId) + Component.onCompleted: root.communityTokensStore.asyncGetOwnerTokenDetails(communityId) + communityName: communityData.name communityLogo: communityData.image communityColor: communityData.color - tokenSymbol: ownerToken.symbol - tokenChainName: ownerToken.chainName + tokenSymbol: ownerTokenDetails.symbol + tokenChainName: ownerTokenDetails.chainName feeText: feeSubscriber.feeText feeErrorText: feeSubscriber.feeErrorText @@ -850,9 +849,10 @@ QtObject { destroyOnClose: true - onRejectClicked: Global.openDeclineOwnershipPopup(communityData.name) + onRejectClicked: Global.openDeclineOwnershipPopup(finalisePopup.communityId, communityData.name) onFinaliseOwnershipClicked: signPopup.open() - onVisitCommunityClicked: rootStore.setActiveCommunity(communityData.id) + + onVisitCommunityClicked: communitiesStore.navigateToCommunity(finalisePopup.communityId) onOpenControlNodeDocClicked: Global.openLink(link) SetSignerFeesSubscriber { @@ -862,8 +862,9 @@ QtObject { communityTokensStore: root.communityTokensStore } - tokenKey: finalisePopup.ownerToken.contractUniqueKey - accountAddress: finalisePopup.ownerToken.accountAddress + chainId: finalisePopup.ownerTokenDetails.chainId + contractAddress: finalisePopup.ownerTokenDetails.contractAddress + accountAddress: finalisePopup.ownerTokenDetails.accountAddress enabled: finalisePopup.visible || signPopup.visible Component.onCompleted: feesBroker.registerSetSignerFeesSubscriber(feeSubscriber) } @@ -874,7 +875,7 @@ QtObject { title: qsTr("Sign transaction - update %1 smart contract").arg(finalisePopup.communityName) totalFeeText: finalisePopup.isFeeLoading ? "" : finalisePopup.feeText errorText: finalisePopup.feeErrorText - accountName: finalisePopup.ownerToken.accountName + accountName: finalisePopup.ownerTokenDetails.accountName model: QtObject { readonly property string title: finalisePopup.feeLabel @@ -883,8 +884,8 @@ QtObject { } onSignTransactionClicked: { - root.communityTokensStore.updateSmartContract(finalisePopup.communityId, finalisePopup.ownerToken.contractUniqueKey) - close() + finalisePopup.close() + root.communityTokensStore.updateSmartContract(finalisePopup.communityId, finalisePopup.ownerTokenDetails.chainId, finalisePopup.ownerTokenDetails.contractAddress, finalisePopup.ownerTokenDetails.accountAddress) } } @@ -892,7 +893,7 @@ QtObject { target: root onOwnershipDeclined: { finalisePopup.close() - root.communityTokensStore.ownershipDeclined() + root.communityTokensStore.ownershipDeclined(communityId) } } } @@ -903,7 +904,10 @@ QtObject { FinaliseOwnershipDeclinePopup { destroyOnClose: true - onDeclineClicked: root.ownershipDeclined() + onDeclineClicked: { + close() + root.ownershipDeclined() + } } } // End of components related to transfer community ownership flow. diff --git a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml index e649556c57..76169497c6 100644 --- a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml +++ b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml @@ -117,10 +117,10 @@ Popup { case ActivityCenterStore.ActivityCenterNotificationType.NewKeypairAddedToPairedDevice: return newKeypairFromPairedDeviceComponent case ActivityCenterStore.ActivityCenterNotificationType.OwnerTokenReceived: - case ActivityCenterStore.ActivityCenterNotificationType.OwnershipDeclined: - case ActivityCenterStore.ActivityCenterNotificationType.OwnershipSucceeded: + case ActivityCenterStore.ActivityCenterNotificationType.OwnershipReceived: + case ActivityCenterStore.ActivityCenterNotificationType.OwnershipLost: case ActivityCenterStore.ActivityCenterNotificationType.OwnershipFailed: - case ActivityCenterStore.ActivityCenterNotificationType.NoLongerControlNode: + case ActivityCenterStore.ActivityCenterNotificationType.OwnershipDeclined: return ownerTokenReceivedNotificationComponent default: return null diff --git a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml index 44b3710465..3cb4362017 100644 --- a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml +++ b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml @@ -32,10 +32,10 @@ QtObject { ContactRemoved = 11, NewKeypairAddedToPairedDevice = 12, OwnerTokenReceived = 13, - OwnershipDeclined = 14, - OwnershipSucceeded = 15, + OwnershipReceived = 14, + OwnershipLost = 15, OwnershipFailed = 16, - NoLongerControlNode = 17 + OwnershipDeclined = 17 } enum ActivityCenterReadType { diff --git a/ui/imports/shared/stores/CommunityTokensStore.qml b/ui/imports/shared/stores/CommunityTokensStore.qml index 92c9b89dd8..28515b60cd 100644 --- a/ui/imports/shared/stores/CommunityTokensStore.qml +++ b/ui/imports/shared/stores/CommunityTokensStore.qml @@ -12,6 +12,11 @@ QtObject { property var enabledNetworks: networksModule.enabled property var allNetworks: networksModule.all + // set by asyncGetOwnerTokenDetails + readonly property var ownerTokenDetails: { + JSON.parse(communityTokensModuleInst.ownerTokenDetails) + } + signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error, string responseId) signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error, string responseId) signal airdropFeeUpdated(var airdropFees) @@ -25,6 +30,7 @@ QtObject { signal airdropStateChanged(string communityId, string tokenName, string chainName, int status, string url) signal ownerTokenDeploymentStarted(string communityId, string url) signal setSignerStateChanged(string communityId, string communityName, int status, string url) + signal ownershipLost(string communityId, string communityName) // Minting tokens: function deployCollectible(communityId, collectibleItem) @@ -62,13 +68,12 @@ QtObject { communityTokensModuleInst.removeCommunityToken(communityId, parts[0], parts[1]) } - function updateSmartContract(tokenKey, accountAddress) { - console.warn("TODO: Backend to update smart contract and finalise community transfer ownership! The token owner is: " + collectibleItem.symbol) - //communityTokensModuleInst.setSigner(tokenKey, accountAddress) + function updateSmartContract(communityId, chainId, contractAddress, accountAddress) { + communityTokensModuleInst.setSigner(communityId, chainId, contractAddress, accountAddress) } - function ownershipDeclined() { - console.warn("TODO: Backend update notification center and display a toast: Ownership Declined!") + function ownershipDeclined(communityId) { + communityTokensModuleInst.declineOwnership(communityId) } readonly property Connections connections: Connections { @@ -91,8 +96,7 @@ QtObject { } function onSetSignerFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) { - console.warn("TODO: Backend") - //root.setSignerFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) + root.setSignerFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) } function onDeploymentStateChanged(communityId, status, url) { @@ -120,18 +124,21 @@ QtObject { } function onOwnerTokenReceived(communityId, communityName, chainId, communityAddress) { - console.warn("TODO: Backend") -// Global.displayToastMessage(qsTr("You received the Owner token for %1. To finalize ownership, make your device the control node.").arg(communityName), -// qsTr("Finalise ownership"), -// "", -// false, -// Constants.ephemeralNotificationType.normal, -// "") + // TODO clicking url should execute finalise flow + Global.displayToastMessage(qsTr("You received the Owner token for %1. To finalize ownership, make your device the control node.").arg(communityName), + qsTr("Finalise ownership"), + "", + false, + Constants.ephemeralNotificationType.normal, + "") } function onSetSignerStateChanged(communityId, communityName, status, url) { - console.warn("TODO: Backend") - //root.setSignerStateChanged(communityId, communityName, status, url) + root.setSignerStateChanged(communityId, communityName, status, url) + } + + function onOwnershipLost(communityId, communityName) { + root.ownershipLost(communityId, communityName) } } @@ -151,9 +158,8 @@ QtObject { communityTokensModuleInst.computeDeployFee(communityId, chainId, accountAddress, tokenType, isOwnerDeployment, requestId) } - function computeSetSignerFee(tokenKey, accountAddress, requestId) { - console.warn("TODO: Backend!") - //communityTokensModuleInst.computeSetSignerFee(tokenKey, accountAddress, requestId) + function computeSetSignerFee(chainId, contractAddress, accountAddress, requestId) { + communityTokensModuleInst.computeSetSignerFee(chainId, contractAddress, accountAddress, requestId) } /** @@ -190,4 +196,8 @@ QtObject { function airdrop(communityId, airdropTokens, addresses, feeAccountAddress) { communityTokensModuleInst.airdropTokens(communityId, JSON.stringify(airdropTokens), JSON.stringify(addresses), feeAccountAddress) } + + function asyncGetOwnerTokenDetails(communityId) { + communityTokensModuleInst.asyncGetOwnerTokenDetails(communityId) + } } diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 2a3afb8418..2675f1e4a2 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -53,7 +53,7 @@ QtObject { var accounts, var sendModalPopup) signal openFinaliseOwnershipPopup(string communityId) - signal openDeclineOwnershipPopup(string communityName) + signal openDeclineOwnershipPopup(string communityId, string communityName) signal openLink(string link) signal openLinkWithConfirmation(string link, string domain)