fix(MintTokens): Network chosen locks in the network used for all future tokens minted for that community

- Mint Owner token / TokenMaster token form: Changed text description for network field.
- Mint asset / collectible: Lock network. It should be the same network than the owner token one.
-  Mint asset / collectible: Additional simplification / clean-up of description field.
- Added network model helper file.

Fixes #11989
This commit is contained in:
Noelia 2023-09-12 11:26:25 +02:00 committed by Noelia
parent 4f2f9646ec
commit 0a930fc9b1
9 changed files with 180 additions and 143 deletions

View File

@ -11,6 +11,10 @@ SplitView {
id: root id: root
Logs { id: logs } Logs { id: logs }
readonly property string ethereumName : "Ethereum Mainnet"
readonly property string optimismName : "Optimism"
readonly property string arbitrumName : "Arbitrum"
SplitView { SplitView {
orientation: Qt.Vertical orientation: Qt.Vertical
@ -41,7 +45,18 @@ SplitView {
multiSelection: multiSelectionCheckBox.checked multiSelection: multiSelectionCheckBox.checked
onToggleNetwork: logs.logEvent("onToggleNetwork: " + network.chainName) onToggleNetwork: {
logs.logEvent("onToggleNetwork: " + network.chainName)
if(network.chainName === root.ethereumName)
ethRadioBtn.checked = true
else if(network.chainName === root.optimismName)
optRadioBtn.checked = true
else if(network.chainName === root.arbitrumName)
arbRadioBtn.checked = true
}
} }
} }
} }
@ -80,37 +95,21 @@ SplitView {
RadioButton { RadioButton {
id: ethRadioBtn id: ethRadioBtn
text: "Ethereum Mainnet" text: root.ethereumName
onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.ethNet) onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.ethNet)
} }
RadioButton { RadioButton {
text: "Optimism" id: optRadioBtn
text: root.optimismName
onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.optimismNet) onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.optimismNet)
} }
RadioButton { RadioButton {
text: "Arbitrum" id: arbRadioBtn
text: root.arbitrumName
onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.arbitrumNet) onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.arbitrumNet)
} }
RadioButton {
text: "Hermez"
onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.hermezNet)
}
RadioButton {
text: "Testnet"
onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.testnetNet)
}
RadioButton {
text: "Custom"
onCheckedChanged: if(checked) networkFilter.setChain(NetworksModel.customNet)
}
RadioButton {
text: "Undefined"
onCheckedChanged: if(checked) networkFilter.setChain()
}
RadioButton {
text: "Not existing network id"
onCheckedChanged: if(checked) networkFilter.setChain(77)
}
} }
} }
} }

View File

@ -23,9 +23,9 @@ ListModel {
infiniteSupply: false, infiniteSupply: false,
transferable: true, transferable: true,
remoteSelfDestruct: false, remoteSelfDestruct: false,
chainId: 2, chainId: 3,
chainName: "Optimism", chainName: "Arbitrum",
chainIcon: ModelsData.networks.optimism, chainIcon: ModelsData.networks.arbitrum,
accountName: "Another account - generated", accountName: "Another account - generated",
tokenOwnersModel: root.emptyModel tokenOwnersModel: root.emptyModel
}, },
@ -43,9 +43,9 @@ ListModel {
infiniteSupply: true, infiniteSupply: true,
transferable: false, transferable: false,
remoteSelfDestruct: true, remoteSelfDestruct: true,
chainId: 2, chainId: 3,
chainName: "Optimism", chainName: "Arbitrum",
chainIcon: ModelsData.networks.optimism, chainIcon: ModelsData.networks.arbitrum,
accountName: "Another account - generated", accountName: "Another account - generated",
tokenOwnersModel: root.tokenOwnersModel tokenOwnersModel: root.tokenOwnersModel
}, },

View File

@ -12,6 +12,7 @@ import AppLayouts.Communities.helpers 1.0
import AppLayouts.Communities.layouts 1.0 import AppLayouts.Communities.layouts 1.0
import AppLayouts.Communities.popups 1.0 import AppLayouts.Communities.popups 1.0
import AppLayouts.Communities.views 1.0 import AppLayouts.Communities.views 1.0
import AppLayouts.Wallet.helpers 1.0
import shared.controls 1.0 import shared.controls 1.0
@ -271,15 +272,37 @@ StackView {
SettingsPage { SettingsPage {
id: newTokenPage id: newTokenPage
readonly property int ownerTokenChainId: SQUtils.ModelUtils.get(root.tokensModel, "privilegesLevel", Constants.TokenPrivilegesLevel.Owner).chainId ?? 0
readonly property var chainModel: NetworkModelHelpers.getLayerNetworkModelByChainId(root.layer1Networks,
root.layer2Networks,
ownerTokenChainId) ?? root.layer2Networks
readonly property int chainIndex: NetworkModelHelpers.getChainIndexByChainId(root.layer1Networks,
root.layer2Networks,
ownerTokenChainId)
readonly property string chainName: NetworkModelHelpers.getChainName(chainModel, chainIndex)
readonly property string chainIcon: NetworkModelHelpers.getChainIconUrl(chainModel, chainIndex)
property TokenObject asset: TokenObject{ property TokenObject asset: TokenObject{
type: Constants.TokenType.ERC20 type: Constants.TokenType.ERC20
multiplierIndex: 18 multiplierIndex: 18
// Minted tokens will use ALWAYS the same chain where the owner token was deployed.
chainId: newTokenPage.ownerTokenChainId
chainName: newTokenPage.chainName
chainIcon: newTokenPage.chainIcon
} }
property TokenObject collectible: TokenObject { property TokenObject collectible: TokenObject {
type: Constants.TokenType.ERC721 type: Constants.TokenType.ERC721
// Minted tokens will use ALWAYS the same chain where the owner token was deployed.
chainId: newTokenPage.ownerTokenChainId
chainName: newTokenPage.chainName
chainIcon: newTokenPage.chainIcon
} }
property bool isAssetView: false property bool isAssetView: false
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
property string referenceName: "" property string referenceName: ""
@ -343,8 +366,6 @@ StackView {
viewWidth: root.viewWidth viewWidth: root.viewWidth
layer1Networks: root.layer1Networks layer1Networks: root.layer1Networks
layer2Networks: root.layer2Networks layer2Networks: root.layer2Networks
enabledNetworks: root.enabledNetworks
allNetworks: root.allNetworks
accounts: root.accounts accounts: root.accounts
tokensModel: root.tokensModel tokensModel: root.tokensModel
tokensModelWallet: root.tokensModelWallet tokensModelWallet: root.tokensModelWallet

View File

@ -38,8 +38,6 @@ StatusScrollView {
// Network related properties: // Network related properties:
property var layer1Networks property var layer1Networks
property var layer2Networks property var layer2Networks
property var enabledNetworks
property var allNetworks
// Account expected roles: address, name, color, emoji, walletType // Account expected roles: address, name, color, emoji, walletType
property var accounts property var accounts
@ -77,10 +75,6 @@ StatusScrollView {
contentWidth: mainLayout.width contentWidth: mainLayout.width
contentHeight: mainLayout.height contentHeight: mainLayout.height
Component.onCompleted: {
networkSelector.setChain(root.token.chainId)
}
ColumnLayout { ColumnLayout {
id: mainLayout id: mainLayout
@ -122,8 +116,8 @@ StatusScrollView {
validationMode: root.validationMode validationMode: root.validationMode
minLengthValidator.errorMessage: qsTr("Please name your token name (use A-Z and 0-9, hyphens and underscores only)") minLengthValidator.errorMessage: qsTr("Please name your token name (use A-Z and 0-9, hyphens and underscores only)")
regexValidator.errorMessage: d.hasEmoji(text) ? regexValidator.errorMessage: d.hasEmoji(text) ?
qsTr("Your token name is too cool (use A-Z and 0-9, hyphens and underscores only)") : qsTr("Your token name is too cool (use A-Z and 0-9, hyphens and underscores only)") :
qsTr("Your token name contains invalid characters (use A-Z and 0-9, hyphens and underscores only)") qsTr("Your token name contains invalid characters (use A-Z and 0-9, hyphens and underscores only)")
extraValidator.validate: function (value) { extraValidator.validate: function (value) {
// If minting failed, we can retry same deployment, so same name allowed // If minting failed, we can retry same deployment, so same name allowed
const allowRepeatedName = root.token.deployState === Constants.ContractTransactionStatus.Failed const allowRepeatedName = root.token.deployState === Constants.ContractTransactionStatus.Failed
@ -145,7 +139,7 @@ StatusScrollView {
label: qsTr("Description") label: qsTr("Description")
text: root.token.description text: root.token.description
charLimit: 280 charLimit: 280
placeholderText: root.isAssetView ? qsTr("Describe your asset") : qsTr("Describe your collectible") placeholderText: root.isAssetView ? qsTr("Describe your asset (will be shown in hodlers wallets)") : qsTr("Describe your collectible (will be shown in hodlers wallets)")
input.multiline: true input.multiline: true
input.verticalAlignment: Qt.AlignTop input.verticalAlignment: Qt.AlignTop
input.placeholder.verticalAlignment: Qt.AlignTop input.placeholder.verticalAlignment: Qt.AlignTop
@ -169,7 +163,7 @@ StatusScrollView {
validationMode: root.validationMode validationMode: root.validationMode
minLengthValidator.errorMessage: qsTr("Please enter your token symbol (use A-Z only)") minLengthValidator.errorMessage: qsTr("Please enter your token symbol (use A-Z only)")
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your token symbol is too cool (use A-Z only)") : regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your token symbol is too cool (use A-Z only)") :
qsTr("Your token symbol contains invalid characters (use A-Z only)") qsTr("Your token symbol contains invalid characters (use A-Z only)")
regexValidator.regularExpression: Constants.regularExpressions.capitalOnly regexValidator.regularExpression: Constants.regularExpressions.capitalOnly
extraValidator.validate: function (value) { extraValidator.validate: function (value) {
// If minting failed, we can retry same deployment, so same symbol allowed // If minting failed, we can retry same deployment, so same symbol allowed
@ -180,10 +174,11 @@ StatusScrollView {
// Otherwise, no repeated names allowed: // Otherwise, no repeated names allowed:
return (!SQUtils.ModelUtils.contains(root.tokensModel, "symbol", symbolInput.text) && return (!SQUtils.ModelUtils.contains(root.tokensModel, "symbol", symbolInput.text) &&
!SQUtils.ModelUtils.contains(root.tokensModelWallet, "symbol", symbolInput.text)) !SQUtils.ModelUtils.contains(root.tokensModelWallet, "symbol", symbolInput.text))
} }
extraValidator.errorMessage: SQUtils.ModelUtils.contains(root.tokensModelWallet, "symbol", symbolInput.text) ? extraValidator.errorMessage: SQUtils.ModelUtils.contains(root.tokensModelWallet, "symbol", symbolInput.text) ?
qsTr("This token symbol is already in use") : qsTr("You have used this token symbol before") qsTr("This token symbol is already in use") :
qsTr("You have used this token symbol before")
onTextChanged: { onTextChanged: {
const cursorPos = input.edit.cursorPosition const cursorPos = input.edit.cursorPosition
@ -194,11 +189,53 @@ StatusScrollView {
} }
} }
CustomNetworkFilterRowComponent { StatusBaseText {
id: networkSelector text: qsTr("Network")
color: Theme.palette.directColor1
font.pixelSize: Theme.primaryTextFontSize
}
label: qsTr("Select network") Rectangle {
description: qsTr("The network on which this token will be minted") Layout.preferredHeight: 44
Layout.fillWidth: true
radius: 8
color: "transparent"
border.color: Theme.palette.directColor7
RowLayout {
id: networkRow
anchors.verticalCenter: parent.verticalCenter
spacing: 16
StatusSmartIdenticon {
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.current.padding
asset.height: 24
asset.width: asset.height
asset.isImage: true
asset.name: Style.svg(token.chainIcon)
active: true
visible: active
}
StatusBaseText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.rightMargin: Style.current.padding
font.pixelSize: 13
font.weight: Font.Medium
elide: Text.ElideRight
lineHeight: 24
lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter
text: token.chainName
color: Theme.palette.baseColor1
visible: !!text
}
}
} }
CustomSwitchRowComponent { CustomSwitchRowComponent {
@ -221,12 +258,12 @@ StatusScrollView {
visible: !unlimitedSupplyChecker.checked visible: !unlimitedSupplyChecker.checked
label: qsTr("Total finite supply") label: qsTr("Total finite supply")
text: SQUtils.AmountsArithmetic.toNumber(root.token.supply, text: SQUtils.AmountsArithmetic.toNumber(root.token.supply,
root.token.multiplierIndex) root.token.multiplierIndex)
placeholderText: qsTr("e.g. 300") placeholderText: qsTr("e.g. 300")
minLengthValidator.errorMessage: qsTr("Please enter a total finite supply") minLengthValidator.errorMessage: qsTr("Please enter a total finite supply")
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your total finite supply is too cool (use 0-9 only)") : regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your total finite supply is too cool (use 0-9 only)") :
qsTr("Your total finite supply contains invalid characters (use 0-9 only)") qsTr("Your total finite supply contains invalid characters (use 0-9 only)")
regexValidator.regularExpression: Constants.regularExpressions.numerical regexValidator.regularExpression: Constants.regularExpressions.numerical
extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 999999999 } extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 999999999 }
extraValidator.errorMessage: qsTr("Enter a number between 1 and 999,999,999") extraValidator.errorMessage: qsTr("Enter a number between 1 and 999,999,999")
@ -274,7 +311,7 @@ StatusScrollView {
validationMode: StatusInput.ValidationMode.Always validationMode: StatusInput.ValidationMode.Always
minLengthValidator.errorMessage: qsTr("Please enter how many decimals your token should have") minLengthValidator.errorMessage: qsTr("Please enter how many decimals your token should have")
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your decimal amount is too cool (use 0-9 only)") : regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your decimal amount is too cool (use 0-9 only)") :
qsTr("Your decimal amount contains invalid characters (use 0-9 only)") qsTr("Your decimal amount contains invalid characters (use 0-9 only)")
regexValidator.regularExpression: Constants.regularExpressions.numerical regexValidator.regularExpression: Constants.regularExpressions.numerical
extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 10 } extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 10 }
extraValidator.errorMessage: qsTr("Enter a number between 1 and 10") extraValidator.errorMessage: qsTr("Enter a number between 1 and 10")
@ -308,8 +345,8 @@ StatusScrollView {
function onAccountAddressChanged() { function onAccountAddressChanged() {
const idx = SQUtils.ModelUtils.indexOf( const idx = SQUtils.ModelUtils.indexOf(
feesBox.accountsSelector.model, "address", feesBox.accountsSelector.model, "address",
root.token.accountAddress) root.token.accountAddress)
feesBox.accountsSelector.currentIndex = idx feesBox.accountsSelector.currentIndex = idx
} }
@ -414,41 +451,4 @@ StatusScrollView {
id: switch_ id: switch_
} }
} }
component CustomNetworkFilterRowComponent: RowLayout {
id: networkComponent
property string label
property string description
function setChain(chainId) { netFilter.setChain(chainId) }
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
spacing: 32
CustomLabelDescriptionComponent {
label: networkComponent.label
description: networkComponent.description
}
NetworkFilter {
id: netFilter
Layout.preferredWidth: 160
allNetworks: root.allNetworks
layer1Networks: root.layer1Networks
layer2Networks: root.layer2Networks
enabledNetworks: root.enabledNetworks
multiSelection: false
onToggleNetwork: (network) => {
root.token.chainId = network.chainId
root.token.chainName = network.chainName
root.token.chainIcon = network.iconUrl
}
}
}
} }

View File

@ -141,9 +141,7 @@ StatusScrollView {
bottomPadding: Style.current.padding bottomPadding: Style.current.padding
} }
// TO BE REMOVED: It will be removed with the new fees panel
CustomLabelDescriptionComponent { CustomLabelDescriptionComponent {
label: qsTr("Select account") label: qsTr("Select account")
description: qsTr("This account will be where you receive your Owner token and will also be the account that pays the token minting gas fees.") description: qsTr("This account will be where you receive your Owner token and will also be the account that pays the token minting gas fees.")
} }
@ -198,7 +196,7 @@ StatusScrollView {
id: networkSelector id: networkSelector
label: qsTr("Select network") label: qsTr("Select network")
description: qsTr("The network on which these tokens will be minted.") description: qsTr("The network you select will be where all your communitys tokens reside. Once set, this setting cant be changed and tokens cant move to other networks.")
} }
FeesBox { FeesBox {
@ -217,30 +215,6 @@ StatusScrollView {
showAccountsSelector: false showAccountsSelector: false
} }
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.current.halfPadding
StatusIcon {
Layout.preferredWidth: d.iconSize
Layout.preferredHeight: d.iconSize
Layout.alignment: Qt.AlignTop
color: Theme.palette.baseColor1
icon: "info"
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.Wrap
font.pixelSize: Style.current.primaryTextFontSize
color: Theme.palette.baseColor1
lineHeight: 1.2
text: qsTr("Make sure youre happy with the blockchain network selected before minting these tokens as they cant be moved to a different network later.")
}
}
StatusButton { StatusButton {
Layout.preferredHeight: 44 Layout.preferredHeight: 44
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -308,8 +282,14 @@ StatusScrollView {
layer1Networks: root.layer1Networks layer1Networks: root.layer1Networks
layer2Networks: root.layer2Networks layer2Networks: root.layer2Networks
enabledNetworks: root.enabledNetworks enabledNetworks: root.enabledNetworks
multiSelection: false multiSelection: false
control.topPadding: 10
control.background: Rectangle {
height: 44
radius: 8
color: "transparent"
border.color: Theme.palette.directColor7
}
onToggleNetwork: (network) => { onToggleNetwork: (network) => {
// Set Owner Token network properties: // Set Owner Token network properties:

View File

@ -169,8 +169,8 @@ StatusScrollView {
delegate: StatusListItem { delegate: StatusListItem {
height: 64 height: 64
width: mainLayout.width width: mainLayout.width
title: model.name title: model.name ?? ""
subTitle: model.symbol subTitle: model.symbol ?? ""
asset.name: model.image ? model.image : "" asset.name: model.image ? model.image : ""
asset.isImage: true asset.isImage: true
components: [ components: [

View File

@ -7,6 +7,8 @@ import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import AppLayouts.Wallet.helpers 1.0
import utils 1.0 import utils 1.0
import "../views" import "../views"
@ -29,27 +31,12 @@ StatusComboBox {
function setChain(chainId) { function setChain(chainId) {
if(!multiSelection && !!d.currentModel && d.currentModel.count > 0) { if(!multiSelection && !!d.currentModel && d.currentModel.count > 0) {
// Find given chain id: d.currentModel = NetworkModelHelpers.getLayerNetworkModelByChainId(root.layer1Networks,
var chainIdExists = false root.layer2Networks,
if(chainId) { chainId) ?? root.layer2Networks
if(!!root.layer1Networks && ModelUtils.contains(root.layer1Networks, "chainId", chainId)) { d.currentIndex = NetworkModelHelpers.getChainIndexByChainId(root.layer1Networks,
d.currentModel = root.layer1Networks root.layer2Networks,
chainIdExists = true chainId)
} else if(!!root.layer2Networks && ModelUtils.contains(root.layer2Networks, "chainId", chainId)) {
d.currentModel = root.layer2Networks
chainIdExists = true
}
}
// Set chain:
if(chainIdExists) {
d.currentIndex = ModelUtils.indexOf(d.currentModel, "chainId", chainId)
}
else {
// Default value if not specified
d.currentModel = root.layer2Networks
d.currentIndex = 0
}
// Notify change: // Notify change:
root.toggleNetwork(ModelUtils.get(d.currentModel, d.currentIndex)) root.toggleNetwork(ModelUtils.get(d.currentModel, d.currentIndex))
@ -59,8 +46,8 @@ StatusComboBox {
QtObject { QtObject {
id: d id: d
readonly property string selectedChainName: ModelUtils.get(d.currentModel, d.currentIndex, "chainName") ?? "" readonly property string selectedChainName: NetworkModelHelpers.getChainName(d.currentModel, d.currentIndex)
readonly property string selectedIconUrl: ModelUtils.get(d.currentModel, d.currentIndex, "iconUrl") ?? "" readonly property string selectedIconUrl: NetworkModelHelpers.getChainIconUrl(d.currentModel, d.currentIndex)
readonly property bool allSelected: (!!root.enabledNetworks && !!root.allNetworks) ? root.enabledNetworks.count === root.allNetworks.count : readonly property bool allSelected: (!!root.enabledNetworks && !!root.allNetworks) ? root.enabledNetworks.count === root.allNetworks.count :
false false
readonly property bool noneSelected: (!!root.enabledNetworks) ? root.enabledNetworks.count === 0 : false readonly property bool noneSelected: (!!root.enabledNetworks) ? root.enabledNetworks.count === 0 : false

View File

@ -0,0 +1,49 @@
pragma Singleton
import QtQml 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Internal 0.1 as Internal
import AppLayouts.Communities.controls 1.0
QtObject {
// Given a specific network model and an index inside the model, it gets the chain name.
function getChainName(model, index) {
return ModelUtils.get(model, index, "chainName") ?? ""
}
// Given a specific network model and an index inside the model, it gets the chain icon url.
function getChainIconUrl(model, index) {
return ModelUtils.get(model, index, "iconUrl") ?? ""
}
// Given a layer1 network model and layer2 network model, it looks for the provided chainId and returns
// the layer network model that contains the specific chain. If not found, returns undefined.
function getLayerNetworkModelByChainId(layer1NetworksModel, layer2NetworksModel, chainId) {
if(chainId) {
if(!!layer1NetworksModel && ModelUtils.contains(layer1NetworksModel, "chainId", chainId))
return layer1NetworksModel
else if(!!layer2NetworksModel && ModelUtils.contains(layer2NetworksModel, "chainId", chainId))
return layer2NetworksModel
}
// Default value if chainId is not part of any provided layer network model
return undefined
}
// Given a layer1 network model and layer2 network model, it looks for the provided chainId and returns
// the index of the the specific chain. If not found, returns 0 value.
function getChainIndexByChainId(layer1NetworksModel, layer2NetworksModel, chainId) {
const currentModel = getLayerNetworkModelByChainId(layer1NetworksModel, layer2NetworksModel, chainId)
if(!!currentModel)
return ModelUtils.indexOf(currentModel, "chainId", chainId)
// Default value if no model specified
return 0
}
}

View File

@ -0,0 +1 @@
singleton NetworkModelHelpers 1.0 NetworkModelHelpers.qml