diff --git a/src/app/modules/main/communities/tokens/io_interface.nim b/src/app/modules/main/communities/tokens/io_interface.nim index 50cc7712ed2..f8a04a6911b 100644 --- a/src/app/modules/main/communities/tokens/io_interface.nim +++ b/src/app/modules/main/communities/tokens/io_interface.nim @@ -21,14 +21,14 @@ method computeAirdropFee*(self: AccessInterface, communityId: string, tokensJson method selfDestructCollectibles*(self: AccessInterface, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} = raise newException(ValueError, "No implementation available") -method burnTokens*(self: AccessInterface, communityId: string, contractUniqueKey: string, amount: float64) {.base.} = +method burnTokens*(self: AccessInterface, communityId: string, contractUniqueKey: string, amount: string) {.base.} = raise newException(ValueError, "No implementation available") -method deployCollectibles*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: float64, infiniteSupply: bool, transferable: bool, +method deployCollectibles*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: string, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, imageCropInfoJson: string) {.base.} = raise newException(ValueError, "No implementation available") -method deployAssets*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: float64, infiniteSupply: bool, decimals: int, +method deployAssets*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: string, infiniteSupply: bool, decimals: int, chainId: int, imageCropInfoJson: string) {.base.} = raise newException(ValueError, "No implementation available") @@ -44,7 +44,7 @@ method computeDeployFee*(self: AccessInterface, chainId: int, accountAddress: st method computeSelfDestructFee*(self: AccessInterface, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} = raise newException(ValueError, "No implementation available") -method computeBurnFee*(self: AccessInterface, contractUniqueKey: string, amount: float64) {.base.} = +method computeBurnFee*(self: AccessInterface, contractUniqueKey: string, amount: string) {.base.} = raise newException(ValueError, "No implementation available") method onDeployFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.base.} = 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 76b1f193d6b..2964da99cb8 100644 --- a/src/app/modules/main/communities/tokens/models/token_model.nim +++ b/src/app/modules/main/communities/tokens/models/token_model.nim @@ -2,6 +2,7 @@ import NimQml, Tables, strformat, sequtils, stint import token_item import token_owners_item import token_owners_model +import ../../../../../../app_service/service/community/dto/community import ../../../../../../app_service/service/community_tokens/dto/community_token import ../../../../../../app_service/common/utils import ../../../../../../app_service/common/types @@ -30,6 +31,7 @@ type Decimals BurnState RemotelyDestructState + MultiplierIndex QtObject: type TokenModel* = ref object of QAbstractListModel @@ -170,6 +172,7 @@ QtObject: ModelRole.Decimals.int:"decimals", ModelRole.BurnState.int:"burnState", ModelRole.RemotelyDestructState.int:"remotelyDestructState", + ModelRole.MultiplierIndex.int:"multiplierIndex", }.toTable method data(self: TokenModel, index: QModelIndex, role: int): QVariant = @@ -194,7 +197,7 @@ QtObject: result = newQVariant(item.tokenDto.description) of ModelRole.Supply: # we need to present maxSupply - destructedAmount - result = newQVariant(supplyByType(item.tokenDto.supply - item.destructedAmount, item.tokenDto.tokenType)) + result = newQVariant((item.tokenDto.supply - item.destructedAmount).toString(10)) of ModelRole.InfiniteSupply: result = newQVariant(item.tokenDto.infiniteSupply) of ModelRole.Transferable: @@ -216,7 +219,7 @@ QtObject: of ModelRole.AccountName: result = newQVariant(item.accountName) of ModelRole.RemainingSupply: - result = newQVariant(supplyByType(item.remainingSupply, item.tokenDto.tokenType)) + result = newQVariant(item.remainingSupply.toString(10)) of ModelRole.Decimals: result = newQVariant(item.tokenDto.decimals) of ModelRole.BurnState: @@ -224,9 +227,11 @@ QtObject: of ModelRole.RemotelyDestructState: let destructStatus = if len(item.remoteDestructedAddresses) > 0: ContractTransactionStatus.InProgress.int else: ContractTransactionStatus.Completed.int result = newQVariant(destructStatus) + of ModelRole.MultiplierIndex: + result = newQVariant(if item.tokenDto.tokenType == TokenType.ERC20: 18 else: 0) proc `$`*(self: TokenModel): string = for i in 0 ..< self.items.len: result &= fmt"""TokenModel: [{i}]:({$self.items[i]}) - """ \ No newline at end of file + """ diff --git a/src/app/modules/main/communities/tokens/module.nim b/src/app/modules/main/communities/tokens/module.nim index 64ce824297f..9ff5659de2e 100644 --- a/src/app/modules/main/communities/tokens/module.nim +++ b/src/app/modules/main/communities/tokens/module.nim @@ -86,29 +86,17 @@ proc authenticate(self: Module) = else: self.controller.authenticateUser() -# for collectibles conversion is: "1" -> Uint256(1) -# for assets amount is converted to basic units (wei-like): "1.5" -> Uint256(1500000000000000000) -proc convertAmountByTokenType(self: Module, tokenType: TokenType, amount: float64): Uint256 = - const decimals = 18 - case tokenType - of TokenType.ERC721: - return stint.parse($amount.int, Uint256) - of TokenType.ERC20: - return conversion.eth2Wei(amount, decimals) - else: - error "Converting amount - unknown token type", tokenType=tokenType - proc getTokenAndAmountList(self: Module, communityId: string, tokensJsonString: string): seq[CommunityTokenAndAmount] = try: let tokensJson = tokensJsonString.parseJson for token in tokensJson: let contractUniqueKey = token["contractUniqueKey"].getStr let tokenDto = self.controller.findContractByUniqueId(contractUniqueKey) - let amountStr = token["amount"].getFloat + let amountStr = token["amount"].getStr if tokenDto.tokenType == TokenType.Unknown: error "Can't find token for community", contractUniqueKey=contractUniqueKey return @[] - result.add(CommunityTokenAndAmount(communityToken: tokenDto, amount: self.convertAmountByTokenType(tokenDto.tokenType, amountStr))) + result.add(CommunityTokenAndAmount(communityToken: tokenDto, amount: amountStr.parse(Uint256))) except Exception as e: error "Error getTokenAndAmountList", msg = e.msg @@ -141,22 +129,22 @@ method selfDestructCollectibles*(self: Module, communityId: string, collectibles self.tempContractAction = ContractAction.SelfDestruct self.authenticate() -method burnTokens*(self: Module, communityId: string, contractUniqueKey: string, amount: float64) = +method burnTokens*(self: Module, communityId: string, contractUniqueKey: string, amount: string) = let tokenDto = self.controller.findContractByUniqueId(contractUniqueKey) self.tempCommunityId = communityId self.tempContractUniqueKey = contractUniqueKey - self.tempAmount = self.convertAmountByTokenType(tokenDto.tokenType, amount) + self.tempAmount = amount.parse(Uint256) self.tempContractAction = ContractAction.Burn self.authenticate() method deployCollectibles*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string, - supply: float64, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, imageCropInfoJson: string) = + supply: string, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, imageCropInfoJson: string) = self.tempAddressFrom = fromAddress self.tempCommunityId = communityId self.tempChainId = chainId self.tempDeploymentParams.name = name self.tempDeploymentParams.symbol = symbol - self.tempDeploymentParams.supply = self.convertAmountByTokenType(TokenType.ERC721, supply) + self.tempDeploymentParams.supply = supply.parse(Uint256) self.tempDeploymentParams.infiniteSupply = infiniteSupply self.tempDeploymentParams.transferable = transferable self.tempDeploymentParams.remoteSelfDestruct = selfDestruct @@ -167,14 +155,14 @@ method deployCollectibles*(self: Module, communityId: string, fromAddress: strin self.tempContractAction = ContractAction.Deploy self.authenticate() -method deployAssets*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: float64, infiniteSupply: bool, decimals: int, +method deployAssets*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: string, infiniteSupply: bool, decimals: int, chainId: int, imageCropInfoJson: string) = self.tempAddressFrom = fromAddress self.tempCommunityId = communityId self.tempChainId = chainId self.tempDeploymentParams.name = name self.tempDeploymentParams.symbol = symbol - self.tempDeploymentParams.supply = self.convertAmountByTokenType(TokenType.ERC20, supply) + self.tempDeploymentParams.supply = supply.parse(Uint256) self.tempDeploymentParams.infiniteSupply = infiniteSupply self.tempDeploymentParams.decimals = decimals self.tempDeploymentParams.tokenUri = utl.changeCommunityKeyCompression(communityId) & "/" @@ -221,9 +209,9 @@ method computeSelfDestructFee*(self: Module, collectiblesToBurnJsonString: strin let walletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString) self.controller.computeSelfDestructFee(walletAndAmountList, contractUniqueKey) -method computeBurnFee*(self: Module, contractUniqueKey: string, amount: float64) = +method computeBurnFee*(self: Module, contractUniqueKey: string, amount: string) = let tokenDto = self.controller.findContractByUniqueId(contractUniqueKey) - self.controller.computeBurnFee(contractUniqueKey, self.convertAmountByTokenType(tokenDto.tokenType, amount)) + self.controller.computeBurnFee(contractUniqueKey, amount.parse(Uint256)) proc createUrl(self: Module, chainId: int, transactionHash: string): string = let network = self.controller.getNetwork(chainId) diff --git a/src/app/modules/main/communities/tokens/view.nim b/src/app/modules/main/communities/tokens/view.nim index 62e4551d023..12a560540ed 100644 --- a/src/app/modules/main/communities/tokens/view.nim +++ b/src/app/modules/main/communities/tokens/view.nim @@ -21,10 +21,10 @@ QtObject: result.QObject.setup result.communityTokensModule = communityTokensModule - proc deployCollectible*(self: View, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: float, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, imageCropInfoJson: string) {.slot.} = + proc deployCollectible*(self: View, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: string, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, imageCropInfoJson: string) {.slot.} = self.communityTokensModule.deployCollectibles(communityId, fromAddress, name, symbol, description, supply, infiniteSupply, transferable, selfDestruct, chainId, imageCropInfoJson) - proc deployAssets*(self: View, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: float, infiniteSupply: bool, decimals: int, chainId: int, imageCropInfoJson: string) {.slot.} = + proc deployAssets*(self: View, communityId: string, fromAddress: string, name: string, symbol: string, description: string, supply: string, infiniteSupply: bool, decimals: int, chainId: int, imageCropInfoJson: string) {.slot.} = self.communityTokensModule.deployAssets(communityId, fromAddress, name, symbol, description, supply, infiniteSupply, decimals, chainId, imageCropInfoJson) proc removeCommunityToken*(self: View, communityId: string, chainId: int, address: string) {.slot.} = @@ -39,7 +39,7 @@ QtObject: proc selfDestructCollectibles*(self: View, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} = self.communityTokensModule.selfDestructCollectibles(communityId, collectiblesToBurnJsonString, contractUniqueKey) - proc burnTokens*(self: View, communityId: string, contractUniqueKey: string, amount: float) {.slot.} = + proc burnTokens*(self: View, communityId: string, contractUniqueKey: string, amount: string) {.slot.} = self.communityTokensModule.burnTokens(communityId, contractUniqueKey, amount) proc deployFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.} @@ -53,7 +53,7 @@ QtObject: proc computeSelfDestructFee*(self: View, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} = self.communityTokensModule.computeSelfDestructFee(collectiblesToBurnJsonString, contractUniqueKey) - proc computeBurnFee*(self: View, contractUniqueKey: string, amount: float) {.slot.} = + proc computeBurnFee*(self: View, contractUniqueKey: string, amount: string) {.slot.} = self.communityTokensModule.computeBurnFee(contractUniqueKey, amount) proc updateDeployFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) = diff --git a/src/app_service/common/conversion.nim b/src/app_service/common/conversion.nim index 5c57f643d59..be21eaa5629 100644 --- a/src/app_service/common/conversion.nim +++ b/src/app_service/common/conversion.nim @@ -45,6 +45,7 @@ proc toUInt256*(flt: float): UInt256 = proc toUInt64*(flt: float): StUInt[64] = toStUInt(flt, StUInt[64]) +# This method may introduce distortions and should be avoided if possible. proc eth2Wei*(eth: float, decimals: int = 18): UInt256 = let weiValue = eth * parseFloat(alignLeft("1", decimals + 1, '0')) weiValue.toUInt256 diff --git a/src/app_service/service/community_tokens/dto/community_token.nim b/src/app_service/service/community_tokens/dto/community_token.nim index 17eb3a49fbc..e79868598e1 100644 --- a/src/app_service/service/community_tokens/dto/community_token.nim +++ b/src/app_service/service/community_tokens/dto/community_token.nim @@ -74,14 +74,3 @@ proc toCommunityTokenDto*(jsonObj: JsonNode): CommunityTokenDto = proc parseCommunityTokens*(response: RpcResponse[JsonNode]): seq[CommunityTokenDto] = result = map(response.result.getElems(), proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto()) - -proc supplyByType*(supply: Uint256, tokenType: TokenType): float64 = - try: - var eths: string - if tokenType == TokenType.ERC20: - eths = wei2Eth(supply, 18) - else: - eths = supply.toString(10) - return parseFloat(eths) - except Exception as e: - error "Error parsing supply by type ", msg=e.msg, supply=supply, tokenType=tokenType \ No newline at end of file diff --git a/storybook/pages/AirdropsSettingsPanelPage.qml b/storybook/pages/AirdropsSettingsPanelPage.qml index aec42d8c3aa..6cb9fa100ab 100644 --- a/storybook/pages/AirdropsSettingsPanelPage.qml +++ b/storybook/pages/AirdropsSettingsPanelPage.qml @@ -104,6 +104,8 @@ SplitView { assetsModel: AssetsModel {} collectiblesModel: ListModel {} + accountsModel: ListModel {} + CollectiblesModel { id: collectiblesModel } @@ -118,6 +120,10 @@ SplitView { name: "supply" expression: ((model.index + 1) * 115).toString() }, + ExpressionRole { + name: "multiplierIndex" + expression: 0 + }, ExpressionRole { name: "infiniteSupply" expression: !(model.index % 4) @@ -158,7 +164,12 @@ SplitView { proxyRoles: [ ExpressionRole { name: "supply" - expression: ((model.index + 1) * 258).toString() + expression: ((model.index + 1) * 584).toString() + + "0".repeat(18) + }, + ExpressionRole { + name: "multiplierIndex" + expression: 18 }, ExpressionRole { name: "infiniteSupply" diff --git a/storybook/pages/HoldingsDropdownPage.qml b/storybook/pages/HoldingsDropdownPage.qml index 6e4bf708ebb..9e1be7cfdc6 100644 --- a/storybook/pages/HoldingsDropdownPage.qml +++ b/storybook/pages/HoldingsDropdownPage.qml @@ -66,7 +66,11 @@ SplitView { proxyRoles: [ ExpressionRole { name: "supply" - expression: (model.index + 1) * 115 + expression: ((model.index + 1) * 115).toString() + }, + ExpressionRole { + name: "multiplierIndex" + expression: 0 }, ExpressionRole { name: "infiniteSupply" @@ -104,7 +108,12 @@ SplitView { proxyRoles: [ ExpressionRole { name: "supply" - expression: (model.index + 1) * 584 + expression: ((model.index + 1) * 584).toString() + + "0".repeat(18) + }, + ExpressionRole { + name: "multiplierIndex" + expression: 18 }, ExpressionRole { name: "infiniteSupply" diff --git a/storybook/pages/InlineNetworksComboBoxPage.qml b/storybook/pages/InlineNetworksComboBoxPage.qml index 8731a0e7b3a..1c31fdef414 100644 --- a/storybook/pages/InlineNetworksComboBoxPage.qml +++ b/storybook/pages/InlineNetworksComboBoxPage.qml @@ -13,20 +13,30 @@ Item { { name: "Optimism", icon: Style.svg(ModelsData.networks.optimism), - amount: 300, + amount: "300", + multiplierIndex: 0, infiniteAmount: false }, { name: "Arbitrum", icon: Style.svg(ModelsData.networks.arbitrum), - amount: 400, + amount: "400000", + multiplierIndex: 3, infiniteAmount: false }, { name: "Hermez", icon: Style.svg(ModelsData.networks.hermez), - amount: 0, + amount: "0", + multiplierIndex: 0, infiniteAmount: true + }, + { + name: "Ethereum", + icon: Style.svg(ModelsData.networks.ethereum), + amount: "12" + "0".repeat(18), + multiplierIndex: 18, + infiniteAmount: false } ] @@ -78,6 +88,10 @@ Item { Layout.alignment: Qt.AlignHCenter text: `current amount: ${comboBox.currentAmount}` } + Label { + Layout.alignment: Qt.AlignHCenter + text: `current multiplier index: ${comboBox.currentMultiplierIndex}` + } Label { Layout.alignment: Qt.AlignHCenter text: `current amount infinite: ${comboBox.currentInfiniteAmount}` diff --git a/storybook/pages/TokenPanelPage.qml b/storybook/pages/TokenPanelPage.qml index caf3c34e469..78ca30cfa41 100644 --- a/storybook/pages/TokenPanelPage.qml +++ b/storybook/pages/TokenPanelPage.qml @@ -2,6 +2,8 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import Qt.labs.settings 1.0 + import Models 1.0 import Storybook 1.0 import utils 1.0 @@ -20,20 +22,30 @@ SplitView { { name: "Optimism", icon: Style.svg(ModelsData.networks.optimism), - amount: 300, + amount: "300", + multiplierIndex: 0, infiniteAmount: false }, { name: "Arbitrum", icon: Style.svg(ModelsData.networks.arbitrum), - amount: 400, + amount: "400000", + multiplierIndex: 3, infiniteAmount: false }, { name: "Hermez", icon: Style.svg(ModelsData.networks.hermez), - amount: 500, + amount: "0", + multiplierIndex: 0, infiniteAmount: true + }, + { + name: "Ethereum", + icon: Style.svg(ModelsData.networks.ethereum), + amount: "12" + "0".repeat(18), + multiplierIndex: 18, + infiniteAmount: false } ] @@ -119,8 +131,17 @@ SplitView { text: "∞" } } + + Label { + text: "amount: " + tokenPanel.amount + } } } + + Settings { + property alias networksModelCheckBoxChecked: + networksModelCheckBox.checked + } } // category: Panels diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml b/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml index 4ed50875195..e35a75c6662 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml @@ -40,13 +40,14 @@ QtObject { console.assert(!isNaN(number) && Number.isInteger(multiplier) && multiplier >= 0) const amount = new Big.Big(number).times(10 ** multiplier) - console.assert(amount.eq(amount.round())) + // TODO: restore assert when permissions handled as bigints + // console.assert(amount.eq(amount.round())) return amount } /*! \qmlmethod AmountsArithmetic::toNumber(amount, multiplier = 0) - \brief Converts an amount to a java script number. + \brief Converts an amount (in form of amount object or string) to a java script number. This operation may result in loss of precision. Because of that it should be used only to display a value in the user interface, but requires @@ -57,10 +58,16 @@ QtObject { \qml console.log(AmountsArithmetic.toNumber( AmountsArithmetic.fromString("123456789123456789123"))) // 123456789123456800000 + console.log(AmountsArithmetic.toNumber("123456789123456789123")) // 123456789123456800000 \endqml */ function toNumber(amount, multiplier = 0) { console.assert(Number.isInteger(multiplier) && multiplier >= 0) + + if (typeof amount === "string") + amount = fromString(amount) + + console.assert(amount instanceof Big.Big) return amount.div(10 ** multiplier).toNumber() } @@ -84,7 +91,8 @@ QtObject { function fromString(numStr) { console.assert(typeof numStr === "string") const amount = new Big.Big(numStr) - console.assert(amount.eq(amount.round())) + // TODO: restore assert when permissions handled as bigints + //console.assert(amount.eq(amount.round())) return amount } @@ -105,6 +113,16 @@ QtObject { return amount.times(multiplier) } + /*! + \qmlmethod AmountsArithmetic::div(divident, divisor) + \brief Returns a Big number whose value is the value of divident divided by divisor. + */ + function div(divident, divisor) { + console.assert(divident instanceof Big.Big) + console.assert(divisor instanceof Big.Big) + return divident.div(divisor) + } + /*! \qmlmethod AmountsArithmetic::cmp(amount1, amount2) \brief Compares two amounts. diff --git a/ui/app/AppLayouts/Communities/controls/InlineNetworksComboBox.qml b/ui/app/AppLayouts/Communities/controls/InlineNetworksComboBox.qml index 75eb377dc72..18c577d6e37 100644 --- a/ui/app/AppLayouts/Communities/controls/InlineNetworksComboBox.qml +++ b/ui/app/AppLayouts/Communities/controls/InlineNetworksComboBox.qml @@ -5,6 +5,7 @@ import QtQml 2.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 +import StatusQ.Core.Utils 0.1 as SQUtils import SortFilterProxyModel 0.2 @@ -13,6 +14,7 @@ StatusComboBox { readonly property string currentName: control.currentText readonly property alias currentAmount: instantiator.amount + readonly property alias currentMultiplierIndex: instantiator.multiplierIndex readonly property alias currentInfiniteAmount: instantiator.infiniteAmount readonly property alias currentIcon: instantiator.icon @@ -49,6 +51,10 @@ StatusComboBox { readonly property int iconSize: 32 readonly property string infinitySymbol: "∞" + + function amountText(amount, multiplierIndex) { + return SQUtils.AmountsArithmetic.toNumber(amount, multiplierIndex) + } } component CustomText: StatusBaseText { @@ -94,7 +100,8 @@ StatusComboBox { id: instantiator property string icon - property int amount + property string amount + property int multiplierIndex property bool infiniteAmount model: SortFilterProxyModel { @@ -109,6 +116,7 @@ StatusComboBox { readonly property list bindings: [ Bind { property: "icon"; value: model.icon }, Bind { property: "amount"; value: model.amount }, + Bind { property: "multiplierIndex"; value: model.multiplierIndex }, Bind { property: "infiniteAmount"; value: model.infiniteAmount } ] } @@ -118,10 +126,17 @@ StatusComboBox { title: root.control.displayText iconSource: instantiator.icon - amount: !d.oneItem - ? (instantiator.infiniteAmount ? d.infinitySymbol - : instantiator.amount) - : "" + amount: { + if (d.oneItem || !instantiator.amount) + return "" + + if (instantiator.infiniteAmount) + return d.infinitySymbol + + return d.amountText(instantiator.amount, + instantiator.multiplierIndex) + } + cursorShape: d.oneItem ? Qt.ArrowCursor : Qt.PointingHandCursor onClicked: { @@ -135,7 +150,9 @@ StatusComboBox { delegate: DelegateItem { title: model.name iconSource: model.icon - amount: model.infiniteAmount ? d.infinitySymbol : model.amount + amount: model.infiniteAmount + ? d.infinitySymbol + : d.amountText(model.amount, model.multiplierIndex) width: root.width height: root.height diff --git a/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml b/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml index f5a6b0c9954..14424f1469d 100644 --- a/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml +++ b/ui/app/AppLayouts/Communities/controls/ListDropdownContent.qml @@ -1,12 +1,13 @@ -import QtQuick 2.13 -import QtQuick.Layouts 1.14 -import QtQuick.Controls 2.13 -import QtGraphicalEffects 1.13 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtGraphicalEffects 1.15 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 -import StatusQ.Controls 0.1 -import StatusQ.Components 0.1 +import StatusQ.Core.Utils 0.1 as SQUtils import utils 1.0 @@ -96,7 +97,17 @@ StatusListView { iconSource: model.iconSource ?? "" showSubItemsIcon: !!model.subItems && model.subItems.count > 0 selected: root.checkedKeys.includes(model.key) - amount: !!model.infiniteSupply ? "∞" : model.supply ?? "" + amount: { + if (model.supply === undefined + || model.multiplierIndex === undefined) + return "" + + if (model.infiniteSupply) + return "∞" + + return SQUtils.AmountsArithmetic.toNumber( + model.supply, model.multiplierIndex) + } onItemClicked: root.itemClicked( model.key, name, shortName, iconSource, model.subItems) diff --git a/ui/app/AppLayouts/Communities/controls/TokenPanel.qml b/ui/app/AppLayouts/Communities/controls/TokenPanel.qml index 189950b9112..b39e31efc70 100644 --- a/ui/app/AppLayouts/Communities/controls/TokenPanel.qml +++ b/ui/app/AppLayouts/Communities/controls/TokenPanel.qml @@ -1,5 +1,5 @@ -import QtQuick 2.14 -import QtQuick.Layouts 1.14 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -21,6 +21,7 @@ ColumnLayout { property alias tokenImage: item.iconSource property alias amountText: amountInput.text property alias amount: amountInput.amount + property alias multiplierIndex: amountInput.multiplierIndex property alias tokenCategoryText: tokenLabel.text property alias networkLabelText: d.networkLabelText property alias addOrUpdateButtonEnabled: addOrUpdateButton.enabled @@ -33,8 +34,9 @@ ColumnLayout { signal updateClicked signal removeClicked - function setAmount(amount) { - amountInput.setAmount(amount) + function setAmount(amount, multiplierIndex = 0) { + console.assert(typeof amount === "string") + amountInput.setAmount(amount, multiplierIndex) } QtObject { @@ -86,7 +88,10 @@ ColumnLayout { spacing: 10 property alias currentAmount: inlineNetworksComboBox.currentAmount - property alias currentInfiniteAmount: inlineNetworksComboBox.currentInfiniteAmount + property alias currentMultiplierIndex: + inlineNetworksComboBox.currentMultiplierIndex + property alias currentInfiniteAmount: + inlineNetworksComboBox.currentInfiniteAmount CustomText { id: networkLabel @@ -124,10 +129,14 @@ ColumnLayout { !networksComboBoxLoader.item.currentInfiniteAmount maximumAmount: !!networksComboBoxLoader.item - ? networksComboBoxLoader.item.currentAmount : 0 + ? networksComboBoxLoader.item.currentAmount : "0" + + multiplierIndex: !!networksComboBoxLoader.item + ? networksComboBoxLoader.item.currentMultiplierIndex : 0 onKeyPressed: { - if(!addOrUpdateButton.enabled) return + if(!addOrUpdateButton.enabled) + return if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return) addOrUpdateButton.clicked() @@ -145,7 +154,8 @@ ColumnLayout { StatusButton { id: addOrUpdateButton - text: (root.mode === HoldingTypes.Mode.Add) ? qsTr("Add") : qsTr("Update") + text: root.mode === HoldingTypes.Mode.Add ? qsTr("Add") + : qsTr("Update") Layout.preferredHeight: d.defaultHeight Layout.topMargin: d.defaultSpacing Layout.fillWidth: true diff --git a/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml b/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml index af8d0f1e173..778dd4678cd 100644 --- a/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml +++ b/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml @@ -74,6 +74,9 @@ QtObject { } function setHoldingsTextFormat(type, name, amount) { + if (typeof amount === "string") + amount = AmountsArithmetic.toNumber(AmountsArithmetic.fromString(amount)) + switch (type) { case HoldingTypes.Type.Asset: return `${LocaleUtils.numberToLocaleString(amount)} ${name}` diff --git a/ui/app/AppLayouts/Communities/helpers/TokenObject.qml b/ui/app/AppLayouts/Communities/helpers/TokenObject.qml index 1bd1b2d445e..bfb2d38e8ed 100644 --- a/ui/app/AppLayouts/Communities/helpers/TokenObject.qml +++ b/ui/app/AppLayouts/Communities/helpers/TokenObject.qml @@ -23,8 +23,9 @@ QtObject { property string symbol property string description property bool infiniteSupply: true - property int supply: 1 - property int remainingTokens: supply + property string supply: "1" + property string remainingTokens: supply + property int multiplierIndex: 0 // Artwork related properties: property url artworkSource diff --git a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml index a3f985e81de..c4e221646b7 100644 --- a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml @@ -66,13 +66,13 @@ StackView { signal mintOwnerToken(var ownerToken, var tMasterToken) signal deployFeesRequested(int chainId, string accountAddress, int tokenType) - signal burnFeesRequested(string tokenKey, int amount, string accountAddress) + signal burnFeesRequested(string tokenKey, string amount, string accountAddress) signal signRemoteDestructTransactionOpened(var remotelyDestructTokensList, // [key , amount] string tokenKey) signal remotelyDestructCollectibles(var remotelyDestructTokensList, // [key , amount] string tokenKey) - signal signBurnTransactionOpened(string tokenKey, int amount, string accountAddress) - signal burnToken(string tokenKey, int amount, string accountAddress) + signal signBurnTransactionOpened(string tokenKey, string amount, string accountAddress) + signal burnToken(string tokenKey, string amount, string accountAddress) signal airdropToken(string tokenKey, int type, var addresses) signal deleteToken(string tokenKey) @@ -294,6 +294,7 @@ StackView { property TokenObject asset: TokenObject{ type: Constants.TokenType.ERC20 + multiplierIndex: 18 } property TokenObject collectible: TokenObject { @@ -642,7 +643,7 @@ StackView { // helper properties to pass data through popups property var remotelyDestructTokensList - property int burnAmount + property string burnAmount property string accountAddress RemotelyDestructPopup { @@ -718,6 +719,7 @@ StackView { communityName: root.communityName tokenName: footer.token.name remainingTokens: footer.token.remainingTokens + multiplierIndex: footer.token.multiplierIndex tokenSource: footer.token.artworkSource chainName: footer.token.chainName @@ -801,6 +803,7 @@ StackView { token.type: model.tokenType token.burnState: model.burnState token.remotelyDestructState: model.remotelyDestructState + token.multiplierIndex: model.multiplierIndex // TODO: Backend //token.accountAddress: model.accountAddress } diff --git a/ui/app/AppLayouts/Communities/panels/TokenInfoPanel.qml b/ui/app/AppLayouts/Communities/panels/TokenInfoPanel.qml index 927347a665a..13d7fe96db6 100644 --- a/ui/app/AppLayouts/Communities/panels/TokenInfoPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/TokenInfoPanel.qml @@ -177,7 +177,11 @@ Control { id: totalbox label: qsTr("Total") - value: token.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(token.supply) + value: token.infiniteSupply + ? d.infiniteSymbol + : LocaleUtils.numberToLocaleString( + StatusQUtils.AmountsArithmetic.toNumber(token.supply, + token.multiplierIndex)) isLoading: !token.infiniteSupply && ((!root.isAssetPanel && token.remotelyDestructState === Constants.ContractTransactionStatus.InProgress) || (d.burnState === Constants.ContractTransactionStatus.InProgress)) @@ -189,7 +193,11 @@ Control { readonly property int remainingTokens: root.preview ? token.supply : token.remainingTokens label: qsTr("Remaining") - value: token.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(remainingTokens) + value: token.infiniteSupply + ? d.infiniteSymbol + : LocaleUtils.numberToLocaleString( + StatusQUtils.AmountsArithmetic.toNumber(token.remainingTokens, + token.multiplierIndex)) isLoading: !token.infiniteSupply && (d.burnState === Constants.ContractTransactionStatus.InProgress) } diff --git a/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml b/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml index 6c5a477c6c1..cf28c12b7a8 100644 --- a/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml @@ -25,7 +25,8 @@ StatusDialog { property string communityName property bool isAsset // If asset isAsset = true; if collectible --> isAsset = false property string tokenName - property int remainingTokens + property string remainingTokens + property int multiplierIndex property url tokenSource property string chainName @@ -38,13 +39,20 @@ StatusDialog { // Account expected roles: address, name, color, emoji, walletType property var accounts - signal burnClicked(int burnAmount, string accountAddress) + signal burnClicked(string burnAmount, string accountAddress) signal cancelClicked - signal burnFeesRequested(int burnAmount, string accountAddress) + signal burnFeesRequested(string burnAmount, string accountAddress) QtObject { id: d + readonly property real remainingTokensFloat: + SQUtils.AmountsArithmetic.toNumber( + root.remainingTokens, root.multiplierIndex) + + readonly property string remainingTokensDisplayText: + LocaleUtils.numberToLocaleString(remainingTokensFloat) + property string accountAddress property alias amountToBurn: amountToBurnInput.text readonly property bool isFeeError: root.feeErrorText !== "" @@ -75,7 +83,7 @@ StatusDialog { StatusBaseText { Layout.fillWidth: true - text: qsTr("How many of %1’s remaining %n %2 tokens would you like to burn?", "", root.remainingTokens).arg(root.communityName).arg(root.tokenName) + text: qsTr("How many of %1’s remaining %n %2 tokens would you like to burn?", "", d.remainingTokensFloat).arg(root.communityName).arg(root.tokenName) wrapMode: Text.WordWrap lineHeight: 1.2 font.pixelSize: Style.current.primaryTextFontSize @@ -107,7 +115,19 @@ StatusDialog { validationMode: StatusInput.ValidationMode.OnlyWhenDirty validators: [ StatusValidator { - validate: (value) => { return (parseInt(value) > 0 && parseInt(value) <= root.remainingTokens) } + validate: (value) => { + const intAmount = parseInt(value) + + if (!intAmount) + return false + + const current = SQUtils.AmountsArithmetic.fromNumber( + intAmount, root.multiplierIndex) + const remaining = SQUtils.AmountsArithmetic.fromString( + root.remainingTokens) + + return SQUtils.AmountsArithmetic.cmp(current, remaining) <= 0 + } errorMessage: qsTr("Exceeds available remaining") }, StatusValidator { @@ -127,7 +147,7 @@ StatusDialog { Layout.alignment: Qt.AlignTop - text: qsTr("All available remaining (%1)").arg(root.remainingTokens) + text: qsTr("All available remaining (%1)").arg(d.remainingTokensDisplayText) font.pixelSize: Style.current.primaryTextFontSize ButtonGroup.group: radioGroup } @@ -193,10 +213,18 @@ StatusDialog { interval: 500 onTriggered: { - if(specificAmountButton.checked) - root.burnFeesRequested(parseInt(amountToBurnInput.text), d.accountAddress) - else + if(specificAmountButton.checked) { + if (!amountToBurnInput.text) + return + + root.burnFeesRequested( + SQUtils.AmountsArithmetic.fromNumber( + parseInt(amountToBurnInput.text), + root.multiplierIndex), + d.accountAddress) + } else { root.burnFeesRequested(root.remainingTokens, d.accountAddress) + } } } @@ -213,7 +241,7 @@ StatusDialog { header: StatusDialogHeader { headline.title: qsTr("Burn %1 tokens").arg(root.tokenName) - headline.subtitle: qsTr("%n %1 remaining in smart contract", "", root.remainingTokens).arg(root.tokenName) + headline.subtitle: qsTr("%n %1 remaining in smart contract", "", d.remainingTokensFloat).arg(root.tokenName) leftComponent: Rectangle { height: 40 width: height @@ -256,10 +284,15 @@ StatusDialog { text: qsTr("Burn tokens") type: StatusBaseButton.Type.Danger onClicked: { - if(specificAmountButton.checked) - root.burnClicked(parseInt(amountToBurnInput.text), d.accountAddress) - else + if(specificAmountButton.checked) { + root.burnClicked( + SQUtils.AmountsArithmetic.fromNumber( + parseInt(amountToBurnInput.text), + root.multiplierIndex), + d.accountAddress) + } else { root.burnClicked(root.remainingTokens, d.accountAddress) + } } } } diff --git a/ui/app/AppLayouts/Communities/popups/HoldingsDropdown.qml b/ui/app/AppLayouts/Communities/popups/HoldingsDropdown.qml index 84e5f029c80..b39ff30d6a9 100644 --- a/ui/app/AppLayouts/Communities/popups/HoldingsDropdown.qml +++ b/ui/app/AppLayouts/Communities/popups/HoldingsDropdown.qml @@ -35,19 +35,20 @@ StatusDropdown { property var usedEnsNames: [] property string assetKey: "" - property real assetAmount: 0 + property string assetAmount: "0" + property int assetMultiplierIndex: 0 property string collectibleKey: "" - property real collectibleAmount: 1 + property string collectibleAmount: "1" property string ensDomainName: "" - signal addAsset(string key, real amount) - signal addCollectible(string key, real amount) + signal addAsset(string key, string amount) + signal addCollectible(string key, string amount) signal addEns(string domain) - signal updateAsset(string key, real amount) - signal updateCollectible(string key, real amount) + signal updateAsset(string key, string amount) + signal updateCollectible(string key, string amount) signal updateEns(string domain) signal removeClicked @@ -93,8 +94,10 @@ StatusDropdown { ] readonly property var tabsModel: [qsTr("Assets"), qsTr("Collectibles"), qsTr("ENS")] readonly property var tabsModelNoEns: [qsTr("Assets"), qsTr("Collectibles")] - readonly property bool assetsReady: root.assetAmount > 0 && root.assetKey - readonly property bool collectiblesReady: root.collectibleAmount > 0 && root.collectibleKey + + readonly property bool assetsReady: root.assetAmount !== "0" && root.assetKey + readonly property bool collectiblesReady: root.collectibleAmount !== "0" && root.collectibleKey + readonly property bool ensReady: d.ensDomainNameValid property int extendedDropdownType: ExtendedDropdownContent.Type.Assets @@ -138,8 +141,8 @@ StatusDropdown { function setDefaultAmounts() { d.assetAmountText = "" d.collectibleAmountText = "" - root.assetAmount = 0 - root.collectibleAmount = 1 + root.assetAmount = "0" + root.collectibleAmount = "1" } function forceLayout() { @@ -375,7 +378,8 @@ StatusDropdown { TokenPanel { id: assetPanel - readonly property real effectiveAmount: amountValid ? amount : 0 + readonly property string effectiveAmount: amountValid ? amount : "0" + property bool completed: false tokenName: PermissionsHelpers.getTokenNameByKey(root.assetsModel, root.assetKey) tokenShortName: PermissionsHelpers.getTokenShortNameByKey(root.assetsModel, root.assetKey) @@ -402,9 +406,10 @@ StatusDropdown { return append({ - name:chainName, + name: chainName, icon: chainIcon, amount: asset.supply, + multiplierIndex: asset.multiplierIndex, infiniteAmount: asset.infiniteSupply }) @@ -412,7 +417,12 @@ StatusDropdown { } } - onEffectiveAmountChanged: root.assetAmount = effectiveAmount + onEffectiveAmountChanged: { + if (completed) + root.assetAmount = effectiveAmount + } + + onMultiplierIndexChanged: root.assetMultiplierIndex = multiplierIndex onAmountTextChanged: d.assetAmountText = amountText onAddClicked: root.addAsset(root.assetKey, root.assetAmount) onUpdateClicked: root.updateAsset(root.assetKey, root.assetAmount) @@ -425,8 +435,11 @@ StatusDropdown { } Component.onCompleted: { - if (d.assetAmountText.length === 0 && root.assetAmount) - assetPanel.setAmount(root.assetAmount) + completed = true + + if (d.assetAmountText.length === 0 && root.assetAmount !== "0") + assetPanel.setAmount(root.assetAmount, + root.assetMultiplierIndex) } } } @@ -437,7 +450,8 @@ StatusDropdown { TokenPanel { id: collectiblePanel - readonly property real effectiveAmount: amountValid ? amount : 0 + readonly property string effectiveAmount: amountValid ? amount : "0" + property bool completed: false tokenName: PermissionsHelpers.getTokenNameByKey(root.collectiblesModel, root.collectibleKey) tokenShortName: "" @@ -469,6 +483,7 @@ StatusDropdown { name:chainName, icon: chainIcon, amount: collectible.supply, + multiplierIndex: collectible.multiplierIndex, infiniteAmount: collectible.infiniteSupply }) @@ -476,13 +491,19 @@ StatusDropdown { } } - onEffectiveAmountChanged: root.collectibleAmount = effectiveAmount + onEffectiveAmountChanged: { + if (completed) + root.collectibleAmount = effectiveAmount + } + onAmountTextChanged: d.collectibleAmountText = amountText onAddClicked: root.addCollectible(root.collectibleKey, root.collectibleAmount) onUpdateClicked: root.updateCollectible(root.collectibleKey, root.collectibleAmount) onRemoveClicked: root.removeClicked() Component.onCompleted: { + completed = true + if (d.collectibleAmountText.length === 0 && root.collectibleAmount) collectiblePanel.setAmount(root.collectibleAmount) } diff --git a/ui/app/AppLayouts/Communities/views/EditAirdropView.qml b/ui/app/AppLayouts/Communities/views/EditAirdropView.qml index b20f93daa09..0b552da52d5 100644 --- a/ui/app/AppLayouts/Communities/views/EditAirdropView.qml +++ b/ui/app/AppLayouts/Communities/views/EditAirdropView.qml @@ -178,18 +178,18 @@ StatusScrollView { onTotalRevisionChanged: Qt.callLater(() => d.resetFees()) function prepareEntry(key, amount, type) { - let tokenModel = null - if(type === Constants.TokenType.ERC20) - tokenModel = root.assetsModel - else if (type === Constants.TokenType.ERC721) - tokenModel = root.collectiblesModel - + const tokenModel = type === Constants.TokenType.ERC20 + ? root.assetsModel : root.collectiblesModel const modelItem = PermissionsHelpers.getTokenByKey( tokenModel, key) - + const multiplierIndex = modelItem.multiplierIndex + const amountNumber = AmountsArithmetic.toNumber( + amount, multiplierIndex) + const amountLocalized = LocaleUtils.numberToLocaleString( + amountNumber, -1) return { key, amount, type, - tokenText: amount + " " + modelItem.name, + tokenText: amountLocalized + " " + modelItem.name, tokenImage: modelItem.iconSource, networkText: modelItem.chainName, networkImage: Style.svg(modelItem.chainIcon), @@ -278,7 +278,13 @@ StatusScrollView { if (!item || item.infiniteSupply) continue - min = Math.min(item.supply / item.amount, min) + const dividient = AmountsArithmetic.fromString(item.supply) + const divisor = AmountsArithmetic.fromString(item.amount) + + const quotient = AmountsArithmetic.toNumber( + AmountsArithmetic.div(dividient, divisor)) + + min = Math.min(quotient, min) } infinity = min === Number.MAX_SAFE_INTEGER @@ -286,12 +292,24 @@ StatusScrollView { } delegate: QtObject { - readonly property int supply: model.supply - readonly property real amount: model.amount + readonly property string supply: model.supply + readonly property string amount: model.amount readonly property bool infiniteSupply: model.infiniteSupply - readonly property bool valid: - infiniteSupply || amount * airdropRecipientsSelector.count <= supply + readonly property bool valid: { + if (infiniteSupply) + return true + + const recipientsCount = airdropRecipientsSelector.count + const demand = AmountsArithmetic.times( + AmountsArithmetic.fromString(amount), + recipientsCount) + + const available = AmountsArithmetic.fromString(supply) + + return AmountsArithmetic.cmp(demand, available) <= 0 + } + onSupplyChanged: recipientsCountInstantiator.findRecipientsCount() onAmountChanged: recipientsCountInstantiator.findRecipientsCount() diff --git a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml index 00b57861563..bb7cd99ca42 100644 --- a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml +++ b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml @@ -254,7 +254,13 @@ StatusScrollView { visible: !unlimitedSupplyChecker.checked label: qsTr("Total finite supply") - text: root.isAssetView ? asset.supply : collectible.supply + text: { + const token = root.isAssetView ? root.asset : root.collectible + + return SQUtils.AmountsArithmetic.toNumber(token.supply, + token.multiplierIndex) + } + placeholderText: qsTr("e.g. 300") 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)") : @@ -264,14 +270,13 @@ StatusScrollView { extraValidator.errorMessage: qsTr("Enter a number between 1 and 999,999,999") onTextChanged: { - const amount = parseInt(text) - if (Number.isNaN(amount) || Object.values(errors).length) + const supplyNumber = parseInt(text) + if (Number.isNaN(supplyNumber) || Object.values(errors).length) return - if(root.isAssetView) - asset.supply = amount - else - collectible.supply = amount + const token = root.isAssetView ? root.asset : root.collectible + token.supply = SQUtils.AmountsArithmetic.fromNumber( + supplyNumber, token.multiplierIndex).toFixed(0) } } diff --git a/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml b/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml index 40aa7bbb519..d12360072d7 100644 --- a/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml +++ b/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml @@ -50,7 +50,7 @@ StatusScrollView { symbol: PermissionsHelpers.communityNameToSymbol(isOwner, root.communityName) transferable: true remotelyDestruct: false - supply: 1 + supply: "1" infiniteSupply: false description: qsTr("This is the %1 Owner token. The hodler of this collectible has ultimate control over %1 Community token administration.").arg(root.communityName) } diff --git a/ui/app/AppLayouts/Communities/views/EditPermissionView.qml b/ui/app/AppLayouts/Communities/views/EditPermissionView.qml index 2c5662470ab..bd49a783abf 100644 --- a/ui/app/AppLayouts/Communities/views/EditPermissionView.qml +++ b/ui/app/AppLayouts/Communities/views/EditPermissionView.qml @@ -172,7 +172,7 @@ StatusScrollView { onPermissionTypeChanged: Qt.callLater(() => d.loadInitValues()) contentWidth: mainLayout.width contentHeight: mainLayout.height - + SequenceColumnLayout { id: mainLayout @@ -241,7 +241,7 @@ StatusScrollView { const key = item.key d.dirtyValues.selectedHoldingsModel.append( - { type, key, amount }) + { type, key, amount: parseFloat(amount) }) } function prepareUpdateIndex(key) { @@ -269,6 +269,7 @@ StatusScrollView { onAddAsset: { const modelItem = PermissionsHelpers.getTokenByKey( root.assetsModel, key) + addItem(HoldingTypes.Type.Asset, modelItem, amount) dropdown.close() } @@ -276,6 +277,7 @@ StatusScrollView { onAddCollectible: { const modelItem = PermissionsHelpers.getTokenByKey( root.collectiblesModel, key) + addItem(HoldingTypes.Type.Collectible, modelItem, amount) dropdown.close() } @@ -291,7 +293,7 @@ StatusScrollView { const modelItem = PermissionsHelpers.getTokenByKey(root.assetsModel, key) d.dirtyValues.selectedHoldingsModel.set( - itemIndex, { type: HoldingTypes.Type.Asset, key, amount }) + itemIndex, { type: HoldingTypes.Type.Asset, key, amount: parseFloat(amount) }) dropdown.close() } @@ -302,7 +304,7 @@ StatusScrollView { d.dirtyValues.selectedHoldingsModel.set( itemIndex, - { type: HoldingTypes.Type.Collectible, key, amount }) + { type: HoldingTypes.Type.Collectible, key, amount: parseFloat(amount) }) dropdown.close() } diff --git a/ui/app/AppLayouts/Communities/views/MintedTokensView.qml b/ui/app/AppLayouts/Communities/views/MintedTokensView.qml index 43d5b9e9e55..4a387b149ec 100644 --- a/ui/app/AppLayouts/Communities/views/MintedTokensView.qml +++ b/ui/app/AppLayouts/Communities/views/MintedTokensView.qml @@ -161,7 +161,7 @@ StatusScrollView { id: assetsList Layout.fillWidth: true - Layout.preferredHeight: childrenRect.height + Layout.preferredHeight: contentHeight visible: count > 0 model: assetsModel diff --git a/ui/imports/shared/controls/AmountInput.qml b/ui/imports/shared/controls/AmountInput.qml index f28b6fb7d1c..ba9de3cf9e8 100644 --- a/ui/imports/shared/controls/AmountInput.qml +++ b/ui/imports/shared/controls/AmountInput.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts 1.14 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 as SQUtils import utils 1.0 @@ -13,11 +14,13 @@ Input { property var locale: Qt.locale() readonly property alias amount: d.amount + property alias multiplierIndex: d.multiplierIndex + readonly property bool valid: validationError.length === 0 property bool allowDecimals: true property bool validateMaximumAmount: false - property real maximumAmount: 0 + property string maximumAmount: "0" validationErrorTopMargin: 8 fontPixelSize: 13 @@ -27,18 +30,27 @@ Input { textField.rightPadding: labelText.implicitWidth + labelText.anchors.rightMargin + textField.leftPadding - function setAmount(amount) { - root.text = LocaleUtils.numberToLocaleString(amount, -1, root.locale) + function setAmount(amount, multiplierIndex = 0) { + console.assert(typeof amount === "string") + + d.multiplierIndex = multiplierIndex + const amountNumber = SQUtils.AmountsArithmetic.toNumber( + amount, multiplierIndex) + + root.text = LocaleUtils.numberToLocaleString(amountNumber, -1, + root.locale) } onTextChanged: d.validate() onValidateMaximumAmountChanged: d.validate() onMaximumAmountChanged: d.validate() + onMultiplierIndexChanged: d.validate() QtObject { id: d - property real amount: 0 + property string amount: "0" + property int multiplierIndex: 0 function getEffectiveDigitsCount(str) { const digits = LocaleUtils.getLocalizedDigitsCount(text, root.locale) @@ -50,7 +62,7 @@ Input { root.text = root.text.replace(root.locale.decimalPoint, "") if(root.text.length === 0) { - d.amount = 0 + d.amount = "0" root.validationError = "" return } @@ -60,16 +72,38 @@ Input { return } - const amount = LocaleUtils.numberFromLocaleString(root.text, root.locale) - if (isNaN(amount)) { - d.amount = 0 + const amountNumber = LocaleUtils.numberFromLocaleString(root.text, root.locale) + if (isNaN(amountNumber)) { + d.amount = "0" root.validationError = qsTr("Invalid amount format") - } else if (root.validateMaximumAmount && amount > root.maximumAmount) { - root.validationError = qsTr("Amount exceeds balance") - } else { - d.amount = amount - root.validationError = "" + return } + + const amount = SQUtils.AmountsArithmetic.fromNumber( + amountNumber, d.multiplierIndex) + + if (root.validateMaximumAmount) { + const maximumAmount = SQUtils.AmountsArithmetic.fromString( + root.maximumAmount) + + const maxExceeded = SQUtils.AmountsArithmetic.cmp( + amount, maximumAmount) === 1 + + if (SQUtils.AmountsArithmetic.cmp(amount, maximumAmount) === 1) { + root.validationError = qsTr("Amount exceeds balance") + return + } + } + + // Fallback to handle float amounts for permissions + // As a target amount should be always integer number + if (!Number.isInteger(amountNumber) && d.multiplierIndex === 0) { + d.amount = amount.toString() + } else { + d.amount = amount.toFixed(0) + } + + root.validationError = "" } } diff --git a/ui/imports/shared/stores/CommunityTokensStore.qml b/ui/imports/shared/stores/CommunityTokensStore.qml index fa40ccc58af..d3af9c32694 100644 --- a/ui/imports/shared/stores/CommunityTokensStore.qml +++ b/ui/imports/shared/stores/CommunityTokensStore.qml @@ -24,10 +24,10 @@ QtObject { // Minting tokens: function deployCollectible(communityId, collectibleItem) - { - if (collectibleItem.key !== "") { + { + if (collectibleItem.key !== "") deleteToken(communityId, collectibleItem.key) - } + const jsonArtworkFile = Utils.getImageAndCropInfoJson(collectibleItem.artworkSource, collectibleItem.artworkCropRect) communityTokensModuleInst.deployCollectible(communityId, collectibleItem.accountAddress, collectibleItem.name, collectibleItem.symbol, collectibleItem.description, collectibleItem.supply, @@ -37,9 +37,9 @@ QtObject { function deployAsset(communityId, assetItem) { - if (assetItem.key !== "") { + if (assetItem.key !== "") deleteToken(communityId, assetItem.key) - } + const jsonArtworkFile = Utils.getImageAndCropInfoJson(assetItem.artworkSource, assetItem.artworkCropRect) communityTokensModuleInst.deployAssets(communityId, assetItem.accountAddress, assetItem.name, assetItem.symbol, assetItem.description, assetItem.supply, @@ -110,11 +110,13 @@ QtObject { // Burn: function computeBurnFee(tokenKey, amount, accountAddress) { + console.assert(typeof amount === "string") // TODO: Backend. It should include the account address in the calculation. communityTokensModuleInst.computeBurnFee(tokenKey, amount/*, accountAddress*/) } function burnToken(communityId, tokenKey, burnAmount, accountAddress) { + console.assert(typeof burnAmount === "string") // TODO: Backend. It should include the account address in the burn action. communityTokensModuleInst.burnTokens(communityId, tokenKey, burnAmount/*, accountAddress*/) }