From 8db0ac94f00aa1cad72279742647b2a0bfca7543 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Mon, 22 Jul 2024 09:52:44 -0400 Subject: [PATCH] fix(networks): enable network before mint or airdrop (#15601) Fixes #15507 Makes sure to enable the network where the owner token was minted before minting or airdroping a token. This makes sure that the account selector shows the right balance and there is no ETH error before doing the transation --- .../main/wallet_section/networks/view.nim | 3 + ui/app/AppLayouts/Chat/ChatLayout.qml | 2 + .../panels/AirdropsSettingsPanel.qml | 5 ++ .../panels/MintTokensSettingsPanel.qml | 61 ++++++++++++++----- .../panels/NetworkWarningPanel.qml | 26 ++++++++ ui/app/AppLayouts/Communities/panels/qmldir | 1 + .../Communities/popups/BurnTokensPopup.qml | 10 +++ .../popups/RemotelyDestructPopup.qml | 17 +++++- .../views/CommunitySettingsView.qml | 9 +++ .../Communities/views/EditAirdropView.qml | 35 +++++++++++ .../views/EditCommunityTokenView.qml | 12 ++++ ui/app/AppLayouts/Wallet/stores/RootStore.qml | 4 ++ 12 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 ui/app/AppLayouts/Communities/panels/NetworkWarningPanel.qml diff --git a/src/app/modules/main/wallet_section/networks/view.nim b/src/app/modules/main/wallet_section/networks/view.nim index 78c6165eb1..2d8e01ea05 100644 --- a/src/app/modules/main/wallet_section/networks/view.nim +++ b/src/app/modules/main/wallet_section/networks/view.nim @@ -91,6 +91,9 @@ QtObject: let (chainIds, enable) = self.flatNetworks.networksToChangeStateOnUserActionFor(chainId, self.areTestNetworksEnabled) self.delegate.setNetworksState(chainIds, enable) + proc enableNetwork*(self: View, chainId: int) {.slot.} = + self.delegate.setNetworksState(@[chainId], enable = true) + proc getNetworkShortNames*(self: View, preferredNetworks: string): string {.slot.} = return self.flatNetworks.getNetworkShortNames(preferredNetworks, self.areTestNetworksEnabled) diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index 6c76005155..13a7251ce9 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -235,6 +235,8 @@ StackLayout { id: communitySettingsView rootStore: root.rootStore walletAccountsModel: WalletStore.RootStore.nonWatchAccounts + enabledChainIds: WalletStore.RootStore.networkFilters + onEnableNetwork: WalletStore.RootStore.enableNetwork(chainId) tokensStore: root.tokensStore sendModalPopup: root.sendModalPopup transactionStore: root.transactionStore diff --git a/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml index 89a46a8658..2529569972 100644 --- a/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml @@ -33,12 +33,15 @@ StackView { required property var membersModel required property var accountsModel + required property string enabledChainIds + property int viewWidth: 560 // by design property string previousPageName: depth > 1 ? qsTr("Airdrops") : "" signal airdropClicked(var airdropTokens, var addresses, string feeAccountAddress) signal navigateToMintTokenSettings(bool isAssetType) signal registerAirdropFeeSubscriber(var feeSubscriber) + signal enableNetwork(int chainId) function navigateBack() { pop(StackView.Immediate) @@ -120,6 +123,8 @@ StackView { feeErrorText: feesSubscriber.feesError feesPerSelectedContract: feesSubscriber.feesPerContract feesAvailable: !!feesSubscriber.airdropFeesResponse + enabledChainIds: root.enabledChainIds + onEnableNetwork: root.enableNetwork(chainId) onAirdropClicked: { root.airdropClicked(airdropTokens, addresses, feeAccountAddress) diff --git a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml index 2d0e4173fd..aa95d18c4b 100644 --- a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml @@ -49,15 +49,19 @@ StackView { // 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 - // Network related properties: - property var flatNetworks - signal mintCollectible(var collectibleItem) signal mintAsset(var assetItem) signal mintOwnerToken(var ownerToken, var tMasterToken) @@ -83,6 +87,8 @@ StackView { signal startTokenHoldersManagement(int chainId, string address) signal stopTokenHoldersManagement() + signal enableNetwork(int chainId) + function navigateBack() { pop(StackView.Immediate) } @@ -122,6 +128,8 @@ StackView { QtObject { id: d + property string networkThatIsNotActive + // Owner or TMaster token retry navigation function retryPrivilegedToken(key, chainId, accountName, accountAddress) { var properties = { @@ -136,6 +144,18 @@ StackView { } } + 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") @@ -276,18 +296,15 @@ StackView { SettingsPage { id: newTokenPage - readonly property int ownerTokenChainId: SQUtils.ModelUtils.get(root.tokensModel, "privilegesLevel", Constants.TokenPrivilegesLevel.Owner).chainId ?? 0 - readonly property int chainIndex: NetworkModelHelpers.getChainIndexByChainId(root.flatNetworks, ownerTokenChainId) - readonly property string chainName: NetworkModelHelpers.getChainName(root.flatNetworks, chainIndex) - readonly property string chainIcon: NetworkModelHelpers.getChainIconUrl(root.flatNetworks, chainIndex) + 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: newTokenPage.ownerTokenChainId - chainName: newTokenPage.chainName + chainId: root.ownerTokenChainId + chainName: root.chainName chainIcon: newTokenPage.chainIcon } @@ -295,13 +312,11 @@ StackView { type: Constants.TokenType.ERC721 // Minted tokens will use ALWAYS the same chain where the owner token was deployed. - chainId: newTokenPage.ownerTokenChainId - chainName: newTokenPage.chainName + chainId: root.ownerTokenChainId + chainName: root.chainName chainIcon: newTokenPage.chainIcon } - - property bool isAssetView: false property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty property string referenceName: "" @@ -373,6 +388,12 @@ StackView { feeErrorText: deployFeeSubscriber.feeErrorText isFeeLoading: !deployFeeSubscriber.feesResponse + networkThatIsNotActive: d.networkThatIsNotActive + onEnableNetwork: { + root.enableNetwork(root.ownerTokenChainId) + d.networkThatIsNotActive = "" + } + onPreviewClicked: { const properties = { token: token @@ -780,7 +801,13 @@ StackView { feeText: remotelyDestructFeeSubscriber.feeText feeErrorText: remotelyDestructFeeSubscriber.feeErrorText - isFeeLoading: !remotelyDestructFeeSubscriber.feesResponse + isFeeLoading: !remotelyDestructFeeSubscriber.feesResponse + + networkThatIsNotActive: d.networkThatIsNotActive + onEnableNetwork: { + root.enableNetwork(root.ownerTokenChainId) + d.networkThatIsNotActive = "" + } onRemotelyDestructClicked: { remotelyDestructPopup.close() @@ -812,6 +839,12 @@ StackView { tokenSource: footer.token.artworkSource chainName: footer.token.chainName + networkThatIsNotActive: d.networkThatIsNotActive + onEnableNetwork: { + root.enableNetwork(root.ownerTokenChainId) + d.networkThatIsNotActive = "" + } + onAmountToBurnChanged: burnTokensFeeSubscriber.updateAmount() feeText: burnTokensFeeSubscriber.feeText diff --git a/ui/app/AppLayouts/Communities/panels/NetworkWarningPanel.qml b/ui/app/AppLayouts/Communities/panels/NetworkWarningPanel.qml new file mode 100644 index 0000000000..1940e58215 --- /dev/null +++ b/ui/app/AppLayouts/Communities/panels/NetworkWarningPanel.qml @@ -0,0 +1,26 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 + +RowLayout { + id: root + + property string networkThatIsNotActive + signal enableNetwork + + spacing: 6 + + WarningPanel { + id: wantedNetworkNotActive + Layout.fillWidth: true + text: qsTr("The owner token is minted on a network that isn't selected. Click here to enable it:") + } + + StatusButton { + text: qsTr("Enable %1").arg(root.networkThatIsNotActive) + Layout.alignment: Qt.AlignVCenter + onClicked: root.enableNetwork() + } +} diff --git a/ui/app/AppLayouts/Communities/panels/qmldir b/ui/app/AppLayouts/Communities/panels/qmldir index f4f7c10d6a..48682a4aaa 100644 --- a/ui/app/AppLayouts/Communities/panels/qmldir +++ b/ui/app/AppLayouts/Communities/panels/qmldir @@ -19,6 +19,7 @@ MembersSettingsPanel 1.0 MembersSettingsPanel.qml MembersTabPanel 1.0 MembersTabPanel.qml MintTokensFooterPanel 1.0 MintTokensFooterPanel.qml MintTokensSettingsPanel 1.0 MintTokensSettingsPanel.qml +NetworkWarningPanel 1.0 NetworkWarningPanel.qml OverviewSettingsChart 1.0 OverviewSettingsChart.qml OverviewSettingsFooter 1.0 OverviewSettingsFooter.qml OverviewSettingsPanel 1.0 OverviewSettingsPanel.qml diff --git a/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml b/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml index cf9d0bdac9..1effbbde26 100644 --- a/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml @@ -26,6 +26,7 @@ StatusDialog { property int multiplierIndex property url tokenSource property string chainName + property string networkThatIsNotActive readonly property alias amountToBurn: d.amountToBurn readonly property alias selectedAccountAddress: d.accountAddress @@ -41,6 +42,7 @@ StatusDialog { signal burnClicked(string burnAmount, string accountAddress) signal cancelClicked + signal enableNetwork QtObject { id: d @@ -186,6 +188,14 @@ StatusDialog { readonly property bool error: d.isFeeError } } + + NetworkWarningPanel { + visible: !!root.networkThatIsNotActive + Layout.fillWidth: true + + networkThatIsNotActive: root.networkThatIsNotActive + onEnableNetwork: root.enableNetwork() + } } header: StatusDialogHeader { diff --git a/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml b/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml index ca347fe129..dc8f4d2c91 100644 --- a/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml @@ -19,6 +19,7 @@ StatusDialog { property alias model: tokenHoldersPanel.model property string collectibleName property string chainName + property string networkThatIsNotActive // Fees related properties: property bool isFeeLoading @@ -38,6 +39,7 @@ StatusDialog { property var accounts signal remotelyDestructClicked(var walletsAndAmounts, string accountAddress) + signal enableNetwork QtObject { id: d @@ -103,7 +105,7 @@ StatusDialog { id: feesBox Layout.fillWidth: true - Layout.bottomMargin: 16 + Layout.bottomMargin: networkWarningPanel.visible ? 0 : 16 Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -129,6 +131,19 @@ StatusDialog { readonly property bool error: d.isFeeError } } + + NetworkWarningPanel { + id: networkWarningPanel + + visible: !!root.networkThatIsNotActive + Layout.fillWidth: true + Layout.bottomMargin: 16 + Layout.leftMargin: 18 + Layout.rightMargin: 18 + + networkThatIsNotActive: root.chainName + onEnableNetwork: root.enableNetwork() + } } footer: StatusDialogFooter { diff --git a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml index 8a480c01e5..d7fea79cdc 100644 --- a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml @@ -40,6 +40,8 @@ StatusSectionLayout { property bool communitySettingsDisabled property var sendModalPopup + required property string enabledChainIds + required property var walletAccountsModel // name, address, emoji, color readonly property bool isOwner: community.memberRole === Constants.memberRole.owner @@ -51,6 +53,8 @@ StatusSectionLayout { required property bool isPendingOwnershipRequest signal finaliseOwnershipClicked + signal enableNetwork(int chainId) + readonly property string filteredSelectedTags: { let tagsArray = [] if (community && community.tags) { @@ -334,6 +338,7 @@ StatusSectionLayout { readonly property string sectionName: qsTr("Tokens") readonly property string sectionIcon: "token" readonly property bool sectionEnabled: true + enabledChainIds: root.enabledChainIds readonly property CommunityTokensStore communityTokensStore: rootStore.communityTokensStore @@ -373,6 +378,8 @@ StatusSectionLayout { onStopTokenHoldersManagement: communityTokensStore.stopTokenHoldersManagement() + onEnableNetwork: root.enableNetwork(chainId) + onMintCollectible: communityTokensStore.deployCollectible( root.community.id, collectibleItem) @@ -526,6 +533,8 @@ StatusSectionLayout { assetsModel: assetsModelLoader.item collectiblesModel: collectiblesModelLoader.item membersModel: community.members + enabledChainIds: root.enabledChainIds + onEnableNetwork: root.enableNetwork(chainId) accountsModel: root.walletAccountsModel onAirdropClicked: communityTokensStore.airdrop( diff --git a/ui/app/AppLayouts/Communities/views/EditAirdropView.qml b/ui/app/AppLayouts/Communities/views/EditAirdropView.qml index f9f883aed5..6392e213fc 100644 --- a/ui/app/AppLayouts/Communities/views/EditAirdropView.qml +++ b/ui/app/AppLayouts/Communities/views/EditAirdropView.qml @@ -45,6 +45,8 @@ StatusScrollView { // Bool property indicating whether the fees are available required property bool feesAvailable + property string enabledChainIds + property int viewWidth: 560 // by design readonly property var selectedHoldingsModel: ListModel {} @@ -101,6 +103,8 @@ StatusScrollView { signal navigateToMintTokenSettings(bool isAssetType) + signal enableNetwork(int chainId) + function selectToken(key, amount, type) { if(selectedHoldingsModel) selectedHoldingsModel.clear() @@ -125,6 +129,9 @@ StatusScrollView { && airdropRecipientsSelector.valid && airdropRecipientsSelector.count > 0 + property string networkThatIsNotActive + property int networkIdThatIsNotActive + readonly property var selectedContractKeysAndAmounts: { //Depedencies: root.selectedHoldingsModel @@ -161,6 +168,7 @@ StatusScrollView { key, amount, type, tokenText: amountLocalized + " " + modelItem.name, tokenImage: modelItem.iconSource, + networkId: modelItem.chainId, networkText: modelItem.chainName, networkImage: Style.svg(modelItem.chainIcon), remainingSupply: modelItem.remainingSupply, @@ -292,12 +300,25 @@ StatusScrollView { root.selectedHoldingsModel, ["key", "amount"]) } + function isChainEnabled(entry) { + // If the tokens' network is not activated, show a warning to the user + if (!!entry && !root.enabledChainIds.includes(entry.networkId)) { + d.networkThatIsNotActive = entry.networkText + d.networkIdThatIsNotActive = entry.networkId + } else { + d.networkThatIsNotActive = "" + d.networkIdThatIsNotActive = 0 + } + } + onAddAsset: { const entry = d.prepareEntry(key, amount, Constants.TokenType.ERC20) entry.valid = true selectedHoldingsModel.append(entry) dropdown.close() + + isChainEnabled(entry) } onAddCollectible: { @@ -306,6 +327,8 @@ StatusScrollView { selectedHoldingsModel.append(entry) dropdown.close() + + isChainEnabled(entry) } onUpdateAsset: { @@ -554,6 +577,18 @@ StatusScrollView { recipientsCountInstantiator.maximumRecipientsCount < airdropRecipientsSelector.count } + NetworkWarningPanel { + visible: !!d.networkThatIsNotActive + Layout.fillWidth: true + Layout.topMargin: Style.current.padding + networkThatIsNotActive: d.networkThatIsNotActive + onEnableNetwork: { + root.enableNetwork(d.networkIdThatIsNotActive) + d.networkThatIsNotActive = "" + d.networkIdThatIsNotActive = 0 + } + } + StatusButton { Layout.preferredHeight: 44 Layout.alignment: Qt.AlignHCenter diff --git a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml index 1557f06c5c..a5a544e2d5 100644 --- a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml +++ b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml @@ -44,6 +44,9 @@ StatusScrollView { property string feeErrorText property bool isFeeLoading + property string networkThatIsNotActive + signal enableNetwork + readonly property string feeLabel: isAssetView ? qsTr("Mint asset on %1").arg(root.token.chainName) : qsTr("Mint collectible on %1").arg(root.token.chainName) @@ -253,6 +256,15 @@ StatusScrollView { } } + NetworkWarningPanel { + visible: !!root.networkThatIsNotActive + Layout.fillWidth: true + Layout.topMargin: Style.current.padding + + networkThatIsNotActive: root.networkThatIsNotActive + onEnableNetwork: root.enableNetwork() + } + CustomSwitchRowComponent { id: unlimitedSupplyChecker diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 9869d41693..de9c4475e6 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -375,6 +375,10 @@ QtObject { networksModule.toggleNetwork(chainId) } + function enableNetwork(chainId) { + networksModule.enableNetwork(chainId) + } + function copyToClipboard(text) { globalUtils.copyToClipboard(text) }