diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 8d8838d401..7383cfb1c3 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -432,6 +432,10 @@ proc init*(self: Controller) = self.events.on(SIGNAL_PROFILE_MIGRATION_NEEDED_UPDATED) do(e: Args): self.delegate.checkAndPerformProfileMigrationIfNeeded() + self.events.on(SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED) do(e: Args): + let args = CommunityTokensDetailsArgs(e) + self.delegate.onCommunityTokensDetailsLoaded(args.communityId, args.communityTokens, args.communityTokenJsonItems) + proc isConnected*(self: Controller): bool = return self.nodeService.isConnected() @@ -494,8 +498,8 @@ proc getStatusForContactWithId*(self: Controller, publicKey: string): StatusUpda proc getVerificationRequestFrom*(self: Controller, publicKey: string): VerificationRequest = self.contactsService.getVerificationRequestFrom(publicKey) -proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] = - self.communityTokensService.getCommunityTokens(communityId) +proc getCommunityTokensDetailsAsync*(self: Controller, communityId: string) = + self.communityTokensService.getCommunityTokensDetailsAsync(communityId) proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] = return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress) diff --git a/src/app/modules/main/io_interface.nim b/src/app/modules/main/io_interface.nim index bdc59c8978..196cca0071 100644 --- a/src/app/modules/main/io_interface.nim +++ b/src/app/modules/main/io_interface.nim @@ -1,4 +1,4 @@ -import NimQml, stint +import NimQml, stint, json import app_service/service/settings/service as settings_service import app_service/service/node_configuration/service as node_configuration_service @@ -370,6 +370,10 @@ method insertMockedKeycardAction*(self: AccessInterface, cardIndex: int) {.base. method removeMockedKeycardAction*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method onCommunityTokensDetailsLoaded*(self: AccessInterface, communityId: string, + communityTokens: seq[CommunityTokenDto], communityTokenJsonItems: JsonNode) {.base.} = + raise newException(ValueError, "No implementation available") + # This way (using concepts) is used only for the modules managed by AppController type DelegateInterface* = concept c diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 0cbacf3856..26f200ed46 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -255,16 +255,54 @@ proc createTokenItem[T](self: Module[T], tokenDto: CommunityTokenDto) : TokenIte let destructedAmount = self.controller.getRemoteDestructedAmount(tokenDto.chainId, tokenDto.address) result = initTokenItem(tokenDto, network, tokenOwners, ownerAddressName, burnState, remoteDestructedAddresses, remainingSupply, destructedAmount) +proc createTokenItemImproved[T](self: Module[T], tokenDto: CommunityTokenDto, communityTokenJsonItems: JsonNode) : TokenItem = + # These 3 values come from local caches so they can be done sync + let network = self.controller.getNetwork(tokenDto.chainId) + let tokenOwners = self.controller.getCommunityTokenOwners(tokenDto.communityId, tokenDto.chainId, tokenDto.address) + let ownerAddressName = if len(tokenDto.deployer) > 0: self.controller.getCommunityTokenOwnerName(tokenDto.deployer) else: "" + + var tokenDetails: JsonNode + + for details in communityTokenJsonItems.items: + if details["address"].getStr == tokenDto.address: + tokenDetails = details + break + + if tokenDetails.kind == JNull: + error "Token details not found for token", name = tokenDto.name, address = tokenDto.address + return + + let remainingSupply = tokenDetails["remainingSupply"].getStr + let burnState = tokenDetails["burnState"].getInt + let remoteDestructedAddresses = map(tokenDetails["remoteDestructedAddresses"].getElems(), + proc(remoteDestructAddress: JsonNode): string = remoteDestructAddress.getStr) + let destructedAmount = tokenDetails["destructedAmount"].getStr + + result = initTokenItem( + tokenDto, + network, + tokenOwners, + ownerAddressName, + ContractTransactionStatus(burnState), + remoteDestructedAddresses, + stint.parse(remainingSupply, Uint256), + stint.parse(destructedAmount, Uint256), + ) + +method onCommunityTokensDetailsLoaded[T](self: Module[T], communityId: string, + communityTokens: seq[CommunityTokenDto], communityTokenJsonItems: JsonNode) = + let communityTokensItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): TokenItem = + result = self.createTokenItemImproved(tokenDto, communityTokenJsonItems) + ) + self.view.model().setTokenItems(communityId, communityTokensItems) + proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): SectionItem = let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community var communityDetails: CommunityDto var communityTokensItems: seq[TokenItem] if (isCommunity): communityDetails = self.controller.getCommunityById(channelGroup.id) - let communityTokens = self.controller.getCommunityTokens(channelGroup.id) - communityTokensItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): TokenItem = - result = self.createTokenItem(tokenDto) - ) + self.controller.getCommunityTokensDetailsAsync(channelGroup.id) # Get community members' revealed accounts # We will update the model later when we finish loading the accounts if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster: diff --git a/src/app/modules/shared_models/section_model.nim b/src/app/modules/shared_models/section_model.nim index c0fbd3bbfe..6cd1770540 100644 --- a/src/app/modules/shared_models/section_model.nim +++ b/src/app/modules/shared_models/section_model.nim @@ -3,7 +3,7 @@ import NimQml, Tables, strutils, strformat import json import section_item, member_model -import ../main/communities/tokens/models/token_item +import ../main/communities/tokens/models/[token_item, token_model] type ModelRole {.pure.} = enum @@ -425,3 +425,10 @@ QtObject: for pubkey, revealedAccounts in communityMembersAirdropAddress.pairs: self.items[index].members.setAirdropAddress(pubkey, revealedAccounts) + + proc setTokenItems*(self: SectionModel, id: string, communityTokensItems: seq[TokenItem]) = + let index = self.getItemIndex(id) + if (index == -1): + return + + self.items[index].communityTokens.setItems(communityTokensItems) diff --git a/src/app_service/service/community_tokens/async_tasks.nim b/src/app_service/service/community_tokens/async_tasks.nim index 1e2752c457..e3133a5972 100644 --- a/src/app_service/service/community_tokens/async_tasks.nim +++ b/src/app_service/service/community_tokens/async_tasks.nim @@ -209,3 +209,108 @@ const fetchCollectibleOwnersTaskArg: Task = proc(argEncoded: string) {.gcsafe, n "error": e.msg } arg.finish(output) + +type + GetCommunityTokensDetailsArg = ref object of QObjectTaskArg + communityId*: string + +const getCommunityTokensDetailsTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[GetCommunityTokensDetailsArg](argEncoded) + + try: + proc getRemainingSupply(chainId: int, contractAddress: string): string = + let response = tokens_backend.remainingSupply(chainId, contractAddress) + return response.result.getStr() + + proc getPendingTransactions(): seq[TransactionDto] = + let response = backend.getPendingTransactions().result + if (response.kind == JArray and response.len > 0): + return response.getElems().map(x => x.toPendingTransactionDto()) + + return @[] + + proc getCommunityTokenBurnState(chainId: int, contractAddress: string): ContractTransactionStatus = + let allPendingTransactions = getPendingTransactions() + + let burnTransactions = allPendingTransactions.filter(x => x.typeValue == $PendingTransactionTypeDto.BurnCommunityToken) + + for transaction in burnTransactions: + try: + let communityToken = toCommunityTokenDto(parseJson(transaction.additionalData)) + if communityToken.chainId == chainId and communityToken.address == contractAddress: + return ContractTransactionStatus.InProgress + except Exception: + discard + return ContractTransactionStatus.Completed + + proc getRemoteDestructedAddresses(chainId: int, contractAddress: string): seq[string] = + let allPendingTransactions = getPendingTransactions() + let remoteDestructTransactions = allPendingTransactions.filter(x => x.typeValue == $PendingTransactionTypeDto.RemoteDestructCollectible) + for transaction in remoteDestructTransactions: + let remoteDestructTransactionDetails = toRemoteDestroyTransactionDetails(parseJson(transaction.additionalData)) + if remoteDestructTransactionDetails.chainId == chainId and remoteDestructTransactionDetails.contractAddress == contractAddress: + return remoteDestructTransactionDetails.addresses + + proc getCommunityToken(communityTokens: seq[CommunityTokenDto], chainId: int, address: string): CommunityTokenDto = + for token in communityTokens: + if token.chainId == chainId and token.address == address: + return token + + proc getRemoteDestructedAmount(communityTokens: seq[CommunityTokenDto],chainId: int, contractAddress: string): string = + let tokenType = getCommunityToken(communityTokens, chainId, contractAddress).tokenType + if tokenType != TokenType.ERC721: + return "0" + let response = tokens_backend.remoteDestructedAmount(chainId, contractAddress) + return response.result.getStr() + + proc createTokenItemJson(communityTokens: seq[CommunityTokenDto], tokenDto: CommunityTokenDto): JsonNode = + try: + let remainingSupply = if tokenDto.infiniteSupply: + "0" + else: + getRemainingSupply(tokenDto.chainId, tokenDto.address) + + let burnState = getCommunityTokenBurnState(tokenDto.chainId, tokenDto.address) + let remoteDestructedAddresses = getRemoteDestructedAddresses(tokenDto.chainId, tokenDto.address) + + let destructedAmount = getRemoteDestructedAmount(communityTokens, tokenDto.chainId, tokenDto.address) + + return %* { + "address": tokenDto.address, + "remainingSupply": remainingSupply, + "burnState": burnState.int, + "remoteDestructedAddresses": %* (remoteDestructedAddresses), + "destructedAmount": destructedAmount, + "error": "", + } + except Exception as e: + return %* { + "error": e.msg, + } + + let response = tokens_backend.getCommunityTokens(arg.communityId) + + if not response.error.isNil: + raise newException(ValueError, "Error getCommunityTokens" & response.error.message) + + let communityTokens = parseCommunityTokens(response) + + let communityTokenJsonItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): JsonNode = + result = createTokenItemJson(communityTokens, tokenDto) + if result["error"].getStr != "": + raise newException(ValueError, "Error creating token item" & result["error"].getStr) + ) + + let output = %* { + "communityTokensResponse": response, + "communityTokenJsonItems": communityTokenJsonItems, + "communityId": arg.communityId, + "error": "" + } + arg.finish(output) + except Exception as e: + let output = %* { + "communityId": arg.communityId, + "error": e.msg + } + arg.finish(output) 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 6ef91a9d68..c00396cbed 100644 --- a/src/app_service/service/community_tokens/dto/community_token.nim +++ b/src/app_service/service/community_tokens/dto/community_token.nim @@ -89,3 +89,82 @@ proc toCommunityTokenDto*(jsonObj: JsonNode): CommunityTokenDto = proc parseCommunityTokens*(response: RpcResponse[JsonNode]): seq[CommunityTokenDto] = result = map(response.result.getElems(), proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto()) + +proc parseCommunityTokens*(response: JsonNode): seq[CommunityTokenDto] = + result = map(response.getElems(), + proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto()) + +type + CommunityTokenAndAmount* = object + communityToken*: CommunityTokenDto + amount*: Uint256 # for assets the value is converted to wei + +type + ContractTuple* = tuple + chainId: int + address: string + +proc `%`*(self: ContractTuple): JsonNode = + result = %* { + "address": self.address, + "chainId": self.chainId + } + +proc toContractTuple*(json: JsonNode): ContractTuple = + return (json["chainId"].getInt, json["address"].getStr) + +type + ChainWalletTuple* = tuple + chainId: int + address: string + +type + WalletAndAmount* = object + walletAddress*: string + amount*: int + +type + RemoteDestroyTransactionDetails* = object + chainId*: int + contractAddress*: string + addresses*: seq[string] + +proc `%`*(self: RemoteDestroyTransactionDetails): JsonNode = + result = %* { + "contractAddress": self.contractAddress, + "chainId": self.chainId, + "addresses": self.addresses + } + +type + OwnerTokenDeploymentTransactionDetails* = object + ownerToken*: ContractTuple + masterToken*: ContractTuple + communityId*: string + +proc `%`*(self: OwnerTokenDeploymentTransactionDetails): JsonNode = + result = %* { + "ownerToken": %self.ownerToken, + "masterToken": %self.masterToken, + "communityId": self.communityId + } + +proc toOwnerTokenDeploymentTransactionDetails*(jsonObj: JsonNode): OwnerTokenDeploymentTransactionDetails = + result = OwnerTokenDeploymentTransactionDetails() + try: + result.ownerToken = (jsonObj["ownerToken"]["chainId"].getInt, jsonObj["ownerToken"]["address"].getStr) + result.masterToken = (jsonObj["masterToken"]["chainId"].getInt, jsonObj["masterToken"]["address"].getStr) + result.communityId = jsonObj["communityId"].getStr + except Exception as e: + error "Error parsing OwnerTokenDeploymentTransactionDetails json", msg=e.msg + +proc toRemoteDestroyTransactionDetails*(json: JsonNode): RemoteDestroyTransactionDetails = + return RemoteDestroyTransactionDetails(chainId: json["chainId"].getInt, contractAddress: json["contractAddress"].getStr, addresses: to(json["addresses"], seq[string])) + +type + ComputeFeeErrorCode* {.pure.} = enum + Success, + Infura, + Balance, + Other + diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim index 3734e236dd..02ff0652c2 100644 --- a/src/app_service/service/community_tokens/service.nim +++ b/src/app_service/service/community_tokens/service.nim @@ -13,6 +13,7 @@ import ../wallet_account/service as wallet_account_service import ../ens/utils as ens_utils import ../eth/dto/transaction from backend/collectibles_types import CollectibleOwner +import ../../../backend/backend import ../../../backend/response_type @@ -33,40 +34,11 @@ export community_token_owner const ethSymbol = "ETH" -type - CommunityTokenAndAmount* = object - communityToken*: CommunityTokenDto - amount*: Uint256 # for assets the value is converted to wei - -type - ContractTuple* = tuple - chainId: int - address: string - -proc `%`*(self: ContractTuple): JsonNode = - result = %* { - "address": self.address, - "chainId": self.chainId - } - -proc toContractTuple*(json: JsonNode): ContractTuple = - return (json["chainId"].getInt, json["address"].getStr) - -type - ChainWalletTuple* = tuple - chainId: int - address: string - include async_tasks logScope: topics = "community-tokens-service" -type - WalletAndAmount* = object - walletAddress*: string - amount*: int - type CommunityTokenDeployedStatusArgs* = ref object of Args communityId*: string @@ -114,50 +86,6 @@ type transactionHash*: string status*: ContractTransactionStatus -type - RemoteDestroyTransactionDetails* = object - chainId*: int - contractAddress*: string - addresses*: seq[string] - -proc `%`*(self: RemoteDestroyTransactionDetails): JsonNode = - result = %* { - "contractAddress": self.contractAddress, - "chainId": self.chainId, - "addresses": self.addresses - } - -type - OwnerTokenDeploymentTransactionDetails* = object - ownerToken*: ContractTuple - masterToken*: ContractTuple - communityId*: string - -proc `%`*(self: OwnerTokenDeploymentTransactionDetails): JsonNode = - result = %* { - "ownerToken": %self.ownerToken, - "masterToken": %self.masterToken, - "communityId": self.communityId - } - -proc toOwnerTokenDeploymentTransactionDetails*(jsonObj: JsonNode): OwnerTokenDeploymentTransactionDetails = - result = OwnerTokenDeploymentTransactionDetails() - try: - result.ownerToken = (jsonObj["ownerToken"]["chainId"].getInt, jsonObj["ownerToken"]["address"].getStr) - result.masterToken = (jsonObj["masterToken"]["chainId"].getInt, jsonObj["masterToken"]["address"].getStr) - result.communityId = jsonObj["communityId"].getStr - except Exception as e: - error "Error parsing OwnerTokenDeploymentTransactionDetails json", msg=e.msg - -proc toRemoteDestroyTransactionDetails*(json: JsonNode): RemoteDestroyTransactionDetails = - return RemoteDestroyTransactionDetails(chainId: json["chainId"].getInt, contractAddress: json["contractAddress"].getStr, addresses: to(json["addresses"], seq[string])) - -type - ComputeFeeErrorCode* {.pure.} = enum - Success, - Infura, - Balance, - Other type ComputeFeeArgs* = ref object of Args @@ -205,6 +133,12 @@ type chainId*: int owners*: seq[CommunityCollectibleOwner] +type + CommunityTokensDetailsArgs* = ref object of Args + communityId*: string + 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" @@ -220,6 +154,7 @@ 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_DEPLOY_OWNER_TOKEN* = "deployOwnerToken" @@ -542,6 +477,34 @@ QtObject: except RpcException: error "Error getting all community tokens", message = getCurrentExceptionMsg() + proc getCommunityTokensDetailsAsync*(self: Service, communityId: string) = + let arg = GetCommunityTokensDetailsArg( + tptr: cast[ByteAddress](getCommunityTokensDetailsTaskArg), + vptr: cast[ByteAddress](self.vptr), + slot: "onCommunityTokensDetailsLoaded", + communityId: communityId + ) + self.threadpool.start(arg) + + proc onCommunityTokensDetailsLoaded*(self:Service, response: string) {.slot.} = + try: + let responseJson = response.parseJson() + + if responseJson["error"].getStr != "": + raise newException(ValueError, responseJson["error"].getStr) + + let communityTokens = parseCommunityTokens(responseJson["communityTokensResponse"]["result"]) + let communityTokenJsonItems = responseJson["communityTokenJsonItems"] + + self.events.emit(SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED, + CommunityTokensDetailsArgs( + communityId: responseJson["communityId"].getStr, + communityTokens: communityTokens, + communityTokenJsonItems: communityTokenJsonItems, + )) + except RpcException as e: + error "Error getting community tokens details", message = e.msg + proc removeCommunityToken*(self: Service, communityId: string, chainId: int, address: string) = try: let response = tokens_backend.removeCommunityToken(chainId, address) @@ -579,8 +542,8 @@ QtObject: proc getRemoteDestructedAddresses*(self: Service, chainId: int, contractAddress: string): seq[string] = try: - let burnTransactions = self.transactionService.getPendingTransactionsForType(PendingTransactionTypeDto.RemoteDestructCollectible) - for transaction in burnTransactions: + let remoteDestructTransactions = self.transactionService.getPendingTransactionsForType(PendingTransactionTypeDto.RemoteDestructCollectible) + for transaction in remoteDestructTransactions: let remoteDestructTransactionDetails = toRemoteDestroyTransactionDetails(parseJson(transaction.additionalData)) if remoteDestructTransactionDetails.chainId == chainId and remoteDestructTransactionDetails.contractAddress == contractAddress: return remoteDestructTransactionDetails.addresses