import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import QtQml 2.15 import StatusQ.Controls 0.1 import StatusQ.Core.Backpressure 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as SQUtils import AppLayouts.Communities.controls 1.0 import AppLayouts.Communities.helpers 1.0 import AppLayouts.Communities.layouts 1.0 import AppLayouts.Communities.popups 1.0 import AppLayouts.Communities.views 1.0 import AppLayouts.Wallet.helpers 1.0 import shared.controls 1.0 import utils 1.0 import shared.popups 1.0 import SortFilterProxyModel 0.2 StackView { id: root // General properties: property int viewWidth: 560 // by design property string previousPageName: depth > 1 ? qsTr("Back") : "" required property string communityId required property string communityName required property string communityLogo required property color communityColor property var sendModalPopup // User profile props: required property bool isOwner required property bool isTokenMasterOwner required property bool isAdmin readonly property bool isAdminOnly: root.isAdmin && !root.isPrivilegedTokenOwnerProfile readonly property bool isPrivilegedTokenOwnerProfile: root.isOwner || root.isTokenMasterOwner // Owner and TMaster token related properties: readonly property bool arePrivilegedTokensDeployed: root.isOwnerTokenDeployed && root.isTMasterTokenDeployed property bool isOwnerTokenDeployed: false property bool isTMasterTokenDeployed: false property bool anyPrivilegedTokenFailed: false // It will monitorize if Owner and/or TMaster token items are included in the `tokensModel` despite the deployment state property bool ownerOrTMasterTokenItemsExist: false // Network related properties: property var flatNetworks readonly property int ownerTokenChainId: SQUtils.ModelUtils.get(root.tokensModel, "privilegesLevel", Constants.TokenPrivilegesLevel.Owner).chainId ?? 0 readonly property int chainIndex: NetworkModelHelpers.getChainIndexByChainId(root.flatNetworks, root.ownerTokenChainId) readonly property string chainName: NetworkModelHelpers.getChainName(root.flatNetworks, chainIndex) property string enabledChainIds // Models: property var tokensModel property var membersModel property var accounts // Expected roles: address, name, color, emoji, walletType required property var referenceAssetsBySymbolModel signal mintCollectible(var collectibleItem) signal mintAsset(var assetItem) signal mintOwnerToken(var ownerToken, var tMasterToken) signal kickUserRequested(string contactId) signal banUserRequested(string contactId) signal remotelyDestructCollectibles(var walletsAndAmounts, // { [walletAddress (string), amount (int)] } string tokenKey, string accountAddress) signal remotelyDestructAndBan(string contactId, string tokenKey, string accountAddress, bool removeMessages) signal remotelyDestructAndKick(string contactId, string tokenKey, string accountAddress) signal burnToken(string tokenKey, string amount, string accountAddress) signal airdropToken(string tokenKey, string amount, int type, var addresses) signal deleteToken(string tokenKey) signal refreshToken(string tokenKey) signal registerDeployFeesSubscriber(var feeSubscriber) signal registerSelfDestructFeesSubscriber(var feeSubscriber) signal registerBurnTokenFeesSubscriber(var feeSubscriber) signal startTokenHoldersManagement(int chainId, string address) signal stopTokenHoldersManagement() signal enableNetwork(int chainId) function navigateBack() { pop(StackView.Immediate) } function resetNavigation() { pop(initialItem, StackView.Immediate) } // This method will be called from the outside from a different section like Airdrop or Permissions function openNewTokenForm(isAssetView) { resetNavigation() if(root.isAdminOnly) { // Admins can only see the initial tokens page. They cannot mint. Initial view. return } if(root.arePrivilegedTokensDeployed) { // Regular minting flow for Owner and TMaster owner, selecting the specific tab const properties = { isAssetView } root.push(newTokenViewComponent, properties, StackView.Immediate) return } if(root.ownerOrTMasterTokenItemsExist) { // Owner and TMaster tokens deployment action has been started at least ones but still without success. Initial view. return } if(root.isOwner) { // Owner and TMaster tokens to be deployed. Never tried. root.push(ownerTokenViewComponent, StackView.Immediate) return } } QtObject { id: d property string networkThatIsNotActive // Owner or TMaster token retry navigation function retryPrivilegedToken(key, chainId, accountName, accountAddress) { var properties = { key: key, chainId: chainId, accountName: accountName, accountAddress: accountAddress, } root.push(ownerTokenEditViewComponent, properties, StackView.Immediate) } } onVisibleChanged: { if (!visible) { return } // If the tokens' network is not activated, show a warning to the user if (!root.enabledChainIds.includes(root.ownerTokenChainId)) { d.networkThatIsNotActive = root.chainName } else { d.networkThatIsNotActive = "" } } initialItem: SettingsPage { implicitWidth: 0 title: qsTr("Tokens") buttons: [ StatusButton { objectName: "addNewItemButton" text: qsTr("Mint token") interactive: root.isPrivilegedTokenOwnerProfile && root.arePrivilegedTokensDeployed onClicked: root.push(newTokenViewComponent, StackView.Immediate) tooltip.text: root.isAdminOnly ? qsTr("In order to mint, you must hodl the TokenMaster token for %1").arg(root.communityName) : "" } ] contentItem: MintedTokensView { model: SortFilterProxyModel { sourceModel: root.tokensModel proxyRoles: ExpressionRole { name: "color" expression: root.communityColor } } isOwner: root.isOwner isAdmin: root.isAdmin communityName: root.communityName communityId: root.communityId anyPrivilegedTokenFailed: root.anyPrivilegedTokenFailed onItemClicked: root.push(tokenViewComponent, { tokenKey }, StackView.Immediate) onMintOwnerTokenClicked: root.push(ownerTokenViewComponent, StackView.Immediate) onRetryOwnerTokenClicked: d.retryPrivilegedToken(tokenKey, chainId, accountName, accountAddress) } } Component { id: tokenObjectComponent TokenObject {} } // Mint tokens possible view contents: Component { id: ownerTokenViewComponent SettingsPage { id: ownerTokenPage title: qsTr("Mint Owner token") contentItem: OwnerTokenWelcomeView { viewWidth: root.viewWidth communityLogo: root.communityLogo communityColor: root.communityColor communityName: root.communityName onNextClicked: { const chainId = !!root.flatNetworks? SQUtils.ModelUtils.getByKey(root.flatNetworks, "layer", 2).chainId : 0 const accountName = !!root.accounts && root.accounts.count > 0? SQUtils.ModelUtils.get(root.accounts, 0).name : "" const accountAddress = !!root.accounts && root.accounts.count > 0? SQUtils.ModelUtils.get(root.accounts, 0).address : "" d.retryPrivilegedToken("", chainId, accountName, accountAddress) } } } } Component { id: ownerTokenEditViewComponent SettingsPage { id: ownerTokenPage property int chainId property string accountName property string accountAddress title: qsTr("Mint Owner token") contentItem: EditOwnerTokenView { id: editOwnerTokenView function signMintTransaction() { root.mintOwnerToken(ownerToken, tMasterToken) root.resetNavigation() } viewWidth: root.viewWidth communityLogo: root.communityLogo communityColor: root.communityColor communityName: root.communityName ownerToken.chainId: ownerTokenPage.chainId ownerToken.accountName: ownerTokenPage.accountName ownerToken.accountAddress: ownerTokenPage.accountAddress tMasterToken.chainId: ownerTokenPage.chainId tMasterToken.accountName: ownerTokenPage.accountName tMasterToken.accountAddress: ownerTokenPage.accountAddress flatNetworks: root.flatNetworks accounts: root.accounts feeText: feeSubscriber.feeText feeErrorText: feeSubscriber.feeErrorText isFeeLoading: !feeSubscriber.feesResponse onMintClicked: signMintPopup.open() DeployFeesSubscriber { id: feeSubscriber communityId: root.communityId chainId: editOwnerTokenView.ownerToken.chainId tokenType: editOwnerTokenView.ownerToken.type isOwnerDeployment: editOwnerTokenView.ownerToken.isPrivilegedToken accountAddress: editOwnerTokenView.ownerToken.accountAddress enabled: editOwnerTokenView.visible || signMintPopup.visible Component.onCompleted: root.registerDeployFeesSubscriber(feeSubscriber) } SignTransactionsPopup { id: signMintPopup title: qsTr("Sign transaction - Mint %1 tokens").arg( editOwnerTokenView.communityName) totalFeeText: editOwnerTokenView.isFeeLoading ? "" : editOwnerTokenView.feeText errorText: editOwnerTokenView.feeErrorText accountName: editOwnerTokenView.ownerToken.accountName model: QtObject { readonly property string title: editOwnerTokenView.feeLabel readonly property string feeText: signMintPopup.totalFeeText readonly property bool error: editOwnerTokenView.feeErrorText !== "" } onSignTransactionClicked: editOwnerTokenView.signMintTransaction() } } } } Component { id: newTokenViewComponent SettingsPage { id: newTokenPage readonly property string chainIcon: NetworkModelHelpers.getChainIconUrl(root.flatNetworks, root.chainIndex) property TokenObject asset: TokenObject{ type: Constants.TokenType.ERC20 multiplierIndex: 18 // Minted tokens will use ALWAYS the same chain where the owner token was deployed. chainId: root.ownerTokenChainId chainName: root.chainName chainIcon: newTokenPage.chainIcon } property TokenObject collectible: TokenObject { type: Constants.TokenType.ERC721 // Minted tokens will use ALWAYS the same chain where the owner token was deployed. chainId: root.ownerTokenChainId chainName: root.chainName chainIcon: newTokenPage.chainIcon } property bool isAssetView: false property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty property string referenceName: "" property string referenceSymbol: "" title: qsTr("Mint token") contentItem: ColumnLayout { width: root.viewWidth spacing: Style.current.padding StatusSwitchTabBar { id: optionsTab Layout.preferredWidth: root.viewWidth currentIndex: newTokenPage.isAssetView ? 1 : 0 StatusSwitchTabButton { id: collectiblesTab text: qsTr("Collectibles") } StatusSwitchTabButton { id: assetsTab text: qsTr("Assets") } } StackLayout { Layout.preferredWidth: root.viewWidth Layout.fillHeight: true currentIndex: optionsTab.currentIndex CustomEditCommunityTokenView { id: newCollectibleView isAssetView: false validationMode: !newTokenPage.isAssetView ? newTokenPage.validationMode : StatusInput.ValidationMode.OnlyWhenDirty token: newTokenPage.collectible } CustomEditCommunityTokenView { id: newAssetView isAssetView: true validationMode: newTokenPage.isAssetView ? newTokenPage.validationMode : StatusInput.ValidationMode.OnlyWhenDirty token: newTokenPage.asset } component CustomEditCommunityTokenView: EditCommunityTokenView { id: editView viewWidth: root.viewWidth accounts: root.accounts tokensModel: root.tokensModel referenceAssetsBySymbolModel: root.referenceAssetsBySymbolModel referenceName: newTokenPage.referenceName referenceSymbol: newTokenPage.referenceSymbol feeText: deployFeeSubscriber.feeText feeErrorText: deployFeeSubscriber.feeErrorText isFeeLoading: !deployFeeSubscriber.feesResponse networkThatIsNotActive: d.networkThatIsNotActive onEnableNetwork: { root.enableNetwork(root.ownerTokenChainId) d.networkThatIsNotActive = "" } onPreviewClicked: { const properties = { token: token } root.push(previewTokenViewComponent, properties, StackView.Immediate) } DeployFeesSubscriber { id: deployFeeSubscriber communityId: root.communityId chainId: editView.token.chainId tokenType: editView.token.type isOwnerDeployment: editView.token.isPrivilegedToken accountAddress: editView.token.accountAddress enabled: editView.visible Component.onCompleted: root.registerDeployFeesSubscriber(deployFeeSubscriber) } } } } } } Component { id: previewTokenViewComponent SettingsPage { id: tokenPreviewPage property alias token: preview.token title: token.name subtitle: token.symbol contentItem: CommunityTokenView { id: preview viewWidth: root.viewWidth preview: true accounts: root.accounts feeText: feeSubscriber.feeText feeErrorText: feeSubscriber.feeErrorText isFeeLoading: !feeSubscriber.feesResponse onMintClicked: signMintPopup.open() function signMintTransaction() { if(preview.isAssetView) root.mintAsset(token) else root.mintCollectible(token) root.resetNavigation() } DeployFeesSubscriber { id: feeSubscriber communityId: root.communityId chainId: preview.token.chainId tokenType: preview.token.type isOwnerDeployment: preview.token.isPrivilegedToken accountAddress: preview.token.accountAddress enabled: preview.visible || signMintPopup.visible Component.onCompleted: root.registerDeployFeesSubscriber(feeSubscriber) } SignTransactionsPopup { id: signMintPopup title: qsTr("Sign transaction - Mint %1 token").arg( preview.token.name) totalFeeText: preview.isFeeLoading ? "" : preview.feeText accountName: preview.token.accountName model: QtObject { readonly property string title: preview.feeLabel readonly property string feeText: signMintPopup.totalFeeText readonly property bool error: preview.feeErrorText !== "" } onSignTransactionClicked: preview.signMintTransaction() } } } } component TokenViewPage: SettingsPage { id: tokenViewPage property TokenObject token: TokenObject {} readonly property bool deploymentFailed: view.deployState === Constants.ContractTransactionStatus.Failed property var membersModel property var tokenOwnersModel property string airdropKey // Owner and TMaster related props readonly property bool isPrivilegedTokenItem: isOwnerTokenItem || isTMasterTokenItem readonly property bool isOwnerTokenItem: token.privilegesLevel === Constants.TokenPrivilegesLevel.Owner readonly property bool isTMasterTokenItem: token.privilegesLevel === Constants.TokenPrivilegesLevel.TMaster title: view.name subtitle: view.symbol buttons: [ StatusButton { text: qsTr("Delete") type: StatusBaseButton.Type.Danger visible: (!tokenViewPage.isPrivilegedTokenItem) && !root.isAdminOnly && tokenViewPage.deploymentFailed onClicked: deleteTokenAlertPopup.open() }, StatusButton { function retryAssetOrCollectible() { // https://bugreports.qt.io/browse/QTBUG-91917 var isAssetView = tokenViewPage.token.type === Constants.TokenType.ERC20 // Copy TokenObject var tokenObject = tokenObjectComponent.createObject(null, view.token) // Then move on to the new token view, but token pre-filled: var properties = { isAssetView, referenceName: tokenObject.name, referenceSymbol: tokenObject.symbol, validationMode: StatusInput.ValidationMode.Always, [isAssetView ? "asset" : "collectible"]: tokenObject } var tokenView = root.push(newTokenViewComponent, properties, StackView.Immediate) // Cleanup dynamically created TokenObject tokenView.Component.destruction.connect(() => tokenObject.destroy()) } text: qsTr("Retry mint") visible: (tokenViewPage.isPrivilegedTokenItem && root.isOwner && tokenViewPage.deploymentFailed) || (!tokenViewPage.isPrivilegedTokenItem && !root.isAdminOnly && tokenViewPage.deploymentFailed) onClicked: { if(tokenViewPage.isPrivilegedTokenItem) { d.retryPrivilegedToken(view.token.key, view.token.chainId, view.token.accountName, view.token.accountAddress) } else { retryAssetOrCollectible() } } }, StatusButton { text: qsTr("Refresh") visible: localAppSettings.refreshTokenEnabled && (tokenViewPage.token.deployState === Constants.ContractTransactionStatus.InProgress) onClicked: root.refreshToken(tokenViewPage.token.key) tooltip.text: qsTr("Restart token's transaction listening if the token got stuck in minting state") } ] contentItem: CommunityTokenView { id: view property string airdropKey: tokenViewPage.airdropKey // TO REMOVE: Temporal property until airdrop backend is not ready to use token key instead of symbol viewWidth: root.viewWidth token: tokenViewPage.token membersModel: tokenViewPage.membersModel tokenOwnersModel: tokenViewPage.tokenOwnersModel isOwnerTokenItem: tokenViewPage.isOwnerTokenItem onStartTokenHoldersManagement: root.startTokenHoldersManagement(chainId, address) onStopTokenHoldersManagement: root.stopTokenHoldersManagement() onGeneralAirdropRequested: { root.airdropToken(view.airdropKey, "1" + "0".repeat(view.token.multiplierIndex), view.token.type, []) // tokenKey instead when backend airdrop ready to use key instead of symbol } onAirdropRequested: { root.airdropToken(view.airdropKey, "1" + "0".repeat(view.token.multiplierIndex), view.token.type, [address]) // tokenKey instead when backend airdrop ready to use key instead of symbol } onViewProfileRequested: { Global.openProfilePopup(contactId) } onViewMessagesRequested: { // TODO: https://github.com/status-im/status-desktop/issues/11860 console.warn("View Messages is not implemented yet") } onRemoteDestructRequested: { if (token.isPrivilegedToken) { tokenMasterActionPopup.openPopup( TokenMasterActionPopup.ActionType.RemotelyDestruct, name, address, "") } else { remotelyDestructPopup.open() // TODO: set the address selected in the popup's list } } onBanRequested: { if (token.isPrivilegedToken) tokenMasterActionPopup.openPopup( TokenMasterActionPopup.ActionType.Ban, name, address, contactId) else kickBanPopup.openPopup(KickBanPopup.Mode.Ban, name, contactId) } onKickRequested: { if (token.isPrivilegedToken) tokenMasterActionPopup.openPopup( TokenMasterActionPopup.ActionType.Kick, name, address, contactId) else kickBanPopup.openPopup(KickBanPopup.Mode.Kick, name, contactId) } TokenMasterActionPopup { id: tokenMasterActionPopup property string address: "" property string contactId: "" communityName: root.communityName networkName: view.token.chainName accountsModel: root.accounts feeText: selfDestructFeesSubscriber.feeText feeErrorText: selfDestructFeesSubscriber.feeErrorText isFeeLoading: !selfDestructFeesSubscriber.feesResponse function openPopup(type, userName, address, contactId) { tokenMasterActionPopup.actionType = type tokenMasterActionPopup.userName = userName || SQUtils.Utils.elideAndFormatWalletAddress(address) tokenMasterActionPopup.address = address tokenMasterActionPopup.contactId = contactId open() } onRemotelyDestructClicked: signPopup.open() onKickClicked: signPopup.open() onBanClicked: signPopup.open() SelfDestructFeesSubscriber { id: selfDestructFeesSubscriber walletsAndAmounts: [{ walletAddress: tokenMasterActionPopup.address, amount: 1 }] accountAddress: tokenMasterActionPopup.selectedAccount tokenKey: view.token.key enabled: tokenMasterActionPopup.opened Component.onCompleted: root.registerSelfDestructFeesSubscriber( selfDestructFeesSubscriber) } SignTransactionsPopup { id: signPopup title: qsTr("Sign transaction - Remotely-destruct TokenMaster token") totalFeeText: tokenMasterActionPopup.feeText errorText: tokenMasterActionPopup.feeErrorText accountName: tokenMasterActionPopup.selectedAccountName model: QtObject { readonly property string title: tokenMasterActionPopup.feeLabel readonly property string feeText: tokenMasterActionPopup.feeText readonly property bool error: tokenMasterActionPopup.feeErrorText !== "" } onSignTransactionClicked: { // https://bugreports.qt.io/browse/QTBUG-91917 var contactId = tokenMasterActionPopup.contactId var tokenKey = tokenViewPage.token.key var accountAddress = tokenMasterActionPopup.selectedAccount switch (tokenMasterActionPopup.actionType) { case TokenMasterActionPopup.ActionType.RemotelyDestruct: var tokenToDestruct = { walletAddress: tokenMasterActionPopup.address, amount: 1 } root.remotelyDestructCollectibles( [tokenToDestruct], tokenKey, accountAddress) break case TokenMasterActionPopup.ActionType.Kick: root.remotelyDestructAndKick(contactId, tokenKey, accountAddress) break case TokenMasterActionPopup.ActionType.Ban: root.remotelyDestructAndBan(contactId, tokenKey, accountAddress, tokenMasterActionPopup.deleteMessages) break } tokenMasterActionPopup.close() } } } KickBanPopup { id: kickBanPopup property string contactId communityName: root.communityName onAccepted: { if (mode === KickBanPopup.Mode.Kick) root.kickUserRequested(contactId) else root.banUserRequested(contactId) } function openPopup(mode, userName, contactId) { kickBanPopup.mode = mode kickBanPopup.username = userName kickBanPopup.contactId = contactId open() } } } footer: MintTokensFooterPanel { id: footer readonly property TokenObject token: view.token readonly property bool isAssetView: view.isAssetView readonly property bool deployStateCompleted: token.deployState === Constants.ContractTransactionStatus.Completed function closePopups() { remotelyDestructPopup.close() alertPopup.close() signTransactionPopup.close() burnTokensPopup.close() } communityName: root.communityName visible: { if(tokenViewPage.isOwnerTokenItem || tokenViewPage.isTMasterTokenItem) // Only footer if owner profile return root.isOwner // Always present return true } airdropEnabled: deployStateCompleted && (token.infiniteSupply || token.remainingTokens > 0) remotelyDestructEnabled: deployStateCompleted && !!view.tokenOwnersModel && view.tokenOwnersModel.count > 0 burnEnabled: deployStateCompleted sendOwnershipEnabled: deployStateCompleted sendOwnershipVisible: root.isOwner && tokenViewPage.isOwnerTokenItem airdropVisible: !tokenViewPage.isOwnerTokenItem remotelyDestructVisible: !tokenViewPage.isOwnerTokenItem && token.remotelyDestruct burnVisible: !tokenViewPage.isOwnerTokenItem && !token.infiniteSupply onAirdropClicked: root.airdropToken( view.airdropKey, "1" + "0".repeat(view.token.multiplierIndex), view.token.type, []) onRemotelyDestructClicked: remotelyDestructPopup.open() onBurnClicked: burnTokensPopup.open() onSendOwnershipClicked: Global.openTransferOwnershipPopup(root.communityId, root.communityName, root.communityLogo, tokenViewPage.token, root.sendModalPopup) // helper properties to pass data through popups property var walletsAndAmounts property string burnAmount property string accountAddress RemotelyDestructPopup { id: remotelyDestructPopup property alias feeSubscriber: remotelyDestructFeeSubscriber collectibleName: view.token.name model: view.tokenOwnersModel || null accounts: root.accounts chainName: view.token.chainName feeText: remotelyDestructFeeSubscriber.feeText feeErrorText: remotelyDestructFeeSubscriber.feeErrorText isFeeLoading: !remotelyDestructFeeSubscriber.feesResponse networkThatIsNotActive: d.networkThatIsNotActive onEnableNetwork: { root.enableNetwork(root.ownerTokenChainId) d.networkThatIsNotActive = "" } onRemotelyDestructClicked: { remotelyDestructPopup.close() footer.accountAddress = accountAddress footer.walletsAndAmounts = walletsAndAmounts alertPopup.open() } SelfDestructFeesSubscriber { id: remotelyDestructFeeSubscriber walletsAndAmounts: remotelyDestructPopup.selectedWalletsAndAmounts accountAddress: remotelyDestructPopup.selectedAccount tokenKey: view.token.key enabled: remotelyDestructPopup.tokenCount > 0 && accountAddress !== "" && (remotelyDestructPopup.opened || signTransactionPopup.opened) Component.onCompleted: root.registerSelfDestructFeesSubscriber(remotelyDestructFeeSubscriber) } } BurnTokensPopup { id: burnTokensPopup property alias feeSubscriber: burnTokensFeeSubscriber communityName: root.communityName tokenName: footer.token.name remainingTokens: footer.token.remainingTokens multiplierIndex: footer.token.multiplierIndex tokenSource: footer.token.artworkSource chainName: footer.token.chainName networkThatIsNotActive: d.networkThatIsNotActive onEnableNetwork: { root.enableNetwork(root.ownerTokenChainId) d.networkThatIsNotActive = "" } onAmountToBurnChanged: burnTokensFeeSubscriber.updateAmount() feeText: burnTokensFeeSubscriber.feeText feeErrorText: burnTokensFeeSubscriber.feeErrorText isFeeLoading: burnTokensFeeSubscriber.feeText === "" && burnTokensFeeSubscriber.feeErrorText === "" accounts: root.accounts onBurnClicked: { burnTokensPopup.close() footer.burnAmount = burnAmount footer.accountAddress = accountAddress signTransactionPopup.isRemotelyDestructTransaction = false signTransactionPopup.open() } BurnTokenFeesSubscriber { id: burnTokensFeeSubscriber readonly property var updateAmount: Backpressure.debounce(burnTokensFeeSubscriber, 500, () => { burnTokensFeeSubscriber.amount = burnTokensPopup.amountToBurn }) amount: "" tokenKey: tokenViewPage.token.key accountAddress: burnTokensPopup.selectedAccountAddress enabled: burnTokensPopup.visible || signTransactionPopup.visible Component.onCompleted: root.registerBurnTokenFeesSubscriber(burnTokensFeeSubscriber) } } AlertPopup { id: alertPopup title: qsTr("Remotely destruct %n token(s)", "", remotelyDestructPopup.tokenCount) acceptBtnText: qsTr("Remotely destruct") alertText: qsTr("Continuing will destroy tokens held by members and revoke any permissions they are given. To undo you will have to issue them new tokens.") onAcceptClicked: { signTransactionPopup.isRemotelyDestructTransaction = true signTransactionPopup.open() } } SignTransactionsPopup { id: signTransactionPopup property bool isRemotelyDestructTransaction readonly property string tokenName: footer.token.name title: isRemotelyDestructTransaction ? qsTr("Sign transaction - Remotely destruct %1 token").arg(tokenName) : qsTr("Sign transaction - Burn %1 tokens").arg(tokenName) accountName: footer.token.accountName totalFeeText: isRemotelyDestructTransaction ? remotelyDestructPopup.feeText : burnTokensPopup.feeText errorText: isRemotelyDestructTransaction ? remotelyDestructPopup.feeErrorText : burnTokensPopup.feeErrorText model: QtObject { readonly property string title: signTransactionPopup.isRemotelyDestructTransaction ? qsTr("Remotely destruct %Ln %1 token(s) on %2", "", remotelyDestructPopup.tokenCount) .arg(remotelyDestructPopup.collectibleName) .arg(remotelyDestructPopup.chainName) : burnTokensPopup.feeLabel readonly property string feeText: signTransactionPopup.totalFeeText readonly property bool error: signTransactionPopup.errorText !== "" } onSignTransactionClicked: { if(signTransactionPopup.isRemotelyDestructTransaction) root.remotelyDestructCollectibles(footer.walletsAndAmounts, tokenKey, footer.accountAddress) else root.burnToken(tokenKey, footer.burnAmount, footer.accountAddress) footer.closePopups() } } } AlertPopup { id: deleteTokenAlertPopup readonly property string tokenName: view.token.name width: 521 title: qsTr("Delete %1").arg(tokenName) acceptBtnText: qsTr("Delete %1 token").arg(tokenName) alertText: qsTr("%1 is not yet minted, are you sure you want to delete it? All data associated with this token including its icon and description will be permanently deleted.").arg(tokenName) onAcceptClicked: { root.deleteToken(tokenViewPage.token.key) root.navigateBack() } } } Component { id: tokenViewComponent Item { id: tokenViewPageWrapper property string tokenKey Repeater { model: SortFilterProxyModel { sourceModel: root.tokensModel filters: ValueFilter { roleName: "contractUniqueKey" value: tokenViewPageWrapper.tokenKey } } delegate: TokenViewPage { required property var model implicitWidth: 0 anchors.fill: parent tokenOwnersModel: model.tokenOwnersModel membersModel: root.membersModel airdropKey: model.symbol // TO BE REMOVED: When airdrop backend is ready to use token key instead of symbol token.privilegesLevel: model.privilegesLevel token.color: root.communityColor token.accountName: model.accountName token.artworkSource: model.image token.chainIcon: model.chainIcon token.chainId: model.chainId token.chainName: model.chainName token.decimals: model.decimals token.deployState: model.deployState token.description: model.description token.infiniteSupply: model.infiniteSupply token.key: model.contractUniqueKey token.name: model.name token.remainingTokens: model.remainingSupply token.remotelyDestruct: model.remoteSelfDestruct token.supply: model.supply token.symbol: model.symbol token.transferable: model.transferable token.type: model.tokenType token.burnState: model.burnState token.remotelyDestructState: model.remotelyDestructState token.accountAddress: model.accountAddress token.multiplierIndex: model.multiplierIndex token.tokenAddress: model.tokenAddress token.tokenHoldersLoading: model.tokenHoldersLoading } onCountChanged: { if (count === 0) root.navigateBack() } } } } }