chore(Communities): Refactor amounts handling for displaying, minting, airdropping and burning

Closes: #11491
This commit is contained in:
Michał Cieślak 2023-08-10 14:23:59 +02:00 committed by Michał
parent f3526d6e01
commit 1c50ec17a8
29 changed files with 404 additions and 176 deletions

View File

@ -21,14 +21,14 @@ method computeAirdropFee*(self: AccessInterface, communityId: string, tokensJson
method selfDestructCollectibles*(self: AccessInterface, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} = method selfDestructCollectibles*(self: AccessInterface, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} =
raise newException(ValueError, "No implementation available") 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") 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.} = selfDestruct: bool, chainId: int, imageCropInfoJson: string) {.base.} =
raise newException(ValueError, "No implementation available") 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.} = chainId: int, imageCropInfoJson: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -48,7 +48,7 @@ method computeDeployFee*(self: AccessInterface, chainId: int, accountAddress: st
method computeSelfDestructFee*(self: AccessInterface, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} = method computeSelfDestructFee*(self: AccessInterface, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} =
raise newException(ValueError, "No implementation available") 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") raise newException(ValueError, "No implementation available")
method onDeployFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.base.} = method onDeployFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.base.} =

View File

@ -2,6 +2,7 @@ import NimQml, Tables, strformat, sequtils, stint
import token_item import token_item
import token_owners_item import token_owners_item
import token_owners_model import token_owners_model
import ../../../../../../app_service/service/community/dto/community
import ../../../../../../app_service/service/community_tokens/dto/community_token import ../../../../../../app_service/service/community_tokens/dto/community_token
import ../../../../../../app_service/service/community_tokens/community_collectible_owner import ../../../../../../app_service/service/community_tokens/community_collectible_owner
import ../../../../../../app_service/common/utils import ../../../../../../app_service/common/utils
@ -32,6 +33,7 @@ type
BurnState BurnState
RemotelyDestructState RemotelyDestructState
PrivilegesLevel PrivilegesLevel
MultiplierIndex
QtObject: QtObject:
type TokenModel* = ref object of QAbstractListModel type TokenModel* = ref object of QAbstractListModel
@ -181,7 +183,8 @@ QtObject:
ModelRole.Decimals.int:"decimals", ModelRole.Decimals.int:"decimals",
ModelRole.BurnState.int:"burnState", ModelRole.BurnState.int:"burnState",
ModelRole.RemotelyDestructState.int:"remotelyDestructState", ModelRole.RemotelyDestructState.int:"remotelyDestructState",
ModelRole.PrivilegesLevel.int:"privilegesLevel" ModelRole.PrivilegesLevel.int:"privilegesLevel",
ModelRole.MultiplierIndex.int:"multiplierIndex"
}.toTable }.toTable
method data(self: TokenModel, index: QModelIndex, role: int): QVariant = method data(self: TokenModel, index: QModelIndex, role: int): QVariant =
@ -206,7 +209,7 @@ QtObject:
result = newQVariant(item.tokenDto.description) result = newQVariant(item.tokenDto.description)
of ModelRole.Supply: of ModelRole.Supply:
# we need to present maxSupply - destructedAmount # 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: of ModelRole.InfiniteSupply:
result = newQVariant(item.tokenDto.infiniteSupply) result = newQVariant(item.tokenDto.infiniteSupply)
of ModelRole.Transferable: of ModelRole.Transferable:
@ -230,7 +233,7 @@ QtObject:
of ModelRole.AccountAddress: of ModelRole.AccountAddress:
result = newQVariant(item.tokenDto.deployer) result = newQVariant(item.tokenDto.deployer)
of ModelRole.RemainingSupply: of ModelRole.RemainingSupply:
result = newQVariant(supplyByType(item.remainingSupply, item.tokenDto.tokenType)) result = newQVariant(item.remainingSupply.toString(10))
of ModelRole.Decimals: of ModelRole.Decimals:
result = newQVariant(item.tokenDto.decimals) result = newQVariant(item.tokenDto.decimals)
of ModelRole.BurnState: of ModelRole.BurnState:
@ -240,6 +243,8 @@ QtObject:
result = newQVariant(destructStatus) result = newQVariant(destructStatus)
of ModelRole.PrivilegesLevel: of ModelRole.PrivilegesLevel:
result = newQVariant(item.tokenDto.privilegesLevel.int) result = newQVariant(item.tokenDto.privilegesLevel.int)
of ModelRole.MultiplierIndex:
result = newQVariant(if item.tokenDto.tokenType == TokenType.ERC20: 18 else: 0)
proc `$`*(self: TokenModel): string = proc `$`*(self: TokenModel): string =
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:

View File

@ -93,29 +93,17 @@ proc authenticate(self: Module) =
else: else:
self.controller.authenticateUser() 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] = proc getTokenAndAmountList(self: Module, communityId: string, tokensJsonString: string): seq[CommunityTokenAndAmount] =
try: try:
let tokensJson = tokensJsonString.parseJson let tokensJson = tokensJsonString.parseJson
for token in tokensJson: for token in tokensJson:
let contractUniqueKey = token["contractUniqueKey"].getStr let contractUniqueKey = token["contractUniqueKey"].getStr
let tokenDto = self.controller.findContractByUniqueId(contractUniqueKey) let tokenDto = self.controller.findContractByUniqueId(contractUniqueKey)
let amountStr = token["amount"].getFloat let amountStr = token["amount"].getStr
if tokenDto.tokenType == TokenType.Unknown: if tokenDto.tokenType == TokenType.Unknown:
error "Can't find token for community", contractUniqueKey=contractUniqueKey error "Can't find token for community", contractUniqueKey=contractUniqueKey
return @[] 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: except Exception as e:
error "Error getTokenAndAmountList", msg = e.msg error "Error getTokenAndAmountList", msg = e.msg
@ -148,16 +136,16 @@ method selfDestructCollectibles*(self: Module, communityId: string, collectibles
self.tempContractAction = ContractAction.SelfDestruct self.tempContractAction = ContractAction.SelfDestruct
self.authenticate() 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) let tokenDto = self.controller.findContractByUniqueId(contractUniqueKey)
self.tempCommunityId = communityId self.tempCommunityId = communityId
self.tempContractUniqueKey = contractUniqueKey self.tempContractUniqueKey = contractUniqueKey
self.tempAmount = self.convertAmountByTokenType(tokenDto.tokenType, amount) self.tempAmount = amount.parse(Uint256)
self.tempContractAction = ContractAction.Burn self.tempContractAction = ContractAction.Burn
self.authenticate() self.authenticate()
method deployCollectibles*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string, 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) =
let ownerToken = self.controller.getOwnerToken(communityId) let ownerToken = self.controller.getOwnerToken(communityId)
let masterToken = self.controller.getTokenMasterToken(communityId) let masterToken = self.controller.getTokenMasterToken(communityId)
@ -170,7 +158,7 @@ method deployCollectibles*(self: Module, communityId: string, fromAddress: strin
self.tempChainId = chainId self.tempChainId = chainId
self.tempDeploymentParams.name = name self.tempDeploymentParams.name = name
self.tempDeploymentParams.symbol = symbol self.tempDeploymentParams.symbol = symbol
self.tempDeploymentParams.supply = self.convertAmountByTokenType(TokenType.ERC721, supply) self.tempDeploymentParams.supply = supply.parse(Uint256)
self.tempDeploymentParams.infiniteSupply = infiniteSupply self.tempDeploymentParams.infiniteSupply = infiniteSupply
self.tempDeploymentParams.transferable = transferable self.tempDeploymentParams.transferable = transferable
self.tempDeploymentParams.remoteSelfDestruct = selfDestruct self.tempDeploymentParams.remoteSelfDestruct = selfDestruct
@ -207,14 +195,14 @@ method deployOwnerToken*(self: Module, communityId: string, fromAddress: string,
self.tempContractAction = ContractAction.DeployOwnerToken self.tempContractAction = ContractAction.DeployOwnerToken
self.authenticate() 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) = chainId: int, imageCropInfoJson: string) =
self.tempAddressFrom = fromAddress self.tempAddressFrom = fromAddress
self.tempCommunityId = communityId self.tempCommunityId = communityId
self.tempChainId = chainId self.tempChainId = chainId
self.tempDeploymentParams.name = name self.tempDeploymentParams.name = name
self.tempDeploymentParams.symbol = symbol self.tempDeploymentParams.symbol = symbol
self.tempDeploymentParams.supply = self.convertAmountByTokenType(TokenType.ERC20, supply) self.tempDeploymentParams.supply = supply.parse(Uint256)
self.tempDeploymentParams.infiniteSupply = infiniteSupply self.tempDeploymentParams.infiniteSupply = infiniteSupply
self.tempDeploymentParams.decimals = decimals self.tempDeploymentParams.decimals = decimals
self.tempDeploymentParams.tokenUri = utl.changeCommunityKeyCompression(communityId) & "/" self.tempDeploymentParams.tokenUri = utl.changeCommunityKeyCompression(communityId) & "/"
@ -269,9 +257,9 @@ method computeSelfDestructFee*(self: Module, collectiblesToBurnJsonString: strin
let walletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString) let walletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString)
self.controller.computeSelfDestructFee(walletAndAmountList, contractUniqueKey) 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) 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 = proc createUrl(self: Module, chainId: int, transactionHash: string): string =
let network = self.controller.getNetwork(chainId) let network = self.controller.getNetwork(chainId)

View File

@ -21,10 +21,10 @@ QtObject:
result.QObject.setup result.QObject.setup
result.communityTokensModule = communityTokensModule 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) 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) self.communityTokensModule.deployAssets(communityId, fromAddress, name, symbol, description, supply, infiniteSupply, decimals, chainId, imageCropInfoJson)
proc deployOwnerToken*(self:View, communityId: string, fromAddress: string, ownerName: string, ownerSymbol: string, ownerDescription: string, masterName: string, masterSymbol: string, masterDescription: string, chainId: int, imageCropInfoJson: string) {.slot.} = proc deployOwnerToken*(self:View, communityId: string, fromAddress: string, ownerName: string, ownerSymbol: string, ownerDescription: string, masterName: string, masterSymbol: string, masterDescription: string, chainId: int, imageCropInfoJson: string) {.slot.} =
@ -42,7 +42,7 @@ QtObject:
proc selfDestructCollectibles*(self: View, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} = proc selfDestructCollectibles*(self: View, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} =
self.communityTokensModule.selfDestructCollectibles(communityId, collectiblesToBurnJsonString, contractUniqueKey) 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) self.communityTokensModule.burnTokens(communityId, contractUniqueKey, amount)
proc deployFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.} proc deployFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.}
@ -56,7 +56,7 @@ QtObject:
proc computeSelfDestructFee*(self: View, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} = proc computeSelfDestructFee*(self: View, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} =
self.communityTokensModule.computeSelfDestructFee(collectiblesToBurnJsonString, contractUniqueKey) 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) self.communityTokensModule.computeBurnFee(contractUniqueKey, amount)
proc updateDeployFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) = proc updateDeployFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) =

View File

@ -45,6 +45,7 @@ proc toUInt256*(flt: float): UInt256 =
proc toUInt64*(flt: float): StUInt[64] = proc toUInt64*(flt: float): StUInt[64] =
toStUInt(flt, 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 = proc eth2Wei*(eth: float, decimals: int = 18): UInt256 =
let weiValue = eth * parseFloat(alignLeft("1", decimals + 1, '0')) let weiValue = eth * parseFloat(alignLeft("1", decimals + 1, '0'))
weiValue.toUInt256 weiValue.toUInt256

View File

@ -89,14 +89,3 @@ proc toCommunityTokenDto*(jsonObj: JsonNode): CommunityTokenDto =
proc parseCommunityTokens*(response: RpcResponse[JsonNode]): seq[CommunityTokenDto] = proc parseCommunityTokens*(response: RpcResponse[JsonNode]): seq[CommunityTokenDto] =
result = map(response.result.getElems(), result = map(response.result.getElems(),
proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto()) 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

View File

@ -104,6 +104,8 @@ SplitView {
assetsModel: AssetsModel {} assetsModel: AssetsModel {}
collectiblesModel: ListModel {} collectiblesModel: ListModel {}
accountsModel: ListModel {}
CollectiblesModel { CollectiblesModel {
id: collectiblesModel id: collectiblesModel
} }
@ -118,6 +120,10 @@ SplitView {
name: "supply" name: "supply"
expression: ((model.index + 1) * 115).toString() expression: ((model.index + 1) * 115).toString()
}, },
ExpressionRole {
name: "multiplierIndex"
expression: 0
},
ExpressionRole { ExpressionRole {
name: "infiniteSupply" name: "infiniteSupply"
expression: !(model.index % 4) expression: !(model.index % 4)
@ -158,7 +164,12 @@ SplitView {
proxyRoles: [ proxyRoles: [
ExpressionRole { ExpressionRole {
name: "supply" name: "supply"
expression: ((model.index + 1) * 258).toString() expression: ((model.index + 1) * 584).toString()
+ "0".repeat(18)
},
ExpressionRole {
name: "multiplierIndex"
expression: 18
}, },
ExpressionRole { ExpressionRole {
name: "infiniteSupply" name: "infiniteSupply"

View File

@ -66,7 +66,11 @@ SplitView {
proxyRoles: [ proxyRoles: [
ExpressionRole { ExpressionRole {
name: "supply" name: "supply"
expression: model.index === 1 ? model.index : (model.index + 1) * 115 expression: (model.index === 1 ? 1 : (model.index + 1) * 115).toString()
},
ExpressionRole {
name: "multiplierIndex"
expression: 0
}, },
ExpressionRole { ExpressionRole {
name: "infiniteSupply" name: "infiniteSupply"
@ -99,7 +103,12 @@ SplitView {
proxyRoles: [ proxyRoles: [
ExpressionRole { ExpressionRole {
name: "supply" name: "supply"
expression: (model.index + 1) * 584 expression: ((model.index + 1) * 584).toString()
+ "0".repeat(18)
},
ExpressionRole {
name: "multiplierIndex"
expression: 18
}, },
ExpressionRole { ExpressionRole {
name: "infiniteSupply" name: "infiniteSupply"

View File

@ -13,20 +13,30 @@ Item {
{ {
name: "Optimism", name: "Optimism",
icon: Style.svg(ModelsData.networks.optimism), icon: Style.svg(ModelsData.networks.optimism),
amount: 300, amount: "300",
multiplierIndex: 0,
infiniteAmount: false infiniteAmount: false
}, },
{ {
name: "Arbitrum", name: "Arbitrum",
icon: Style.svg(ModelsData.networks.arbitrum), icon: Style.svg(ModelsData.networks.arbitrum),
amount: 400, amount: "400000",
multiplierIndex: 3,
infiniteAmount: false infiniteAmount: false
}, },
{ {
name: "Hermez", name: "Hermez",
icon: Style.svg(ModelsData.networks.hermez), icon: Style.svg(ModelsData.networks.hermez),
amount: 0, amount: "0",
multiplierIndex: 0,
infiniteAmount: true 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 Layout.alignment: Qt.AlignHCenter
text: `current amount: ${comboBox.currentAmount}` text: `current amount: ${comboBox.currentAmount}`
} }
Label {
Layout.alignment: Qt.AlignHCenter
text: `current multiplier index: ${comboBox.currentMultiplierIndex}`
}
Label { Label {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: `current amount infinite: ${comboBox.currentInfiniteAmount}` text: `current amount infinite: ${comboBox.currentInfiniteAmount}`

View File

@ -2,6 +2,8 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import Qt.labs.settings 1.0
import Models 1.0 import Models 1.0
import Storybook 1.0 import Storybook 1.0
import utils 1.0 import utils 1.0
@ -20,20 +22,30 @@ SplitView {
{ {
name: "Optimism", name: "Optimism",
icon: Style.svg(ModelsData.networks.optimism), icon: Style.svg(ModelsData.networks.optimism),
amount: 300, amount: "300",
multiplierIndex: 0,
infiniteAmount: false infiniteAmount: false
}, },
{ {
name: "Arbitrum", name: "Arbitrum",
icon: Style.svg(ModelsData.networks.arbitrum), icon: Style.svg(ModelsData.networks.arbitrum),
amount: 400, amount: "400000",
multiplierIndex: 3,
infiniteAmount: false infiniteAmount: false
}, },
{ {
name: "Hermez", name: "Hermez",
icon: Style.svg(ModelsData.networks.hermez), icon: Style.svg(ModelsData.networks.hermez),
amount: 500, amount: "0",
multiplierIndex: 0,
infiniteAmount: true 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: "∞" text: "∞"
} }
} }
Label {
text: "amount: " + tokenPanel.amount
} }
} }
}
Settings {
property alias networksModelCheckBoxChecked:
networksModelCheckBox.checked
}
} }
// category: Panels // category: Panels

View File

@ -40,13 +40,14 @@ QtObject {
console.assert(!isNaN(number) && Number.isInteger(multiplier) console.assert(!isNaN(number) && Number.isInteger(multiplier)
&& multiplier >= 0) && multiplier >= 0)
const amount = new Big.Big(number).times(10 ** multiplier) 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 return amount
} }
/*! /*!
\qmlmethod AmountsArithmetic::toNumber(amount, multiplier = 0) \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 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 be used only to display a value in the user interface, but requires
@ -57,10 +58,16 @@ QtObject {
\qml \qml
console.log(AmountsArithmetic.toNumber( console.log(AmountsArithmetic.toNumber(
AmountsArithmetic.fromString("123456789123456789123"))) // 123456789123456800000 AmountsArithmetic.fromString("123456789123456789123"))) // 123456789123456800000
console.log(AmountsArithmetic.toNumber("123456789123456789123")) // 123456789123456800000
\endqml \endqml
*/ */
function toNumber(amount, multiplier = 0) { function toNumber(amount, multiplier = 0) {
console.assert(Number.isInteger(multiplier) && 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() return amount.div(10 ** multiplier).toNumber()
} }
@ -84,7 +91,8 @@ QtObject {
function fromString(numStr) { function fromString(numStr) {
console.assert(typeof numStr === "string") console.assert(typeof numStr === "string")
const amount = new Big.Big(numStr) 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 return amount
} }
@ -105,6 +113,16 @@ QtObject {
return amount.times(multiplier) 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) \qmlmethod AmountsArithmetic::cmp(amount1, amount2)
\brief Compares two amounts. \brief Compares two amounts.

View File

@ -5,6 +5,7 @@ import QtQml 2.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
@ -13,6 +14,7 @@ StatusComboBox {
readonly property string currentName: control.currentText readonly property string currentName: control.currentText
readonly property alias currentAmount: instantiator.amount readonly property alias currentAmount: instantiator.amount
readonly property alias currentMultiplierIndex: instantiator.multiplierIndex
readonly property alias currentInfiniteAmount: instantiator.infiniteAmount readonly property alias currentInfiniteAmount: instantiator.infiniteAmount
readonly property alias currentIcon: instantiator.icon readonly property alias currentIcon: instantiator.icon
@ -49,6 +51,10 @@ StatusComboBox {
readonly property int iconSize: 32 readonly property int iconSize: 32
readonly property string infinitySymbol: "∞" readonly property string infinitySymbol: "∞"
function amountText(amount, multiplierIndex) {
return SQUtils.AmountsArithmetic.toNumber(amount, multiplierIndex)
}
} }
component CustomText: StatusBaseText { component CustomText: StatusBaseText {
@ -94,7 +100,8 @@ StatusComboBox {
id: instantiator id: instantiator
property string icon property string icon
property int amount property string amount
property int multiplierIndex
property bool infiniteAmount property bool infiniteAmount
model: SortFilterProxyModel { model: SortFilterProxyModel {
@ -109,6 +116,7 @@ StatusComboBox {
readonly property list<Binding> bindings: [ readonly property list<Binding> bindings: [
Bind { property: "icon"; value: model.icon }, Bind { property: "icon"; value: model.icon },
Bind { property: "amount"; value: model.amount }, Bind { property: "amount"; value: model.amount },
Bind { property: "multiplierIndex"; value: model.multiplierIndex },
Bind { property: "infiniteAmount"; value: model.infiniteAmount } Bind { property: "infiniteAmount"; value: model.infiniteAmount }
] ]
} }
@ -118,10 +126,17 @@ StatusComboBox {
title: root.control.displayText title: root.control.displayText
iconSource: instantiator.icon iconSource: instantiator.icon
amount: !d.oneItem amount: {
? (instantiator.infiniteAmount ? d.infinitySymbol if (d.oneItem || !instantiator.amount)
: instantiator.amount) return ""
: ""
if (instantiator.infiniteAmount)
return d.infinitySymbol
return d.amountText(instantiator.amount,
instantiator.multiplierIndex)
}
cursorShape: d.oneItem ? Qt.ArrowCursor : Qt.PointingHandCursor cursorShape: d.oneItem ? Qt.ArrowCursor : Qt.PointingHandCursor
onClicked: { onClicked: {
@ -135,7 +150,9 @@ StatusComboBox {
delegate: DelegateItem { delegate: DelegateItem {
title: model.name title: model.name
iconSource: model.icon iconSource: model.icon
amount: model.infiniteAmount ? d.infinitySymbol : model.amount amount: model.infiniteAmount
? d.infinitySymbol
: d.amountText(model.amount, model.multiplierIndex)
width: root.width width: root.width
height: root.height height: root.height

View File

@ -1,12 +1,13 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import QtQuick.Controls 2.13 import QtQuick.Controls 2.15
import QtGraphicalEffects 1.13 import QtGraphicalEffects 1.15
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1
import utils 1.0 import utils 1.0
@ -98,11 +99,19 @@ StatusListView {
showSubItemsIcon: !!model.subItems && model.subItems.count > 0 showSubItemsIcon: !!model.subItems && model.subItems.count > 0
selected: root.checkedKeys.includes(model.key) selected: root.checkedKeys.includes(model.key)
amount: { amount: {
if(!model.infiniteSupply && !!model.supply && model.supply == 1) if (model.supply === undefined
|| model.multiplierIndex === undefined)
return ""
if (model.infiniteSupply)
return "∞"
if (model.supply === "1")
return qsTr("Max. 1") return qsTr("Max. 1")
if(root.showTokenAmount) if (root.showTokenAmount)
return !!model.infiniteSupply ? "∞" : model.supply ?? "" return SQUtils.AmountsArithmetic.toNumber(model.supply,
model.multiplierIndex)
return "" return ""
} }

View File

@ -1,5 +1,5 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
@ -21,6 +21,7 @@ ColumnLayout {
property alias tokenImage: item.iconSource property alias tokenImage: item.iconSource
property alias amountText: amountInput.text property alias amountText: amountInput.text
property alias amount: amountInput.amount property alias amount: amountInput.amount
property alias multiplierIndex: amountInput.multiplierIndex
property alias tokenCategoryText: tokenLabel.text property alias tokenCategoryText: tokenLabel.text
property alias networkLabelText: d.networkLabelText property alias networkLabelText: d.networkLabelText
property alias addOrUpdateButtonEnabled: addOrUpdateButton.enabled property alias addOrUpdateButtonEnabled: addOrUpdateButton.enabled
@ -33,8 +34,9 @@ ColumnLayout {
signal updateClicked signal updateClicked
signal removeClicked signal removeClicked
function setAmount(amount) { function setAmount(amount, multiplierIndex = 0) {
amountInput.setAmount(amount) console.assert(typeof amount === "string")
amountInput.setAmount(amount, multiplierIndex)
} }
QtObject { QtObject {
@ -86,7 +88,10 @@ ColumnLayout {
spacing: 10 spacing: 10
property alias currentAmount: inlineNetworksComboBox.currentAmount property alias currentAmount: inlineNetworksComboBox.currentAmount
property alias currentInfiniteAmount: inlineNetworksComboBox.currentInfiniteAmount property alias currentMultiplierIndex:
inlineNetworksComboBox.currentMultiplierIndex
property alias currentInfiniteAmount:
inlineNetworksComboBox.currentInfiniteAmount
CustomText { CustomText {
id: networkLabel id: networkLabel
@ -124,10 +129,14 @@ ColumnLayout {
!networksComboBoxLoader.item.currentInfiniteAmount !networksComboBoxLoader.item.currentInfiniteAmount
maximumAmount: !!networksComboBoxLoader.item maximumAmount: !!networksComboBoxLoader.item
? networksComboBoxLoader.item.currentAmount : 0 ? networksComboBoxLoader.item.currentAmount : "0"
multiplierIndex: !!networksComboBoxLoader.item
? networksComboBoxLoader.item.currentMultiplierIndex : 0
onKeyPressed: { onKeyPressed: {
if(!addOrUpdateButton.enabled) return if(!addOrUpdateButton.enabled)
return
if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return) if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return)
addOrUpdateButton.clicked() addOrUpdateButton.clicked()
@ -145,7 +154,8 @@ ColumnLayout {
StatusButton { StatusButton {
id: addOrUpdateButton 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.preferredHeight: d.defaultHeight
Layout.topMargin: d.defaultSpacing Layout.topMargin: d.defaultSpacing
Layout.fillWidth: true Layout.fillWidth: true

View File

@ -73,6 +73,9 @@ QtObject {
} }
function setHoldingsTextFormat(type, name, amount) { function setHoldingsTextFormat(type, name, amount) {
if (typeof amount === "string")
amount = AmountsArithmetic.toNumber(AmountsArithmetic.fromString(amount))
switch (type) { switch (type) {
case HoldingTypes.Type.Asset: case HoldingTypes.Type.Asset:
return `${LocaleUtils.numberToLocaleString(amount)} ${name}` return `${LocaleUtils.numberToLocaleString(amount)} ${name}`

View File

@ -22,8 +22,9 @@ QtObject {
property string symbol property string symbol
property string description property string description
property bool infiniteSupply: true property bool infiniteSupply: true
property int supply: 1 property string supply: "1"
property int remainingTokens: supply property string remainingTokens: supply
property int multiplierIndex: 0
// Artwork related properties: // Artwork related properties:
property url artworkSource property url artworkSource

View File

@ -65,7 +65,7 @@ StackView {
readonly property bool isAdminOnly: root.isAdmin && !root.isPrivilegedTokenOwnerProfile readonly property bool isAdminOnly: root.isAdmin && !root.isPrivilegedTokenOwnerProfile
signal selectToken(string key, int amount, int type) signal selectToken(string key, string amount, int type)
signal addAddresses(var addresses) signal addAddresses(var addresses)
} }

View File

@ -66,16 +66,16 @@ StackView {
signal mintOwnerToken(var ownerToken, var tMasterToken) signal mintOwnerToken(var ownerToken, var tMasterToken)
signal deployFeesRequested(int chainId, string accountAddress, int tokenType) 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 remotelyDestructFeesRequest(var remotelyDestructTokensList, // [key , amount] signal remotelyDestructFeesRequest(var remotelyDestructTokensList, // [key , amount]
string tokenKey, string tokenKey,
string accountAddress) string accountAddress)
signal remotelyDestructCollectibles(var remotelyDestructTokensList, // [key , amount] signal remotelyDestructCollectibles(var remotelyDestructTokensList, // [key , amount]
string tokenKey, string tokenKey,
string accountAddress) string accountAddress)
signal signBurnTransactionOpened(string tokenKey, int amount, string accountAddress) signal signBurnTransactionOpened(string tokenKey, string amount, string accountAddress)
signal burnToken(string tokenKey, int amount, string accountAddress) signal burnToken(string tokenKey, string amount, string accountAddress)
signal airdropToken(string tokenKey, int type, var addresses) signal airdropToken(string tokenKey, string amount, int type, var addresses)
signal deleteToken(string tokenKey) signal deleteToken(string tokenKey)
function setFeeLoading() { function setFeeLoading() {
@ -281,6 +281,7 @@ StackView {
property TokenObject asset: TokenObject{ property TokenObject asset: TokenObject{
type: Constants.TokenType.ERC20 type: Constants.TokenType.ERC20
multiplierIndex: 18
} }
property TokenObject collectible: TokenObject { property TokenObject collectible: TokenObject {
@ -520,11 +521,15 @@ StackView {
token: TokenObject {} token: TokenObject {}
onGeneralAirdropRequested: { onGeneralAirdropRequested: {
root.airdropToken(view.airdropKey, view.token.type, []) // tokenKey instead when backend airdrop ready to use key instead of symbol root.airdropToken(view.airdropKey,
"1" + "0".repeat(view.token.multiplierIndex),
view.token.type, []) // tokenKey instead when backend airdrop ready to use key instead of symbol
} }
onAirdropRequested: { onAirdropRequested: {
root.airdropToken(view.airdropKey, view.token.type, [address]) // tokenKey instead when backend airdrop ready to use key instead of symbol root.airdropToken(view.airdropKey,
"1" + "0".repeat(view.token.multiplierIndex),
view.token.type, [address]) // tokenKey instead when backend airdrop ready to use key instead of symbol
} }
onRemoteDestructRequested: { onRemoteDestructRequested: {
@ -609,7 +614,9 @@ StackView {
remotelyDestructVisible: token.remotelyDestruct remotelyDestructVisible: token.remotelyDestruct
burnVisible: !token.infiniteSupply burnVisible: !token.infiniteSupply
onAirdropClicked: root.airdropToken(view.airdropKey, // tokenKey instead when backend airdrop ready to use key instead of symbol onAirdropClicked: root.airdropToken(
view.airdropKey,
"1" + "0".repeat(view.token.multiplierIndex),
view.token.type, []) view.token.type, [])
onRemotelyDestructClicked: remotelyDestructPopup.open() onRemotelyDestructClicked: remotelyDestructPopup.open()
@ -617,7 +624,7 @@ StackView {
// helper properties to pass data through popups // helper properties to pass data through popups
property var remotelyDestructTokensList property var remotelyDestructTokensList
property int burnAmount property string burnAmount
property string accountAddress property string accountAddress
RemotelyDestructPopup { RemotelyDestructPopup {
@ -703,6 +710,7 @@ StackView {
communityName: root.communityName communityName: root.communityName
tokenName: footer.token.name tokenName: footer.token.name
remainingTokens: footer.token.remainingTokens remainingTokens: footer.token.remainingTokens
multiplierIndex: footer.token.multiplierIndex
tokenSource: footer.token.artworkSource tokenSource: footer.token.artworkSource
chainName: footer.token.chainName chainName: footer.token.chainName
@ -786,6 +794,7 @@ StackView {
token.burnState: model.burnState token.burnState: model.burnState
token.remotelyDestructState: model.remotelyDestructState token.remotelyDestructState: model.remotelyDestructState
token.accountAddress: model.accountAddress token.accountAddress: model.accountAddress
token.multiplierIndex: model.multiplierIndex
} }
onCountChanged: { onCountChanged: {

View File

@ -177,7 +177,11 @@ Control {
id: totalbox id: totalbox
label: qsTr("Total") 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 && isLoading: !token.infiniteSupply &&
((!root.isAssetPanel && token.remotelyDestructState === Constants.ContractTransactionStatus.InProgress) || ((!root.isAssetPanel && token.remotelyDestructState === Constants.ContractTransactionStatus.InProgress) ||
(d.burnState === Constants.ContractTransactionStatus.InProgress)) (d.burnState === Constants.ContractTransactionStatus.InProgress))
@ -189,7 +193,11 @@ Control {
readonly property int remainingTokens: root.preview ? token.supply : token.remainingTokens readonly property int remainingTokens: root.preview ? token.supply : token.remainingTokens
label: qsTr("Remaining") 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) isLoading: !token.infiniteSupply && (d.burnState === Constants.ContractTransactionStatus.InProgress)
} }

View File

@ -25,7 +25,8 @@ StatusDialog {
property string communityName property string communityName
property bool isAsset // If asset isAsset = true; if collectible --> isAsset = false property bool isAsset // If asset isAsset = true; if collectible --> isAsset = false
property string tokenName property string tokenName
property int remainingTokens property string remainingTokens
property int multiplierIndex
property url tokenSource property url tokenSource
property string chainName property string chainName
@ -38,13 +39,20 @@ StatusDialog {
// Account expected roles: address, name, color, emoji, walletType // Account expected roles: address, name, color, emoji, walletType
property var accounts property var accounts
signal burnClicked(int burnAmount, string accountAddress) signal burnClicked(string burnAmount, string accountAddress)
signal cancelClicked signal cancelClicked
signal burnFeesRequested(int burnAmount, string accountAddress) signal burnFeesRequested(string burnAmount, string accountAddress)
QtObject { QtObject {
id: d id: d
readonly property real remainingTokensFloat:
SQUtils.AmountsArithmetic.toNumber(
root.remainingTokens, root.multiplierIndex)
readonly property string remainingTokensDisplayText:
LocaleUtils.numberToLocaleString(remainingTokensFloat)
property string accountAddress property string accountAddress
property alias amountToBurn: amountToBurnInput.text property alias amountToBurn: amountToBurnInput.text
readonly property bool isFeeError: root.feeErrorText !== "" readonly property bool isFeeError: root.feeErrorText !== ""
@ -75,7 +83,7 @@ StatusDialog {
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("How many of %1s remaining %n %2 tokens would you like to burn?", "", root.remainingTokens).arg(root.communityName).arg(root.tokenName) text: qsTr("How many of %1s remaining %n %2 tokens would you like to burn?", "", d.remainingTokensFloat).arg(root.communityName).arg(root.tokenName)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
lineHeight: 1.2 lineHeight: 1.2
font.pixelSize: Style.current.primaryTextFontSize font.pixelSize: Style.current.primaryTextFontSize
@ -107,7 +115,19 @@ StatusDialog {
validationMode: StatusInput.ValidationMode.OnlyWhenDirty validationMode: StatusInput.ValidationMode.OnlyWhenDirty
validators: [ validators: [
StatusValidator { 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") errorMessage: qsTr("Exceeds available remaining")
}, },
StatusValidator { StatusValidator {
@ -127,7 +147,7 @@ StatusDialog {
Layout.alignment: Qt.AlignTop 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 font.pixelSize: Style.current.primaryTextFontSize
ButtonGroup.group: radioGroup ButtonGroup.group: radioGroup
} }
@ -173,12 +193,20 @@ StatusDialog {
interval: 500 interval: 500
onTriggered: { onTriggered: {
if(specificAmountButton.checked) if(specificAmountButton.checked) {
root.burnFeesRequested(parseInt(amountToBurnInput.text), d.accountAddress) if (!amountToBurnInput.text)
else return
root.burnFeesRequested(
SQUtils.AmountsArithmetic.fromNumber(
parseInt(amountToBurnInput.text),
root.multiplierIndex),
d.accountAddress)
} else {
root.burnFeesRequested(root.remainingTokens, d.accountAddress) root.burnFeesRequested(root.remainingTokens, d.accountAddress)
} }
} }
}
QtObject { QtObject {
id: singleFeeModel id: singleFeeModel
@ -193,7 +221,7 @@ StatusDialog {
header: StatusDialogHeader { header: StatusDialogHeader {
headline.title: qsTr("Burn %1 tokens").arg(root.tokenName) 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 { leftComponent: Rectangle {
height: 40 height: 40
width: height width: height
@ -236,14 +264,19 @@ StatusDialog {
text: qsTr("Burn tokens") text: qsTr("Burn tokens")
type: StatusBaseButton.Type.Danger type: StatusBaseButton.Type.Danger
onClicked: { onClicked: {
if(specificAmountButton.checked) if(specificAmountButton.checked) {
root.burnClicked(parseInt(amountToBurnInput.text), d.accountAddress) root.burnClicked(
else SQUtils.AmountsArithmetic.fromNumber(
parseInt(amountToBurnInput.text),
root.multiplierIndex),
d.accountAddress)
} else {
root.burnClicked(root.remainingTokens, d.accountAddress) root.burnClicked(root.remainingTokens, d.accountAddress)
} }
} }
} }
} }
}
onOpened: d.initialize() onOpened: d.initialize()
} }

View File

@ -35,20 +35,21 @@ StatusDropdown {
property var usedEnsNames: [] property var usedEnsNames: []
property string assetKey: "" property string assetKey: ""
property real assetAmount: 0 property string assetAmount: "0"
property int assetMultiplierIndex: 0
property string collectibleKey: "" property string collectibleKey: ""
property real collectibleAmount: 1 property string collectibleAmount: "1"
property string ensDomainName: "" property string ensDomainName: ""
property bool showTokenAmount: true property bool showTokenAmount: true
signal addAsset(string key, real amount) signal addAsset(string key, string amount)
signal addCollectible(string key, real amount) signal addCollectible(string key, string amount)
signal addEns(string domain) signal addEns(string domain)
signal updateAsset(string key, real amount) signal updateAsset(string key, string amount)
signal updateCollectible(string key, real amount) signal updateCollectible(string key, string amount)
signal updateEns(string domain) signal updateEns(string domain)
signal removeClicked signal removeClicked
@ -94,8 +95,10 @@ StatusDropdown {
] ]
readonly property var tabsModel: [qsTr("Assets"), qsTr("Collectibles"), qsTr("ENS")] readonly property var tabsModel: [qsTr("Assets"), qsTr("Collectibles"), qsTr("ENS")]
readonly property var tabsModelNoEns: [qsTr("Assets"), qsTr("Collectibles")] 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 readonly property bool ensReady: d.ensDomainNameValid
property int extendedDropdownType: ExtendedDropdownContent.Type.Assets property int extendedDropdownType: ExtendedDropdownContent.Type.Assets
@ -139,8 +142,8 @@ StatusDropdown {
function setDefaultAmounts() { function setDefaultAmounts() {
d.assetAmountText = "" d.assetAmountText = ""
d.collectibleAmountText = "" d.collectibleAmountText = ""
root.assetAmount = 0 root.assetAmount = "0"
root.collectibleAmount = 1 root.collectibleAmount = "1"
} }
function forceLayout() { function forceLayout() {
@ -388,7 +391,8 @@ StatusDropdown {
TokenPanel { TokenPanel {
id: assetPanel 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) tokenName: PermissionsHelpers.getTokenNameByKey(root.assetsModel, root.assetKey)
tokenShortName: PermissionsHelpers.getTokenShortNameByKey(root.assetsModel, root.assetKey) tokenShortName: PermissionsHelpers.getTokenShortNameByKey(root.assetsModel, root.assetKey)
@ -415,9 +419,10 @@ StatusDropdown {
return return
append({ append({
name:chainName, name: chainName,
icon: chainIcon, icon: chainIcon,
amount: asset.supply, amount: asset.supply,
multiplierIndex: asset.multiplierIndex,
infiniteAmount: asset.infiniteSupply infiniteAmount: asset.infiniteSupply
}) })
@ -425,7 +430,12 @@ StatusDropdown {
} }
} }
onEffectiveAmountChanged: root.assetAmount = effectiveAmount onEffectiveAmountChanged: {
if (completed)
root.assetAmount = effectiveAmount
}
onMultiplierIndexChanged: root.assetMultiplierIndex = multiplierIndex
onAmountTextChanged: d.assetAmountText = amountText onAmountTextChanged: d.assetAmountText = amountText
onAddClicked: root.addAsset(root.assetKey, root.assetAmount) onAddClicked: root.addAsset(root.assetKey, root.assetAmount)
onUpdateClicked: root.updateAsset(root.assetKey, root.assetAmount) onUpdateClicked: root.updateAsset(root.assetKey, root.assetAmount)
@ -438,8 +448,11 @@ StatusDropdown {
} }
Component.onCompleted: { Component.onCompleted: {
if (d.assetAmountText.length === 0 && root.assetAmount) completed = true
assetPanel.setAmount(root.assetAmount)
if (d.assetAmountText.length === 0 && root.assetAmount !== "0")
assetPanel.setAmount(root.assetAmount,
root.assetMultiplierIndex)
} }
} }
} }
@ -450,7 +463,8 @@ StatusDropdown {
TokenPanel { TokenPanel {
id: collectiblePanel 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) tokenName: PermissionsHelpers.getTokenNameByKey(root.collectiblesModel, root.collectibleKey)
tokenShortName: "" tokenShortName: ""
@ -482,6 +496,7 @@ StatusDropdown {
name:chainName, name:chainName,
icon: chainIcon, icon: chainIcon,
amount: collectible.supply, amount: collectible.supply,
multiplierIndex: collectible.multiplierIndex,
infiniteAmount: collectible.infiniteSupply infiniteAmount: collectible.infiniteSupply
}) })
@ -489,13 +504,19 @@ StatusDropdown {
} }
} }
onEffectiveAmountChanged: root.collectibleAmount = effectiveAmount onEffectiveAmountChanged: {
if (completed)
root.collectibleAmount = effectiveAmount
}
onAmountTextChanged: d.collectibleAmountText = amountText onAmountTextChanged: d.collectibleAmountText = amountText
onAddClicked: root.addCollectible(root.collectibleKey, root.collectibleAmount) onAddClicked: root.addCollectible(root.collectibleKey, root.collectibleAmount)
onUpdateClicked: root.updateCollectible(root.collectibleKey, root.collectibleAmount) onUpdateClicked: root.updateCollectible(root.collectibleKey, root.collectibleAmount)
onRemoveClicked: root.removeClicked() onRemoveClicked: root.removeClicked()
Component.onCompleted: { Component.onCompleted: {
completed = true
if (d.collectibleAmountText.length === 0 && root.collectibleAmount) if (d.collectibleAmountText.length === 0 && root.collectibleAmount)
collectiblePanel.setAmount(root.collectibleAmount) collectiblePanel.setAmount(root.collectibleAmount)
} }

View File

@ -403,8 +403,8 @@ StatusSectionLayout {
onAirdropToken: { onAirdropToken: {
root.goTo(Constants.CommunitySettingsSections.Airdrops) root.goTo(Constants.CommunitySettingsSections.Airdrops)
// Force a token selection to be airdroped with default amount 1 // Force a token selection to be airdroped with given amount
airdropPanel.selectToken(tokenKey, 1, type) airdropPanel.selectToken(tokenKey, amount, type)
// Set given addresses as recipients // Set given addresses as recipients
airdropPanel.addAddresses(addresses) airdropPanel.addAddresses(addresses)

View File

@ -87,11 +87,9 @@ StatusScrollView {
function selectToken(key, amount, type) { function selectToken(key, amount, type) {
if(selectedHoldingsModel) if(selectedHoldingsModel)
selectedHoldingsModel.clear() selectedHoldingsModel.clear()
var tokenModel = null
if(type === Constants.TokenType.ERC20) const tokenModel = type === Constants.TokenType.ERC20 ?
tokenModel = root.assetsModel root.assetsModel : root.collectiblesModel
else if (type === Constants.TokenType.ERC721)
tokenModel = root.collectiblesModel
const modelItem = PermissionsHelpers.getTokenByKey( const modelItem = PermissionsHelpers.getTokenByKey(
tokenModel, key) tokenModel, key)
@ -178,22 +176,23 @@ StatusScrollView {
onTotalRevisionChanged: Qt.callLater(() => d.resetFees()) onTotalRevisionChanged: Qt.callLater(() => d.resetFees())
function prepareEntry(key, amount, type) { function prepareEntry(key, amount, type) {
let tokenModel = null const tokenModel = type === Constants.TokenType.ERC20
if(type === Constants.TokenType.ERC20) ? root.assetsModel : root.collectiblesModel
tokenModel = root.assetsModel
else if (type === Constants.TokenType.ERC721)
tokenModel = root.collectiblesModel
const modelItem = PermissionsHelpers.getTokenByKey( const modelItem = PermissionsHelpers.getTokenByKey(
tokenModel, key) tokenModel, key)
const multiplierIndex = modelItem.multiplierIndex
const amountNumber = AmountsArithmetic.toNumber(
amount, multiplierIndex)
const amountLocalized = LocaleUtils.numberToLocaleString(
amountNumber, -1)
return { return {
key, amount, type, key, amount, type,
tokenText: amount + " " + modelItem.name, tokenText: amountLocalized + " " + modelItem.name,
tokenImage: modelItem.iconSource, tokenImage: modelItem.iconSource,
networkText: modelItem.chainName, networkText: modelItem.chainName,
networkImage: Style.svg(modelItem.chainIcon), networkImage: Style.svg(modelItem.chainIcon),
supply: modelItem.supply, supply: modelItem.supply,
multiplierIndex: modelItem.multiplierIndex,
infiniteSupply: modelItem.infiniteSupply, infiniteSupply: modelItem.infiniteSupply,
contractUniqueKey: modelItem.contractUniqueKey, contractUniqueKey: modelItem.contractUniqueKey,
accountName: modelItem.accountName, accountName: modelItem.accountName,
@ -278,7 +277,13 @@ StatusScrollView {
if (!item || item.infiniteSupply) if (!item || item.infiniteSupply)
continue 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 infinity = min === Number.MAX_SAFE_INTEGER
@ -286,12 +291,24 @@ StatusScrollView {
} }
delegate: QtObject { delegate: QtObject {
readonly property int supply: model.supply readonly property string supply: model.supply
readonly property real amount: model.amount readonly property string amount: model.amount
readonly property bool infiniteSupply: model.infiniteSupply readonly property bool infiniteSupply: model.infiniteSupply
readonly property bool valid: readonly property bool valid: {
infiniteSupply || amount * airdropRecipientsSelector.count <= supply 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() onSupplyChanged: recipientsCountInstantiator.findRecipientsCount()
onAmountChanged: recipientsCountInstantiator.findRecipientsCount() onAmountChanged: recipientsCountInstantiator.findRecipientsCount()
@ -426,6 +443,7 @@ StatusScrollView {
case HoldingTypes.Type.Asset: case HoldingTypes.Type.Asset:
dropdown.assetKey = modelItem.key dropdown.assetKey = modelItem.key
dropdown.assetAmount = modelItem.amount dropdown.assetAmount = modelItem.amount
dropdown.assetMultiplierIndex = modelItem.multiplierIndex
dropdown.setActiveTab(HoldingTypes.Type.Asset) dropdown.setActiveTab(HoldingTypes.Type.Asset)
break break
case HoldingTypes.Type.Collectible: case HoldingTypes.Type.Collectible:

View File

@ -254,7 +254,13 @@ StatusScrollView {
visible: !unlimitedSupplyChecker.checked visible: !unlimitedSupplyChecker.checked
label: qsTr("Total finite supply") 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") 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)") :
@ -264,14 +270,13 @@ StatusScrollView {
extraValidator.errorMessage: qsTr("Enter a number between 1 and 999,999,999") extraValidator.errorMessage: qsTr("Enter a number between 1 and 999,999,999")
onTextChanged: { onTextChanged: {
const amount = parseInt(text) const supplyNumber = parseInt(text)
if (Number.isNaN(amount) || Object.values(errors).length) if (Number.isNaN(supplyNumber) || Object.values(errors).length)
return return
if(root.isAssetView) const token = root.isAssetView ? root.asset : root.collectible
asset.supply = amount token.supply = SQUtils.AmountsArithmetic.fromNumber(
else supplyNumber, token.multiplierIndex).toFixed(0)
collectible.supply = amount
} }
} }

View File

@ -49,7 +49,7 @@ StatusScrollView {
symbol: PermissionsHelpers.communityNameToSymbol(true, root.communityName) symbol: PermissionsHelpers.communityNameToSymbol(true, root.communityName)
transferable: true transferable: true
remotelyDestruct: false remotelyDestruct: false
supply: 1 supply: "1"
infiniteSupply: false 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) description: qsTr("This is the %1 Owner token. The hodler of this collectible has ultimate control over %1 Community token administration.").arg(root.communityName)
} }

View File

@ -242,7 +242,7 @@ StatusScrollView {
const key = item.key const key = item.key
d.dirtyValues.selectedHoldingsModel.append( d.dirtyValues.selectedHoldingsModel.append(
{ type, key, amount }) { type, key, amount: parseFloat(amount) })
} }
function prepareUpdateIndex(key) { function prepareUpdateIndex(key) {
@ -270,6 +270,7 @@ StatusScrollView {
onAddAsset: { onAddAsset: {
const modelItem = PermissionsHelpers.getTokenByKey( const modelItem = PermissionsHelpers.getTokenByKey(
root.assetsModel, key) root.assetsModel, key)
addItem(HoldingTypes.Type.Asset, modelItem, amount) addItem(HoldingTypes.Type.Asset, modelItem, amount)
dropdown.close() dropdown.close()
} }
@ -277,6 +278,7 @@ StatusScrollView {
onAddCollectible: { onAddCollectible: {
const modelItem = PermissionsHelpers.getTokenByKey( const modelItem = PermissionsHelpers.getTokenByKey(
root.collectiblesModel, key) root.collectiblesModel, key)
addItem(HoldingTypes.Type.Collectible, modelItem, amount) addItem(HoldingTypes.Type.Collectible, modelItem, amount)
dropdown.close() dropdown.close()
} }
@ -292,7 +294,7 @@ StatusScrollView {
const modelItem = PermissionsHelpers.getTokenByKey(root.assetsModel, key) const modelItem = PermissionsHelpers.getTokenByKey(root.assetsModel, key)
d.dirtyValues.selectedHoldingsModel.set( d.dirtyValues.selectedHoldingsModel.set(
itemIndex, { type: HoldingTypes.Type.Asset, key, amount }) itemIndex, { type: HoldingTypes.Type.Asset, key, amount: parseFloat(amount) })
dropdown.close() dropdown.close()
} }
@ -303,7 +305,7 @@ StatusScrollView {
d.dirtyValues.selectedHoldingsModel.set( d.dirtyValues.selectedHoldingsModel.set(
itemIndex, itemIndex,
{ type: HoldingTypes.Type.Collectible, key, amount }) { type: HoldingTypes.Type.Collectible, key, amount: parseFloat(amount) })
dropdown.close() dropdown.close()
} }

View File

@ -161,7 +161,7 @@ StatusScrollView {
id: assetsList id: assetsList
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: contentHeight
visible: count > 0 visible: count > 0
model: assetsModel model: assetsModel

View File

@ -3,6 +3,7 @@ import QtQuick.Layouts 1.14
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import utils 1.0 import utils 1.0
@ -13,11 +14,13 @@ Input {
property var locale: Qt.locale() property var locale: Qt.locale()
readonly property alias amount: d.amount readonly property alias amount: d.amount
property alias multiplierIndex: d.multiplierIndex
readonly property bool valid: validationError.length === 0 readonly property bool valid: validationError.length === 0
property bool allowDecimals: true property bool allowDecimals: true
property bool validateMaximumAmount: false property bool validateMaximumAmount: false
property real maximumAmount: 0 property string maximumAmount: "0"
validationErrorTopMargin: 8 validationErrorTopMargin: 8
fontPixelSize: 13 fontPixelSize: 13
@ -27,18 +30,27 @@ Input {
textField.rightPadding: labelText.implicitWidth + labelText.anchors.rightMargin textField.rightPadding: labelText.implicitWidth + labelText.anchors.rightMargin
+ textField.leftPadding + textField.leftPadding
function setAmount(amount) { function setAmount(amount, multiplierIndex = 0) {
root.text = LocaleUtils.numberToLocaleString(amount, -1, root.locale) 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() onTextChanged: d.validate()
onValidateMaximumAmountChanged: d.validate() onValidateMaximumAmountChanged: d.validate()
onMaximumAmountChanged: d.validate() onMaximumAmountChanged: d.validate()
onMultiplierIndexChanged: d.validate()
QtObject { QtObject {
id: d id: d
property real amount: 0 property string amount: "0"
property int multiplierIndex: 0
function getEffectiveDigitsCount(str) { function getEffectiveDigitsCount(str) {
const digits = LocaleUtils.getLocalizedDigitsCount(text, root.locale) const digits = LocaleUtils.getLocalizedDigitsCount(text, root.locale)
@ -50,7 +62,7 @@ Input {
root.text = root.text.replace(root.locale.decimalPoint, "") root.text = root.text.replace(root.locale.decimalPoint, "")
if(root.text.length === 0) { if(root.text.length === 0) {
d.amount = 0 d.amount = "0"
root.validationError = "" root.validationError = ""
return return
} }
@ -60,16 +72,38 @@ Input {
return return
} }
const amount = LocaleUtils.numberFromLocaleString(root.text, root.locale) const amountNumber = LocaleUtils.numberFromLocaleString(root.text, root.locale)
if (isNaN(amount)) { if (isNaN(amountNumber)) {
d.amount = 0 d.amount = "0"
root.validationError = qsTr("Invalid amount format") root.validationError = qsTr("Invalid amount format")
} else if (root.validateMaximumAmount && amount > root.maximumAmount) { return
root.validationError = qsTr("Amount exceeds balance")
} else {
d.amount = amount
root.validationError = ""
} }
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 = ""
} }
} }

View File

@ -27,9 +27,9 @@ QtObject {
// Minting tokens: // Minting tokens:
function deployCollectible(communityId, collectibleItem) function deployCollectible(communityId, collectibleItem)
{ {
if (collectibleItem.key !== "") { if (collectibleItem.key !== "")
deleteToken(communityId, collectibleItem.key) deleteToken(communityId, collectibleItem.key)
}
const jsonArtworkFile = Utils.getImageAndCropInfoJson(collectibleItem.artworkSource, collectibleItem.artworkCropRect) const jsonArtworkFile = Utils.getImageAndCropInfoJson(collectibleItem.artworkSource, collectibleItem.artworkCropRect)
communityTokensModuleInst.deployCollectible(communityId, collectibleItem.accountAddress, collectibleItem.name, communityTokensModuleInst.deployCollectible(communityId, collectibleItem.accountAddress, collectibleItem.name,
collectibleItem.symbol, collectibleItem.description, collectibleItem.supply, collectibleItem.symbol, collectibleItem.description, collectibleItem.supply,
@ -39,9 +39,9 @@ QtObject {
function deployAsset(communityId, assetItem) function deployAsset(communityId, assetItem)
{ {
if (assetItem.key !== "") { if (assetItem.key !== "")
deleteToken(communityId, assetItem.key) deleteToken(communityId, assetItem.key)
}
const jsonArtworkFile = Utils.getImageAndCropInfoJson(assetItem.artworkSource, assetItem.artworkCropRect) const jsonArtworkFile = Utils.getImageAndCropInfoJson(assetItem.artworkSource, assetItem.artworkCropRect)
communityTokensModuleInst.deployAssets(communityId, assetItem.accountAddress, assetItem.name, communityTokensModuleInst.deployAssets(communityId, assetItem.accountAddress, assetItem.name,
assetItem.symbol, assetItem.description, assetItem.supply, assetItem.symbol, assetItem.description, assetItem.supply,
@ -120,11 +120,13 @@ QtObject {
// Burn: // Burn:
function computeBurnFee(tokenKey, amount, accountAddress) { function computeBurnFee(tokenKey, amount, accountAddress) {
console.assert(typeof amount === "string")
// TODO: Backend. It should include the account address in the calculation. // TODO: Backend. It should include the account address in the calculation.
communityTokensModuleInst.computeBurnFee(tokenKey, amount/*, accountAddress*/) communityTokensModuleInst.computeBurnFee(tokenKey, amount/*, accountAddress*/)
} }
function burnToken(communityId, tokenKey, burnAmount, accountAddress) { function burnToken(communityId, tokenKey, burnAmount, accountAddress) {
console.assert(typeof burnAmount === "string")
// TODO: Backend. It should include the account address in the burn action. // TODO: Backend. It should include the account address in the burn action.
communityTokensModuleInst.burnTokens(communityId, tokenKey, burnAmount/*, accountAddress*/) communityTokensModuleInst.burnTokens(communityId, tokenKey, burnAmount/*, accountAddress*/)
} }