diff --git a/src/app/modules/main/communities/tokens/controller.nim b/src/app/modules/main/communities/tokens/controller.nim index 32811b23e3..4a03ba62b0 100644 --- a/src/app/modules/main/communities/tokens/controller.nim +++ b/src/app/modules/main/communities/tokens/controller.nim @@ -43,14 +43,20 @@ proc init*(self: Controller) = return self.communityTokensModule.onUserAuthenticated(args.password) self.events.on(SIGNAL_COMPUTE_DEPLOY_FEE) do(e:Args): - let args = ComputeDeployFeeArgs(e) + let args = ComputeFeeArgs(e) self.communityTokensModule.onDeployFeeComputed(args.ethCurrency, args.fiatCurrency, args.errorCode) + self.events.on(SIGNAL_COMPUTE_SELF_DESTRUCT_FEE) do(e:Args): + let args = ComputeFeeArgs(e) + self.communityTokensModule.onSelfDestructFeeComputed(args.ethCurrency, args.fiatCurrency, args.errorCode) self.events.on(SIGNAL_COMMUNITY_TOKEN_DEPLOYED) do(e: Args): let args = CommunityTokenDeployedArgs(e) self.communityTokensModule.onCommunityTokenDeployStateChanged(args.communityToken.communityId, args.communityToken.chainId, args.transactionHash, args.communityToken.deployState) self.events.on(SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS) do(e: Args): let args = CommunityTokenDeployedStatusArgs(e) self.communityTokensModule.onCommunityTokenDeployStateChanged(args.communityId, args.chainId, args.transactionHash, args.deployState) + self.events.on(SIGNAL_REMOTE_DESTRUCT_STATUS) do(e: Args): + let args = RemoteDestructArgs(e) + self.communityTokensModule.onRemoteDestructStateChanged(args.communityToken.communityId, args.communityToken.name, args.communityToken.chainId, args.transactionHash, args.status) proc deployCollectibles*(self: Controller, communityId: string, addressFrom: string, password: string, deploymentParams: DeploymentParameters, tokenMetadata: CommunityTokensMetadataDto, chainId: int) = self.communityTokensService.deployCollectibles(communityId, addressFrom, password, deploymentParams, tokenMetadata, chainId) @@ -58,6 +64,9 @@ proc deployCollectibles*(self: Controller, communityId: string, addressFrom: str proc airdropCollectibles*(self: Controller, communityId: string, password: string, collectiblesAndAmounts: seq[CommunityTokenAndAmount], walletAddresses: seq[string]) = self.communityTokensService.airdropCollectibles(communityId, password, collectiblesAndAmounts, walletAddresses) +proc selfDestructCollectibles*(self: Controller, communityId: string, password: string, walletAndAmounts: seq[WalletAndAmount], contractUniqueKey: string) = + self.communityTokensService.selfDestructCollectibles(communityId, password, walletAndAmounts, contractUniqueKey) + 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) @@ -68,6 +77,9 @@ proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTo proc computeDeployFee*(self: Controller, chainId: int, accountAddress: string) = self.communityTokensService.computeDeployFee(chainId, accountAddress) +proc computeSelfDestructFee*(self: Controller, walletAndAmountList: seq[WalletAndAmount], contractUniqueKey: string) = + self.communityTokensService.computeSelfDestructFee(walletAndAmountList, contractUniqueKey) + proc getCommunityTokenBySymbol*(self: Controller, communityId: string, symbol: string): CommunityTokenDto = return self.communityTokensService.getCommunityTokenBySymbol(communityId, symbol) diff --git a/src/app/modules/main/communities/tokens/io_interface.nim b/src/app/modules/main/communities/tokens/io_interface.nim index 37ddfbbc0a..10c3b10e53 100644 --- a/src/app/modules/main/communities/tokens/io_interface.nim +++ b/src/app/modules/main/communities/tokens/io_interface.nim @@ -13,6 +13,9 @@ method load*(self: AccessInterface) {.base.} = method airdropCollectibles*(self: AccessInterface, communityId: string, collectiblesJsonString: string, walletsJsonString: string) {.base.} = raise newException(ValueError, "No implementation available") +method selfDestructCollectibles*(self: AccessInterface, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} = + raise newException(ValueError, "No implementation available") + method deployCollectible*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: int, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, image: string) {.base.} = raise newException(ValueError, "No implementation available") @@ -26,8 +29,17 @@ method resetTempValues*(self: AccessInterface) {.base.} = method computeDeployFee*(self: AccessInterface, chainId: int, accountAddress: string) {.base.} = raise newException(ValueError, "No implementation available") +method computeSelfDestructFee*(self: AccessInterface, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} = + raise newException(ValueError, "No implementation available") + method onDeployFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.base.} = raise newException(ValueError, "No implementation available") +method onSelfDestructFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.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") + +method onRemoteDestructStateChanged*(self: AccessInterface, communityId: string, tokenName: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/models/token_item.nim b/src/app/modules/main/communities/tokens/models/token_item.nim index 093c7499fa..a2eaca7e57 100644 --- a/src/app/modules/main/communities/tokens/models/token_item.nim +++ b/src/app/modules/main/communities/tokens/models/token_item.nim @@ -13,17 +13,20 @@ type tokenDto*: CommunityTokenDto chainName*: string chainIcon*: string + accountName*: string tokenOwnersModel*: token_owners_model.TokenOwnersModel proc initTokenItem*( tokenDto: CommunityTokenDto, network: NetworkDto, - tokenOwners: seq[CollectibleOwner] + tokenOwners: seq[CollectibleOwner], + accountName: string ): TokenItem = result.tokenDto = tokenDto if network != nil: result.chainName = network.chainName result.chainIcon = network.iconURL + result.accountName = accountName result.tokenOwnersModel = newTokenOwnersModel() result.tokenOwnersModel.setItems(tokenOwners.map(proc(owner: CollectibleOwner): TokenOwnersItem = # TODO find member with the address - later when airdrop to member will be added 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 3ab9a0c420..6ae3031252 100644 --- a/src/app/modules/main/communities/tokens/models/token_model.nim +++ b/src/app/modules/main/communities/tokens/models/token_model.nim @@ -4,10 +4,12 @@ import token_owners_item import token_owners_model import ../../../../../../app_service/service/community_tokens/dto/community_token import ../../../../../../app_service/service/collectible/dto +import ../../../../../../app_service/common/utils type ModelRole {.pure.} = enum - TokenType = UserRole + 1 + ContractUniqueKey = UserRole + 1 + TokenType TokenAddress Name Symbol @@ -22,6 +24,7 @@ type ChainName ChainIcon TokenOwnersModel + AccountName QtObject: type TokenModel* = ref object of QAbstractListModel @@ -43,6 +46,7 @@ QtObject: if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): self.items[i].tokenDto.deployState = deployState let index = self.createIndex(i, 0, nil) + defer: index.delete self.dataChanged(index, index, @[ModelRole.DeployState.int]) return @@ -87,6 +91,7 @@ QtObject: method roleNames(self: TokenModel): Table[int, string] = { + ModelRole.ContractUniqueKey.int:"contractUniqueKey", ModelRole.TokenType.int:"tokenType", ModelRole.TokenAddress.int:"tokenAddress", ModelRole.Name.int:"name", @@ -102,6 +107,7 @@ QtObject: ModelRole.ChainName.int:"chainName", ModelRole.ChainIcon.int:"chainIcon", ModelRole.TokenOwnersModel.int:"tokenOwnersModel", + ModelRole.AccountName.int:"accountName", }.toTable method data(self: TokenModel, index: QModelIndex, role: int): QVariant = @@ -112,6 +118,8 @@ QtObject: let item = self.items[index.row] let enumRole = role.ModelRole case enumRole: + of ModelRole.ContractUniqueKey: + result = newQVariant(contractUniqueKey(item.tokenDto.chainId, item.tokenDto.address)) of ModelRole.TokenType: result = newQVariant(item.tokenDto.tokenType.int) of ModelRole.TokenAddress: @@ -142,6 +150,8 @@ QtObject: result = newQVariant(item.chainIcon) of ModelRole.TokenOwnersModel: result = newQVariant(item.tokenOwnersModel) + of ModelRole.AccountName: + result = newQVariant(item.accountName) proc `$`*(self: TokenModel): string = for i in 0 ..< self.items.len: diff --git a/src/app/modules/main/communities/tokens/module.nim b/src/app/modules/main/communities/tokens/module.nim index 0a962c0494..285298b615 100644 --- a/src/app/modules/main/communities/tokens/module.nim +++ b/src/app/modules/main/communities/tokens/module.nim @@ -18,6 +18,7 @@ type Unknown = 0 Deploy = 1 Airdrop = 2 + SelfDestruct = 3 type Module* = ref object of io_interface.AccessInterface @@ -26,6 +27,7 @@ type view: View viewVariant: QVariant tempTokenAndAmountList: seq[CommunityTokenAndAmount] + tempWalletAndAmountList: seq[WalletAndAmount] tempAddressFrom: string tempCommunityId: string tempChainId: int @@ -34,6 +36,7 @@ type tempTokenMetadata: CommunityTokensMetadataDto tempWalletAddresses: seq[string] tempContractAction: ContractAction + tempContractUniqueKey: string proc newCommunityTokensModule*( parent: parent_interface.AccessInterface, @@ -62,6 +65,8 @@ method resetTempValues(self:Module) = self.tempWalletAddresses = @[] self.tempContractAction = ContractAction.Unknown self.tempTokenAndAmountList = @[] + self.tempWalletAndAmountList = @[] + self.tempContractUniqueKey = "" method load*(self: Module) = singletonInstance.engine.setRootContextProperty("communityTokensModule", self.viewVariant) @@ -92,6 +97,20 @@ method airdropCollectibles*(self: Module, communityId: string, collectiblesJsonS self.tempContractAction = ContractAction.Airdrop self.authenticate() +proc getWalletAndAmountListFromJson(self: Module, collectiblesToBurnJsonString: string): seq[WalletAndAmount] = + let collectiblesToBurnJson = collectiblesToBurnJsonString.parseJson + for collectibleToBurn in collectiblesToBurnJson: + let walletAddress = collectibleToBurn["walletAddress"].getStr + let amount = collectibleToBurn["amount"].getInt + result.add(WalletAndAmount(walletAddress: walletAddress, amount: amount)) + +method selfDestructCollectibles*(self: Module, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) = + self.tempWalletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString) + self.tempCommunityId = communityId + self.tempContractUniqueKey = contractUniqueKey + self.tempContractAction = ContractAction.SelfDestruct + self.authenticate() + method deployCollectible*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: int, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, image: string) = self.tempAddressFrom = fromAddress @@ -119,14 +138,28 @@ method onUserAuthenticated*(self: Module, password: string) = self.controller.deployCollectibles(self.tempCommunityId, self.tempAddressFrom, password, self.tempDeploymentParams, self.tempTokenMetadata, self.tempChainId) elif self.tempContractAction == ContractAction.Airdrop: self.controller.airdropCollectibles(self.tempCommunityId, password, self.tempTokenAndAmountList, self.tempWalletAddresses) + elif self.tempContractAction == ContractAction.SelfDestruct: + self.controller.selfDestructCollectibles(self.tempCommunityId, password, self.tempWalletAndAmountList, self.tempContractUniqueKey) method onDeployFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) = self.view.updateDeployFee(ethCurrency, fiatCurrency, errorCode.int) +method onSelfDestructFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) = + self.view.updateSelfDestructFee(ethCurrency, fiatCurrency, errorCode.int) + method computeDeployFee*(self: Module, chainId: int, accountAddress: string) = self.controller.computeDeployFee(chainId, accountAddress) +method computeSelfDestructFee*(self: Module, collectiblesToBurnJsonString: string, contractUniqueKey: string) = + let walletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString) + self.controller.computeSelfDestructFee(walletAndAmountList, contractUniqueKey) + method onCommunityTokenDeployStateChanged*(self: Module, communityId: string, chainId: int, transactionHash: string, deployState: DeployState) = let network = self.controller.getNetwork(chainId) let url = if network != nil: network.blockExplorerURL & "/tx/" & transactionHash else: "" - self.view.emitDeploymentStateChanged(communityId, deployState.int, url) \ No newline at end of file + self.view.emitDeploymentStateChanged(communityId, deployState.int, url) + +method onRemoteDestructStateChanged*(self: Module, communityId: string, tokenName: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) = + let network = self.controller.getNetwork(chainId) + let url = if network != nil: network.blockExplorerURL & "/tx/" & transactionHash else: "" + self.view.emitRemoteDestructStateChanged(communityId, tokenName, status.int, url) \ 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 dabc3ac649..6217c841a7 100644 --- a/src/app/modules/main/communities/tokens/view.nim +++ b/src/app/modules/main/communities/tokens/view.nim @@ -25,14 +25,28 @@ QtObject: proc airdropCollectibles*(self: View, communityId: string, collectiblesJsonString: string, walletsJsonString: string) {.slot.} = self.communityTokensModule.airdropCollectibles(communityId, collectiblesJsonString, walletsJsonString) + proc selfDestructCollectibles*(self: View, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} = + self.communityTokensModule.selfDestructCollectibles(communityId, collectiblesToBurnJsonString, contractUniqueKey) + proc deployFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.} + proc selfDestructFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.} proc computeDeployFee*(self: View, chainId: int, accountAddress: string) {.slot.} = self.communityTokensModule.computeDeployFee(chainId, accountAddress) + proc computeSelfDestructFee*(self: View, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} = + self.communityTokensModule.computeSelfDestructFee(collectiblesToBurnJsonString, contractUniqueKey) + proc updateDeployFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) = self.deployFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode) + proc updateSelfDestructFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) = + self.selfDestructFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode) + proc deploymentStateChanged*(self: View, communityId: string, status: int, url: string) {.signal.} proc emitDeploymentStateChanged*(self: View, communityId: string, status: int, url: string) = - self.deploymentStateChanged(communityId, status, url) \ No newline at end of file + self.deploymentStateChanged(communityId, status, url) + + proc remoteDestructStateChanged*(self: View, communityId: string, tokenName: string, status: int, url: string) {.signal.} + proc emitRemoteDestructStateChanged*(self: View, communityId: string, tokenName: string, status: int, url: string) = + self.remoteDestructStateChanged(communityId, tokenName, status, url) \ No newline at end of file diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 2df3fe607d..d85ddd4c37 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -474,5 +474,8 @@ proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTo proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] = return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress) +proc getCommunityTokenOwnerName*(self: Controller, chainId: int, contractAddress: string): string = + return self.communityTokensService.contractOwnerName(chainId, contractAddress) + proc getNetwork*(self:Controller, chainId: int): NetworkDto = self.networksService.getNetwork(chainId) \ No newline at end of file diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index de260fedb6..5d84a405ef 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -236,7 +236,8 @@ method delete*[T](self: Module[T]) = proc createTokenItem[T](self: Module[T], tokenDto: CommunityTokenDto) : TokenItem = let network = self.controller.getNetwork(tokenDto.chainId) let tokenOwners = self.controller.getCommunityTokenOwners(tokenDto.communityId, tokenDto.chainId, tokenDto.address) - result = initTokenItem(tokenDto, network, tokenOwners) + let ownerAddressName = self.controller.getCommunityTokenOwnerName(tokenDto.chainId, tokenDto.address) + result = initTokenItem(tokenDto, network, tokenOwners, ownerAddressName) proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): SectionItem = let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community diff --git a/src/app_service/common/utils.nim b/src/app_service/common/utils.nim index 3e6e72a172..fd7c3755ba 100644 --- a/src/app_service/common/utils.nim +++ b/src/app_service/common/utils.nim @@ -80,4 +80,7 @@ proc isPathOutOfTheDefaultStatusDerivationTree*(path: string): bool = path.count("'") != 3 or path.count("/") != 5: return true - return false \ No newline at end of file + return false + +proc contractUniqueKey*(chainId: int, contractAddress: string): string = + return $chainId & "_" & contractAddress \ No newline at end of file diff --git a/src/app_service/service/community_tokens/async_tasks.nim b/src/app_service/service/community_tokens/async_tasks.nim index 3775d2834c..1b15cff94e 100644 --- a/src/app_service/service/community_tokens/async_tasks.nim +++ b/src/app_service/service/community_tokens/async_tasks.nim @@ -1,5 +1,7 @@ +import stint include ../../common/json_utils import ../../../backend/eth +import ../../../backend/community_tokens import ../../../backend/collectibles import ../../../app/core/tasks/common import ../../../app/core/tasks/qt @@ -22,6 +24,26 @@ const asyncGetSuggestedFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimca "error": e.msg, }) +type + AsyncGetBurnFees = ref object of QObjectTaskArg + chainId: int + contractAddress: string + tokenIds: seq[UInt256] + +const asyncGetBurnFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncGetBurnFees](argEncoded) + try: + let feesResponse = eth.suggestedFees(arg.chainId).result + let burnGas = community_tokens.estimateRemoteBurn(arg.chainId, arg.contractAddress, arg.tokenIds).result.getInt + arg.finish(%* { + "fees": feesResponse.toSuggestedFeesDto(), + "burnGas": burnGas, + "error": "" }) + except Exception as e: + arg.finish(%* { + "error": e.msg, + }) + type FetchCollectibleOwnersArg = ref object of QObjectTaskArg chainId*: int diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim index 052811b83e..a9bd07b4b6 100644 --- a/src/app_service/service/community_tokens/service.nim +++ b/src/app_service/service/community_tokens/service.nim @@ -1,4 +1,4 @@ -import NimQml, Tables, chronicles, json, stint, strutils +import NimQml, Tables, chronicles, json, stint, strutils, sugar, sequtils import ../../../app/core/eventemitter import ../../../app/core/tasks/[qt, threadpool] import ../../../app/modules/shared_models/currency_amount @@ -15,6 +15,8 @@ import ../collectible/dto as collectibles_dto import ../../../backend/response_type import ../../common/conversion +import ../../common/account_constants +import ../../common/utils as common_utils import ../community/dto/community import ./dto/deployment_parameters @@ -37,6 +39,11 @@ type communityToken*: CommunityTokenDto amount*: int +type + WalletAndAmount* = object + walletAddress*: string + amount*: int + type CommunityTokenDeployedStatusArgs* = ref object of Args communityId*: string @@ -50,6 +57,18 @@ type communityToken*: CommunityTokenDto transactionHash*: string +type + ContractTransactionStatus* {.pure.} = enum + Failed, + InProgress, + Completed + +type + RemoteDestructArgs* = ref object of Args + communityToken*: CommunityTokenDto + transactionHash*: string + status*: ContractTransactionStatus + type ComputeFeeErrorCode* {.pure.} = enum Success, @@ -58,7 +77,7 @@ type Other type - ComputeDeployFeeArgs* = ref object of Args + ComputeFeeArgs* = ref object of Args ethCurrency*: CurrencyAmount fiatCurrency*: CurrencyAmount errorCode*: ComputeFeeErrorCode @@ -79,7 +98,9 @@ type const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus" const SIGNAL_COMMUNITY_TOKEN_DEPLOYED* = "communityTokenDeployed" const SIGNAL_COMPUTE_DEPLOY_FEE* = "computeDeployFee" +const SIGNAL_COMPUTE_SELF_DESTRUCT_FEE* = "computeSelfDestructFee" const SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED* = "communityTokenOwnersFetched" +const SIGNAL_REMOTE_DESTRUCT_STATUS* = "communityTokenRemoteDestructStatus" QtObject: type @@ -92,15 +113,20 @@ QtObject: walletAccountService: wallet_account_service.Service tempAccountAddress: string tempChainId: int - addressAndTxMap: Table[ContractTuple, string] tokenOwnersTimer: QTimer + tokenOwners1SecTimer: QTimer # used to update 1 sec after changes in owners + tempTokenOwnersToFetch: CommunityTokenDto # used by 1sec timer tokenOwnersCache: Table[ContractTuple, seq[CollectibleOwner]] + tempSuggestedFees: SuggestedFeesDto + tempGasUnits: int # Forward declaration proc fetchAllTokenOwners*(self: Service) + proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] proc delete*(self: Service) = delete(self.tokenOwnersTimer) + delete(self.tokenOwners1SecTimer) self.QObject.delete proc newService*( @@ -119,10 +145,13 @@ QtObject: result.tokenService = tokenService result.settingsService = settingsService result.walletAccountService = walletAccountService - result.addressAndTxMap = initTable[ContractTuple, string]() result.tokenOwnersTimer = newQTimer() result.tokenOwnersTimer.setInterval(10*60*1000) signalConnect(result.tokenOwnersTimer, "timeout()", result, "onRefreshTransferableTokenOwners()", 2) + result.tokenOwners1SecTimer = newQTimer() + result.tokenOwners1SecTimer.setInterval(1000) + result.tokenOwners1SecTimer.setSingleShot(true) + signalConnect(result.tokenOwners1SecTimer, "timeout()", result, "onFetchTempTokenOwners()", 2) proc init*(self: Service) = self.fetchAllTokenOwners() @@ -139,21 +168,27 @@ QtObject: error "Error updating collectibles contract state", message = getCurrentExceptionMsg() let data = CommunityTokenDeployedStatusArgs(communityId: tokenDto.communityId, contractAddress: tokenDto.address, deployState: deployState, chainId: tokenDto.chainId, - transactionHash: self.addressAndTxMap.getOrDefault((tokenDto.address,tokenDto.chainId))) + transactionHash: receivedData.transactionHash) self.events.emit(SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS, data) self.events.on(PendingTransactionTypeDto.CollectibleAirdrop.event) do(e: Args): - var receivedData = TransactionMinedArgs(e) + let receivedData = TransactionMinedArgs(e) let airdropDetails = toAirdropDetails(parseJson(receivedData.data)) if not receivedData.success: error "Collectible airdrop failed", contractAddress=airdropDetails.contractAddress return - try: - # add holders to db - discard addTokenOwners(airdropDetails.chainId, airdropDetails.contractAddress, - airdropDetails.walletAddresses, airdropDetails.amount) - except RpcException: - error "Error adding collectible token owners", message = getCurrentExceptionMsg() + #TODO signalize about airdrops - add when extending airdrops + self.events.on(PendingTransactionTypeDto.CollectibleRemoteSelfDestruct.event) do(e: Args): + let receivedData = TransactionMinedArgs(e) + let tokenDto = toCommunityTokenDto(parseJson(receivedData.data)) + let transactionStatus = if receivedData.success: ContractTransactionStatus.Completed else: ContractTransactionStatus.Failed + let data = RemoteDestructArgs(communityToken: tokenDto, transactionHash: receivedData.transactionHash, status: transactionStatus) + self.events.emit(SIGNAL_REMOTE_DESTRUCT_STATUS, data) + + # update owners list if burn was successfull + if receivedData.success: + self.tempTokenOwnersToFetch = tokenDto + self.tokenOwners1SecTimer.start() proc deployCollectiblesEstimate*(self: Service): int = try: @@ -181,8 +216,6 @@ QtObject: debug "Deployed contract address ", contractAddress=contractAddress debug "Deployment transaction hash ", transactionHash=transactionHash - self.addressAndTxMap[(contractAddress, chainId)] = transactionHash - var communityToken: CommunityTokenDto communityToken.tokenType = TokenType.ERC721 communityToken.communityId = communityId @@ -245,6 +278,13 @@ QtObject: except RpcException: error "Error getting contract owner", message = getCurrentExceptionMsg() + proc contractOwnerName*(self: Service, chainId: int, contractAddress: string): string = + try: + let response = tokens_backend.contractOwner(chainId, contractAddress) + return self.walletAccountService.getAccountByAddress(response.result.getStr().toLower()).name + except RpcException: + error "Error getting contract owner name", message = getCurrentExceptionMsg() + proc airdropCollectibles*(self: Service, communityId: string, password: string, collectiblesAndAmounts: seq[CommunityTokenAndAmount], walletAddresses: seq[string]) = try: for collectibleAndAmount in collectiblesAndAmounts: @@ -286,52 +326,147 @@ QtObject: let arg = AsyncGetSuggestedFees( tptr: cast[ByteAddress](asyncGetSuggestedFeesTask), vptr: cast[ByteAddress](self.vptr), - slot: "onSuggestedFees", + slot: "onDeployFees", chainId: chainId, ) self.threadpool.start(arg) except Exception as e: error "Error loading fees", msg = e.msg - proc onSuggestedFees*(self:Service, response: string) {.slot.} = - let responseJson = response.parseJson() - const ethSymbol = "ETH" + 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 - if responseJson{"error"}.kind != JNull and responseJson{"error"}.getStr != "": - let errorMessage = responseJson["error"].getStr - var errorCode = ComputeFeeErrorCode.Other - if errorMessage.contains("403 Forbidden") or errorMessage.contains("exceed"): - errorCode = ComputeFeeErrorCode.Infura - let ethCurrency = newCurrencyAmount(0.0, ethSymbol, 1, false) - let fiatCurrency = newCurrencyAmount(0.0, self.settingsService.getCurrency(), 1, false) - let data = ComputeDeployFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode) - self.events.emit(SIGNAL_COMPUTE_DEPLOY_FEE, data) + proc getOwnerBalances(self: Service, contractOwners: seq[CollectibleOwner], ownerAddress: string): seq[CollectibleBalance] = + for owner in contractOwners: + if owner.address == ownerAddress: + return owner.balances + + proc collectTokensToBurn(self: Service, walletAndAmountList: seq[WalletAndAmount], contractOwners: seq[CollectibleOwner]): seq[UInt256] = + if len(walletAndAmountList) == 0 or len(contractOwners) == 0: + return + for walletAndAmount in walletAndAmountList: + let ownerBalances = self.getOwnerBalances(contractOwners, walletAndAmount.walletAddress) + let amount = walletAndAmount.amount + if amount > len(ownerBalances): + error "amount to burn is higher than the number of tokens", amount=amount, balance=len(ownerBalances), owner=walletAndAmount.walletAddress return + for i in 0..amount-1: # add the amount of tokens + result.add(ownerBalances[i].tokenId) - let suggestedFees = decodeSuggestedFeesDto(responseJson["fees"]) - let contractGasUnits = self.deployCollectiblesEstimate() + proc getTokensToBurn(self: Service, walletAndAmountList: seq[WalletAndAmount], contract: CommunityTokenDto): seq[Uint256] = + if contract.address == "": + error "Can't find contract" + return + let tokenOwners = self.getCommunityTokenOwners(contract.communityId, contract.chainId, contract.address) + let tokenIds = self.collectTokensToBurn(walletAndAmountList, tokenOwners) + if len(tokenIds) == 0: + error "Can't find token ids to burn" + return tokenIds + + # TODO use temp fees for deployment also + proc buildTransactionFromTempFees(self: Service, addressFrom: string): TransactionDataDto = + return ens_utils.buildTransaction(parseAddress(addressFrom), 0.u256, $self.tempGasUnits, + if self.tempSuggestedFees.eip1559Enabled: "" else: $self.tempSuggestedFees.gasPrice, self.tempSuggestedFees.eip1559Enabled, + if self.tempSuggestedFees.eip1559Enabled: $self.tempSuggestedFees.maxPriorityFeePerGas else: "", + if self.tempSuggestedFees.eip1559Enabled: $self.tempSuggestedFees.maxFeePerGasM else: "") + + proc selfDestructCollectibles*(self: Service, communityId: string, password: string, walletAndAmounts: seq[WalletAndAmount], contractUniqueKey: string) = + try: + let contract = self.findContractByUniqueId(contractUniqueKey) + let tokenIds = self.getTokensToBurn(walletAndAmounts, contract) + if len(tokenIds) == 0: + return + let addressFrom = self.contractOwner(contract.chainId, contract.address) + let txData = self.buildTransactionFromTempFees(addressFrom) + debug "Remote destruct collectibles ", chainId=contract.chainId, address=contract.address, tokens=tokenIds + let response = tokens_backend.remoteBurn(contract.chainId, contract.address, %txData, password, tokenIds) + let transactionHash = response.result.getStr() + debug "Remote destruct transaction hash ", transactionHash=transactionHash + + var data = RemoteDestructArgs(communityToken: contract, transactionHash: transactionHash, status: ContractTransactionStatus.InProgress) + self.events.emit(SIGNAL_REMOTE_DESTRUCT_STATUS, data) + + # observe transaction state + self.transactionService.watchTransaction( + transactionHash, + addressFrom, + contract.address, + $PendingTransactionTypeDto.CollectibleRemoteSelfDestruct, + $contract.toJsonNode(), + contract.chainId, + ) + except Exception as e: + error "Remote self destruct error", msg = e.msg + + proc computeSelfDestructFee*(self: Service, walletAndAmountList: seq[WalletAndAmount], contractUniqueKey: string) = + try: + let contract = self.findContractByUniqueId(contractUniqueKey) + self.tempAccountAddress = self.contractOwner(contract.chainId, contract.address) + self.tempChainId = contract.chainId + let tokenIds = self.getTokensToBurn(walletAndAmountList, contract) + if len(tokenIds) == 0: + warn "token list is empty" + return + let arg = AsyncGetBurnFees( + tptr: cast[ByteAddress](asyncGetBurnFeesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onSelfDestructFees", + chainId: contract.chainId, + contractAddress: contract.address, + tokenIds: tokenIds + ) + self.threadpool.start(arg) + except Exception as e: + error "Error loading fees", msg = e.msg + + proc createComputeFeeArgs(self:Service, jsonNode: JsonNode, gasUnits: int, chainId: int, walletAddress: string): ComputeFeeArgs = + const ethSymbol = "ETH" + if jsonNode{"error"}.kind != JNull and jsonNode{"error"}.getStr != "": + let errorMessage = jsonNode["error"].getStr + var errorCode = ComputeFeeErrorCode.Other + if errorMessage.contains("403 Forbidden") or errorMessage.contains("exceed"): + errorCode = ComputeFeeErrorCode.Infura + let ethCurrency = newCurrencyAmount(0.0, ethSymbol, 1, false) + let fiatCurrency = newCurrencyAmount(0.0, self.settingsService.getCurrency(), 1, false) + return ComputeFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode) + let suggestedFees = decodeSuggestedFeesDto(jsonNode["fees"]) + # save suggested fees and use during operation, we always compute fees before operation + self.tempSuggestedFees = suggestedFees + self.tempGasUnits = gasUnits let maxFees = suggestedFees.maxFeePerGasM let gasPrice = if suggestedFees.eip1559Enabled: maxFees else: suggestedFees.gasPrice - let weiValue = gwei2Wei(gasPrice) * contractGasUnits.u256 + let weiValue = gwei2Wei(gasPrice) * gasUnits.u256 let ethValueStr = wei2Eth(weiValue) let ethValue = parseFloat(ethValueStr) let fiatValue = self.getFiatValue(ethValue, ethSymbol) - let wallet = self.walletAccountService.getAccountByAddress(self.tempAccountAddress) - + let wallet = self.walletAccountService.getAccountByAddress(walletAddress.toLower()) var balance = 0.0 let tokens = wallet.tokens for token in tokens: if token.symbol == ethSymbol: - balance = token.balancesPerChain[self.tempChainId].balance + balance = token.balancesPerChain[chainId].balance break let ethCurrency = newCurrencyAmount(ethValue, ethSymbol, 4, false) let fiatCurrency = newCurrencyAmount(fiatValue, self.settingsService.getCurrency(), 2, false) - let data = ComputeDeployFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, + return ComputeFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: (if ethValue > balance: ComputeFeeErrorCode.Balance else: ComputeFeeErrorCode.Success)) + + proc onSelfDestructFees*(self:Service, response: string) {.slot.} = + let responseJson = response.parseJson() + let burnGas = if responseJson{"burnGas"}.kind != JNull: responseJson{"burnGas"}.getInt else: 0 + let data = self.createComputeFeeArgs(responseJson, burnGas, self.tempChainId, self.tempAccountAddress) + self.events.emit(SIGNAL_COMPUTE_SELF_DESTRUCT_FEE, data) + + proc onDeployFees*(self:Service, response: string) {.slot.} = + let responseJson = response.parseJson() + let data = self.createComputeFeeArgs(responseJson, self.deployCollectiblesEstimate(), self.tempChainId, self.tempAccountAddress) self.events.emit(SIGNAL_COMPUTE_DEPLOY_FEE, data) proc fetchCommunityOwners*(self: Service, communityId: string, chainId: int, contractAddress: string) = @@ -359,7 +494,9 @@ QtObject: let contractAddress = responseJson["contractAddress"].getStr let communityId = responseJson["communityId"].getStr let resultJson = responseJson["result"] - let owners = collectibles_dto.toCollectibleOwnershipDto(resultJson).owners + var owners = collectibles_dto.toCollectibleOwnershipDto(resultJson).owners + owners = owners.filter(x => x.address != ZERO_ADDRESS) + self.tokenOwnersCache[(contractAddress, chainId)] = owners let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: owners) self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data) @@ -369,6 +506,9 @@ QtObject: if token.transferable: self.fetchCommunityOwners(token.communityId, token.chainId, token.address) + proc onFetchTempTokenOwners*(self: Service) {.slot.} = + self.fetchCommunityOwners(self.tempTokenOwnersToFetch.communityId, self.tempTokenOwnersToFetch.chainId, self.tempTokenOwnersToFetch.address) + proc fetchAllTokenOwners*(self: Service) = let allTokens = self.getAllCommunityTokens() for token in allTokens: diff --git a/src/app_service/service/transaction/dto.nim b/src/app_service/service/transaction/dto.nim index 725bc805de..03c78c87ec 100644 --- a/src/app_service/service/transaction/dto.nim +++ b/src/app_service/service/transaction/dto.nim @@ -18,6 +18,7 @@ type WalletTransfer = "WalletTransfer" CollectibleDeployment = "CollectibleDeployment" CollectibleAirdrop = "CollectibleAirdrop" + CollectibleRemoteSelfDestruct = "CollectibleRemoteSelfDestruct" proc event*(self:PendingTransactionTypeDto):string = result = "transaction:" & $self diff --git a/src/backend/community_tokens.nim b/src/backend/community_tokens.nim index c78ccc646b..e8e9dfd9e7 100644 --- a/src/backend/community_tokens.nim +++ b/src/backend/community_tokens.nim @@ -1,4 +1,6 @@ -import json +import json, stint +import std/sequtils +import std/sugar import ./eth import ../app_service/common/utils import ./core, ./response_type @@ -28,6 +30,14 @@ proc mintTo*(chainId: int, contractAddress: string, txData: JsonNode, password: let payload = %* [chainId, contractAddress, txData, utils.hashPassword(password), walletAddresses, amount] return core.callPrivateRPC("collectibles_mintTo", payload) +proc remoteBurn*(chainId: int, contractAddress: string, txData: JsonNode, password: string, tokenIds: seq[UInt256]): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [chainId, contractAddress, txData, utils.hashPassword(password), tokenIds.map(x => x.toString(10))] + return core.callPrivateRPC("collectibles_remoteBurn", payload) + +proc estimateRemoteBurn*(chainId: int, contractAddress: string, tokenIds: seq[UInt256]): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [chainId, contractAddress, tokenIds.map(x => x.toString(10))] + return core.callPrivateRPC("collectibles_estimateRemoteBurn", payload) + proc contractOwner*(chainId: int, contractAddress: string): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [chainId, contractAddress] return core.callPrivateRPC("collectibles_contractOwner", payload) diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityMintTokensSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityMintTokensSettingsPanel.qml index 8cd83dfd02..9a1bfbce71 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/CommunityMintTokensSettingsPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityMintTokensSettingsPanel.qml @@ -67,12 +67,11 @@ SettingsPageLayout { signal signMintTransactionOpened(int chainId, string accountAddress) - signal signSelfDestructTransactionOpened(int chainId) + signal signSelfDestructTransactionOpened(var selfDestructTokensList, // [key , amount] + string contractUniqueKey) signal remoteSelfDestructCollectibles(var selfDestructTokensList, // [key , amount] - int chainId, - string accountName, - string accountAddress) + string contractUniqueKey) signal signBurnTransactionOpened(int chainId) @@ -109,6 +108,7 @@ SettingsPageLayout { property string accountName property int chainId property string chainName + property string contractUniqueKey property var tokenOwnersModel property var selfDestructTokensList @@ -436,13 +436,9 @@ SettingsPageLayout { property bool isRemotelyDestructTransaction function signTransaction() { - root.isFeeLoading = true - root.feeText = "" + root.setFeeLoading() if(signTransactionPopup.isRemotelyDestructTransaction) { - root.remoteSelfDestructCollectibles(d.selfDestructTokensList, - d.chainId, - d.accountName, - d.accountAddress) + root.remoteSelfDestructCollectibles(d.selfDestructTokensList, d.contractUniqueKey) } else { root.burnCollectibles("TODO - KEY"/*d.tokenKey*/, d.burnAmount) } @@ -457,9 +453,13 @@ SettingsPageLayout { networkName: d.chainName feeText: root.feeText isFeeLoading: root.isFeeLoading + errorText: root.errorText - onOpened: signTransactionPopup.isRemotelyDestructTransaction ? root.signSelfDestructTransactionOpened(d.chainId) : - root.signBurnTransactionOpened(d.chainId) + onOpened: { + root.setFeeLoading() + signTransactionPopup.isRemotelyDestructTransaction ? root.signSelfDestructTransactionOpened(d.selfDestructTokensList, d.contractUniqueKey) : + root.signBurnTransactionOpened(d.chainId) + } onCancelClicked: close() onSignTransactionClicked: signTransaction() } @@ -490,6 +490,7 @@ SettingsPageLayout { onItemClicked: { d.accountAddress = accountAddress d.chainId = chainId + d.contractUniqueKey = contractUniqueKey d.chainName = chainName d.accountName = accountName //d.tokenKey = key // TODO: Backend key role diff --git a/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml index 6417110cc8..bd48dc615c 100644 --- a/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml +++ b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml @@ -275,6 +275,25 @@ StatusSectionLayout { readonly property CommunityTokensStore communityTokensStore: rootStore.communityTokensStore + function setFeesInfo(ethCurrency, fiatCurrency, errorCode) { + if (errorCode === Constants.ComputeFeeErrorCode.Success || errorCode === Constants.ComputeFeeErrorCode.Balance) { + let valueStr = LocaleUtils.currencyAmountToLocaleString(ethCurrency) + "(" + LocaleUtils.currencyAmountToLocaleString(fiatCurrency) + ")" + mintPanel.feeText = valueStr + if (errorCode === Constants.ComputeFeeErrorCode.Balance) { + mintPanel.errorText = qsTr("Not enough funds to make transaction") + } + mintPanel.isFeeLoading = false + return + } else if (errorCode === Constants.ComputeFeeErrorCode.Infura) { + mintPanel.errorText = qsTr("Infura error") + mintPanel.isFeeLoading = true + return + } + + mintPanel.errorText = qsTr("Unknown error") + mintPanel.isFeeLoading = true + } + communityName: root.community.name tokensModel: root.community.communityTokens layer1Networks: communityTokensStore.layer1Networks @@ -315,12 +334,11 @@ StatusSectionLayout { accountName, artworkCropRect) } - onSignSelfDestructTransactionOpened: communityTokensStore.computeSelfDestructFee(chainId) + onSignSelfDestructTransactionOpened: communityTokensStore.computeSelfDestructFee(selfDestructTokensList, contractUniqueKey) onRemoteSelfDestructCollectibles: { - communityTokensStore.remoteSelfDestructCollectibles(selfDestructTokensList, - chainId, - accountName, - accountAddress) + communityTokensStore.remoteSelfDestructCollectibles(root.community.id, + selfDestructTokensList, + contractUniqueKey) } onSignBurnTransactionOpened: communityTokensStore.computeBurnFee(chainId) onBurnCollectibles: communityTokensStore.burnCollectibles(tokenKey, amount) @@ -329,22 +347,43 @@ StatusSectionLayout { Connections { target: rootStore.communityTokensStore function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) { - if (errorCode === Constants.ComputeFeeErrorCode.Success || errorCode === Constants.ComputeFeeErrorCode.Balance) { - let valueStr = LocaleUtils.currencyAmountToLocaleString(ethCurrency) + "(" + LocaleUtils.currencyAmountToLocaleString(fiatCurrency) + ")" - mintPanel.feeText = valueStr - if (errorCode === Constants.ComputeFeeErrorCode.Balance) { - mintPanel.errorText = qsTr("Not enough funds to make transaction") - } - mintPanel.isFeeLoading = false - return - } else if (errorCode === Constants.ComputeFeeErrorCode.Infura) { - mintPanel.errorText = qsTr("Infura error") - mintPanel.isFeeLoading = true + mintPanel.setFeesInfo(ethCurrency, fiatCurrency, errorCode) + } + + function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) { + mintPanel.setFeesInfo(ethCurrency, fiatCurrency, errorCode) + } + + function onRemoteDestructStateChanged(communityId, tokenName, status, url) { + if (root.community.id !== communityId) { return } - mintPanel.errorText = qsTr("Unknown error") - mintPanel.isFeeLoading = true + let title = "" + let loading = false + let type = Constants.ephemeralNotificationType.normal + switch (status) { + case Constants.ContractTransactionStatus.InProgress: + title = qsTr("Remotely destroying tokens...") + loading = true + break + case Constants.ContractTransactionStatus.Completed: + title = qsTr("%1 tokens destroyed").arg(tokenName) + type = Constants.ephemeralNotificationType.success + break + case Constants.ContractTransactionStatus.Failed: + title = qsTr("%1 tokens destruction failed").arg(tokenName) + break + default: + console.warn("Unknown destruction state: "+status) + return + } + Global.displayToastMessage(title, + qsTr("View on etherscan"), + "", + loading, + type, + url) } function onDeploymentStateChanged(communityId, status, url) { @@ -356,15 +395,15 @@ StatusSectionLayout { let loading = false let type = Constants.ephemeralNotificationType.normal switch (status) { - case Constants.BackendProcessState.InProgress: + case Constants.ContractTransactionStatus.InProgress: title = qsTr("Token is being minted...") loading = true break - case Constants.BackendProcessState.Completed: + case Constants.ContractTransactionStatus.Completed: title = qsTr("Token minting finished") type = Constants.ephemeralNotificationType.success break - case Constants.BackendProcessState.Failed: + case Constants.ContractTransactionStatus.Failed: title = qsTr("Token minting failed") break default: diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml index 62541eb466..28d91c9d48 100644 --- a/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml @@ -17,6 +17,7 @@ StatusScrollView { signal itemClicked(int index, int chainId, + string contractUniqueKey, string chainName, string accountName, string accountAddress) @@ -25,11 +26,11 @@ StatusScrollView { id: d function getSubtitle(deployState, remainingTokens, supply) { - if(deployState === Constants.BackendProcessState.Failed) { + if(deployState === Constants.ContractTransactionStatus.Failed) { return qsTr("Minting failed") } - if(deployState === Constants.BackendProcessState.InProgress) { + if(deployState === Constants.ContractTransactionStatus.InProgress) { return qsTr("Minting...") } @@ -71,13 +72,13 @@ StatusScrollView { width: gridView.cellWidth title: model.name ? model.name : "..." subTitle: d.getSubtitle(model.deployState, model.remainingTokens, model.supply) - subTitleColor: (model.deployState === Constants.BackendProcessState.Failed) ? Theme.palette.dangerColor1 : Theme.palette.baseColor1 + subTitleColor: (model.deployState === Constants.ContractTransactionStatus.Failed) ? Theme.palette.dangerColor1 : Theme.palette.baseColor1 fallbackImageUrl: model.image ? model.image : "" backgroundColor: model.backgroundColor ? model.backgroundColor : "transparent" // TODO BACKEND isLoading: false navigationIconVisible: true - onClicked: root.itemClicked(model.index, model.chainId, model.chainName, model.accountName, model.address) // TODO: Replace to model.key when role exists in backend + onClicked: root.itemClicked(model.index, model.chainId, model.contractUniqueKey, model.chainName, model.accountName, model.address) } } } diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityTokenView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityTokenView.qml index 2b3685186e..22d90467a9 100644 --- a/ui/app/AppLayouts/Chat/views/communities/CommunityTokenView.qml +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityTokenView.qml @@ -37,13 +37,13 @@ StatusScrollView { property var tokenOwnersModel - property int deployState: Constants.BackendProcessState.None - property int burnState: Constants.BackendProcessState.None + property int deployState: Constants.ContractTransactionStatus.None + property int burnState: Constants.ContractTransactionStatus.None // Collectible object properties (ERC721) property bool transferable property bool selfDestruct - property int remotelyDestructState: Constants.BackendProcessState.None + property int remotelyDestructState: Constants.ContractTransactionStatus.None // Asset properties (ERC20) property alias assetDecimals: decimalsBox.value @@ -88,8 +88,8 @@ StatusScrollView { contentHeight: mainLayout.height padding: 0 - onRemotelyDestructStateChanged: if(remotelyDestructState === Constants.BackendProcessState.Completed) d.startAnimation(false) - onBurnStateChanged: if(burnState === Constants.BackendProcessState.Completed) d.startAnimation(true) + onRemotelyDestructStateChanged: if(remotelyDestructState === Constants.ContractTransactionStatus.Completed) d.startAnimation(false) + onBurnStateChanged: if(burnState === Constants.ContractTransactionStatus.Completed) d.startAnimation(true) ColumnLayout { id: mainLayout @@ -98,16 +98,16 @@ StatusScrollView { spacing: Style.current.padding RowLayout { - visible: !root.preview && ((root.deployState === Constants.BackendProcessState.InProgress) || - (root.deployState === Constants.BackendProcessState.Failed)) + visible: !root.preview && ((root.deployState === Constants.ContractTransactionStatus.InProgress) || + (root.deployState === Constants.ContractTransactionStatus.Failed)) spacing: Style.current.halfPadding StatusDotsLoadingIndicator { - visible: (root.deployState === Constants.BackendProcessState.InProgress) + visible: (root.deployState === Constants.ContractTransactionStatus.InProgress) } StatusIcon { - visible: (root.deployState === Constants.BackendProcessState.Failed) + visible: (root.deployState === Constants.ContractTransactionStatus.Failed) icon: "warning" color: Theme.palette.dangerColor1 } @@ -115,12 +115,12 @@ StatusScrollView { StatusBaseText { elide: Text.ElideRight font.pixelSize: Theme.primaryTextFontSize - text: (root.deployState === Constants.BackendProcessState.InProgress) ? + text: (root.deployState === Constants.ContractTransactionStatus.InProgress) ? (root.isAssetView ? qsTr("Asset is being minted") : qsTr("Collectible is being minted")) : - (root.deployState === Constants.BackendProcessState.Failed) ? + (root.deployState === Constants.ContractTransactionStatus.Failed) ? (root.isAssetView ? qsTr("Asset minting failed") : qsTr("Collectible minting failed")) : "" - color: (root.deployState === Constants.BackendProcessState.Failed) ? Theme.palette.dangerColor1 : Theme.palette.directColor1 + color: (root.deployState === Constants.ContractTransactionStatus.Failed) ? Theme.palette.dangerColor1 : Theme.palette.directColor1 } } @@ -239,8 +239,8 @@ StatusScrollView { label: qsTr("Total") value: root.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(root.supplyAmount) isLoading: !root.infiniteSupply && - ((root.remotelyDestructState === Constants.BackendProcessState.InProgress) || - (root.burnState === Constants.BackendProcessState.InProgress)) + ((root.remotelyDestructState === Constants.ContractTransactionStatus.InProgress) || + (root.burnState === Constants.ContractTransactionStatus.InProgress)) } CustomPreviewBox { @@ -248,7 +248,7 @@ StatusScrollView { label: qsTr("Remaining") value: root.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(root.remainingTokens) - isLoading: !root.infiniteSupply && (root.burnState === Constants.BackendProcessState.InProgress) + isLoading: !root.infiniteSupply && (root.burnState === Constants.ContractTransactionStatus.InProgress) } CustomPreviewBox { diff --git a/ui/imports/shared/stores/CommunityTokensStore.qml b/ui/imports/shared/stores/CommunityTokensStore.qml index 6908a1ef24..124c7cfd9c 100644 --- a/ui/imports/shared/stores/CommunityTokensStore.qml +++ b/ui/imports/shared/stores/CommunityTokensStore.qml @@ -14,10 +14,14 @@ QtObject { property var allNetworks: networksModule.all signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error) + signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error) + signal deploymentStateChanged(string communityId, int status, string url) - signal selfDestructFeeUpdated(string value) // TO BE REMOVED + signal burnFeeUpdated(string value) // TO BE REMOVED + signal remoteDestructStateChanged(string communityId, string tokenName, int status, string url) + // Minting tokens: function deployCollectible(communityId, accountAddress, name, symbol, description, supply, infiniteSupply, transferable, selfDestruct, chainId, artworkSource, accountName, artworkCropRect) @@ -43,26 +47,27 @@ QtObject { function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) { root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode) } + function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) { + root.selfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) + } function onDeploymentStateChanged(communityId, status, url) { root.deploymentStateChanged(communityId, status, url) } + function onRemoteDestructStateChanged(communityId, tokenName, status, url) { + root.remoteDestructStateChanged(communityId, tokenName, status, url) + } } function computeDeployFee(chainId, accountAddress) { communityTokensModuleInst.computeDeployFee(chainId, accountAddress) } - // Remotely destruct: - function computeSelfDestructFee(chainId) { - // TODO BACKEND - root.selfDestructFeeUpdated("0,0005 ETH") - console.warn("TODO: Compute self-destruct fee backend") + function computeSelfDestructFee(selfDestructTokensList, contractUniqueKey) { + communityTokensModuleInst.computeSelfDestructFee(JSON.stringify(selfDestructTokensList), contractUniqueKey) } - function remoteSelfDestructCollectibles(selfDestructTokensList, chainId, accountName, accountAddress) { - // TODO BACKEND - // selfDestructTokensList is a js array with properties: `walletAddress` and `amount` - console.warn("TODO: Remote self-destruct collectible backend") + function remoteSelfDestructCollectibles(communityId, selfDestructTokensList, contractUniqueKey) { + communityTokensModuleInst.selfDestructCollectibles(communityId, JSON.stringify(selfDestructTokensList), contractUniqueKey) } // Burn: diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index da07cacb6a..509fb919e1 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -893,7 +893,8 @@ QtObject { Everyone = 4 } - enum BackendProcessState { + // refers to ContractTransactionStatus and DeployState in Nim + enum ContractTransactionStatus { Failed, InProgress, Completed,