fix(Update fees periodically): Implementing periodic fee update for airdrops, minting, self destruct and burning transactions
This commit is contained in:
parent
f9e7265447
commit
624b758c85
|
@ -100,8 +100,9 @@ const asyncGetRemoteBurnFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimc
|
||||||
"gasTable": tableToJsonArray(gasTable),
|
"gasTable": tableToJsonArray(gasTable),
|
||||||
"chainId": arg.chainId,
|
"chainId": arg.chainId,
|
||||||
"addressFrom": arg.addressFrom,
|
"addressFrom": arg.addressFrom,
|
||||||
|
"error": "",
|
||||||
"requestId": arg.requestId,
|
"requestId": arg.requestId,
|
||||||
"error": "" })
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
arg.finish(%* {
|
arg.finish(%* {
|
||||||
"error": e.msg,
|
"error": e.msg,
|
||||||
|
@ -130,8 +131,9 @@ const asyncGetBurnFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.}
|
||||||
"gasTable": tableToJsonArray(gasTable),
|
"gasTable": tableToJsonArray(gasTable),
|
||||||
"chainId": arg.chainId,
|
"chainId": arg.chainId,
|
||||||
"addressFrom": arg.addressFrom,
|
"addressFrom": arg.addressFrom,
|
||||||
"requestId": arg.requestId,
|
"error": "",
|
||||||
"error": "" })
|
"requestId": arg.requestId
|
||||||
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
arg.finish(%* {
|
arg.finish(%* {
|
||||||
"error": e.msg,
|
"error": e.msg,
|
||||||
|
@ -163,15 +165,16 @@ const asyncGetMintFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.}
|
||||||
arg.walletAddresses, collectibleAndAmount.amount).result.getInt
|
arg.walletAddresses, collectibleAndAmount.amount).result.getInt
|
||||||
gasTable[(chainId, collectibleAndAmount.communityToken.address)] = gas
|
gasTable[(chainId, collectibleAndAmount.communityToken.address)] = gas
|
||||||
arg.finish(%* {
|
arg.finish(%* {
|
||||||
"requestId": arg.requestId,
|
|
||||||
"feeTable": tableToJsonArray(feeTable),
|
"feeTable": tableToJsonArray(feeTable),
|
||||||
"gasTable": tableToJsonArray(gasTable),
|
"gasTable": tableToJsonArray(gasTable),
|
||||||
"addressFrom": arg.addressFrom,
|
"addressFrom": arg.addressFrom,
|
||||||
"error": "" })
|
"error": "",
|
||||||
|
"requestId": arg.requestId
|
||||||
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
let output = %* {
|
let output = %* {
|
||||||
"requestId": arg.requestId,
|
"error": e.msg,
|
||||||
"error": e.msg
|
"requestId": arg.requestId
|
||||||
}
|
}
|
||||||
arg.finish(output)
|
arg.finish(output)
|
||||||
|
|
||||||
|
|
|
@ -656,8 +656,10 @@ QtObject:
|
||||||
)
|
)
|
||||||
self.threadpool.start(arg)
|
self.threadpool.start(arg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
#TODO: handle error - emit error signal
|
|
||||||
error "Error loading airdrop fees", msg = e.msg
|
error "Error loading airdrop fees", msg = e.msg
|
||||||
|
var dataToEmit = AirdropFeesArgs()
|
||||||
|
dataToEmit.errorCode = ComputeFeeErrorCode.Other
|
||||||
|
self.events.emit(SIGNAL_COMPUTE_AIRDROP_FEE, dataToEmit)
|
||||||
|
|
||||||
proc getFiatValue*(self: Service, cryptoBalance: float, cryptoSymbol: string): float =
|
proc getFiatValue*(self: Service, cryptoBalance: float, cryptoSymbol: string): float =
|
||||||
if (cryptoSymbol == ""):
|
if (cryptoSymbol == ""):
|
||||||
|
|
|
@ -68,24 +68,27 @@ SplitView {
|
||||||
|
|
||||||
onBurnClicked: logs.logEvent("BurnTokensPopup::onBurnClicked --> Burn amount: " + burnAmount)
|
onBurnClicked: logs.logEvent("BurnTokensPopup::onBurnClicked --> Burn amount: " + burnAmount)
|
||||||
onCancelClicked: logs.logEvent("BurnTokensPopup::onCancelClicked")
|
onCancelClicked: logs.logEvent("BurnTokensPopup::onCancelClicked")
|
||||||
|
feeText: "0.0015 ETH ($75.43)"
|
||||||
|
feeErrorText: ""
|
||||||
|
isFeeLoading: false
|
||||||
|
|
||||||
onBurnFeesRequested: {
|
onSelectedAccountAddressChanged: {
|
||||||
feeText = ""
|
burnTokensPopup.isFeeLoading = true
|
||||||
feeErrorText = ""
|
timer.delay(2000, () => burnTokensPopup.isFeeLoading = false)
|
||||||
isFeeLoading = true
|
}
|
||||||
|
onAmountToBurnChanged: {
|
||||||
feeCalculationTimer.restart()
|
burnTokensPopup.isFeeLoading = true
|
||||||
|
timer.delay(2000, () => burnTokensPopup.isFeeLoading = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: feeCalculationTimer
|
id: timer
|
||||||
|
function delay(ms, callback) {
|
||||||
interval: 1000
|
timer.interval = ms
|
||||||
|
timer.repeat = false
|
||||||
onTriggered: {
|
timer.triggered.connect(callback)
|
||||||
burnTokensPopup.feeText = "0.0015 ETH ($75.43)"
|
timer.start()
|
||||||
burnTokensPopup.isFeeLoading = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,9 @@ SplitView {
|
||||||
|
|
||||||
token: tokenObject
|
token: tokenObject
|
||||||
tokenOwnersModel: TokenHoldersModel {}
|
tokenOwnersModel: TokenHoldersModel {}
|
||||||
|
feeText: "0.01"
|
||||||
|
feeErrorText: ""
|
||||||
|
isFeeLoading: false
|
||||||
|
|
||||||
accounts: WalletAccountsModel {}
|
accounts: WalletAccountsModel {}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ SplitView {
|
||||||
|
|
||||||
onMintClicked: logs.logEvent("EditOwnerTokenView::onMintClicked")
|
onMintClicked: logs.logEvent("EditOwnerTokenView::onMintClicked")
|
||||||
|
|
||||||
onDeployFeesRequested: {
|
Component.onCompleted: {
|
||||||
feeText = ""
|
feeText = ""
|
||||||
feeErrorText = ""
|
feeErrorText = ""
|
||||||
isFeeLoading = true
|
isFeeLoading = true
|
||||||
|
|
|
@ -30,13 +30,12 @@ SplitView {
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: feesTimer
|
id: timer
|
||||||
|
function delay(delayTime, cb) {
|
||||||
interval: 1000
|
timer.interval = delayTime;
|
||||||
|
timer.repeat = false;
|
||||||
onTriggered: {
|
timer.triggered.connect(cb);
|
||||||
panel.isFeeLoading = false
|
timer.start();
|
||||||
panel.feeText = "0,0002 ETH (123,15 USD)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +47,24 @@ SplitView {
|
||||||
MintTokensSettingsPanel {
|
MintTokensSettingsPanel {
|
||||||
id: panel
|
id: panel
|
||||||
|
|
||||||
|
readonly property var singleTransactionFee: {
|
||||||
|
"ethCurrency": {
|
||||||
|
"objectName":"",
|
||||||
|
"amount":0.000007900500349933282,
|
||||||
|
"symbol":"ETH",
|
||||||
|
"displayDecimals":4,
|
||||||
|
"stripTrailingZeroes":false
|
||||||
|
},
|
||||||
|
"fiatCurrency": {
|
||||||
|
"objectName":"",
|
||||||
|
"amount":0.012852533720433712,
|
||||||
|
"symbol":"USD",
|
||||||
|
"displayDecimals":2,
|
||||||
|
"stripTrailingZeroes":false
|
||||||
|
},
|
||||||
|
"errorCode":0
|
||||||
|
}
|
||||||
|
|
||||||
MintedTokensModel {
|
MintedTokensModel {
|
||||||
id: mintedTokensModel
|
id: mintedTokensModel
|
||||||
}
|
}
|
||||||
|
@ -105,14 +122,9 @@ SplitView {
|
||||||
onMintCollectible: logs.logEvent("CommunityMintTokensSettingsPanel::mintCollectible")
|
onMintCollectible: logs.logEvent("CommunityMintTokensSettingsPanel::mintCollectible")
|
||||||
onMintAsset: logs.logEvent("CommunityMintTokensSettingsPanel::mintAssets")
|
onMintAsset: logs.logEvent("CommunityMintTokensSettingsPanel::mintAssets")
|
||||||
onDeleteToken: logs.logEvent("CommunityMintTokensSettingsPanel::deleteToken: " + tokenKey)
|
onDeleteToken: logs.logEvent("CommunityMintTokensSettingsPanel::deleteToken: " + tokenKey)
|
||||||
|
onRegisterDeployFeesSubscriber: timer.delay(2000, () => feeSubscriber.feesResponse = panel.singleTransactionFee)
|
||||||
onDeployFeesRequested: {
|
onRegisterSelfDestructFeesSubscriber: timer.delay(2000, () => feeSubscriber.feesResponse = panel.singleTransactionFee)
|
||||||
feeText = ""
|
onRegisterBurnTokenFeesSubscriber: timer.delay(2000, () => feeSubscriber.feesResponse = panel.singleTransactionFee)
|
||||||
feeErrorText = ""
|
|
||||||
isFeeLoading = true
|
|
||||||
|
|
||||||
feesTimer.restart()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,12 +74,6 @@ SplitView {
|
||||||
])
|
])
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
onRemotelyDestructFeesRequested: {
|
|
||||||
logs.logEvent("RemoteSelfDestructPopup::onRemotelyDestructFeesRequested",
|
|
||||||
["walletsAndAmounts", "accountAddress"], [
|
|
||||||
JSON.stringify(walletsAndAmounts), accountAddress
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
open()
|
open()
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property string subscriptionId: Utils.uuid()
|
||||||
|
|
||||||
|
property bool isReady: false
|
||||||
|
property int notificationInterval: 3000 // 1 notification every 3 seconds
|
||||||
|
//The topic to subscribe to
|
||||||
|
property string topic: ""
|
||||||
|
property var response: {}
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
//This is a helper component that is used to batch requests and send them periodically
|
||||||
|
//It is used to reduce the number of requests sent to the server and notify different subscribers of the same request
|
||||||
|
//It is used by the Subscription component
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
signal request(string topic)
|
||||||
|
|
||||||
|
signal subscribed(string subscriptionId)
|
||||||
|
signal unsubscribed(string subscriptionId)
|
||||||
|
|
||||||
|
function response(topic, responseObj) {
|
||||||
|
d.onResponse(topic, responseObj)
|
||||||
|
}
|
||||||
|
function subscribe(subscription) {
|
||||||
|
d.subscribe(subscription)
|
||||||
|
}
|
||||||
|
function unsubscribe(subscription) {
|
||||||
|
d.unsubscribe(subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool active: true
|
||||||
|
|
||||||
|
readonly property QtObject d: QtObject {
|
||||||
|
//Mapping subscriptionId to subscription object
|
||||||
|
//subscriptionId is a string and represents the id of the subscription
|
||||||
|
//E.g. "subscriptionId": {subscription: subscriptionObject, topic: "topic"}
|
||||||
|
//The purpose of this mapping is to keep track of the subscriptions and their topics
|
||||||
|
readonly property var managedSubscriptions: ({})
|
||||||
|
|
||||||
|
//Mapping topic to subscriptionIds and request data
|
||||||
|
//topic is a string and represents the topic of the subscription
|
||||||
|
//E.g. "topic": {nextRequestTimestamp: 0, requestInterval: 1000, subscriptions: ["subscriptionId1", "subscriptionId2"], response: null}
|
||||||
|
readonly property var topics: ({})
|
||||||
|
property int topicsCount: 0 //helper property to track change events
|
||||||
|
|
||||||
|
property bool requestIntervalTriggered: false
|
||||||
|
readonly property int requestInterval: {
|
||||||
|
//dependency:
|
||||||
|
d.topicsCount
|
||||||
|
d.requestIntervalTriggered
|
||||||
|
|
||||||
|
const topicInfos = Object.values(d.topics)
|
||||||
|
if(!topicsCount || topicInfos.length === 0)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
const interval = topicInfos.reduce((minInterval, topicInfo) => Math.max(0, Math.min(minInterval, topicInfo.nextRequestTimestamp - now)), Number.MAX_SAFE_INTEGER)
|
||||||
|
|
||||||
|
return interval > 0 ? interval : requestInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property Timer requestTimer: Timer {
|
||||||
|
interval: d.requestInterval
|
||||||
|
repeat: true
|
||||||
|
running: interval > 0 && root.active
|
||||||
|
onTriggered: d.periodicRequest()
|
||||||
|
triggeredOnStart: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribe(subscription) {
|
||||||
|
if(!(subscription instanceof Subscription))
|
||||||
|
return
|
||||||
|
if(d.managedSubscriptions.hasOwnProperty(subscription.subscriptionId))
|
||||||
|
return
|
||||||
|
|
||||||
|
registerToManagedSubscriptions(subscription)
|
||||||
|
connectToSubscriptionEvents(subscription)
|
||||||
|
if(subscription.isReady && subscription.topic)
|
||||||
|
registerToTopic(subscription.topic, subscription.subscriptionId)
|
||||||
|
root.subscribed(subscription.subscriptionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsubscribe(subscriptionId) {
|
||||||
|
if(!subscriptionId || !d.managedSubscriptions.hasOwnProperty(subscriptionId))
|
||||||
|
return
|
||||||
|
|
||||||
|
releaseManagedSubscription(subscriptionId)
|
||||||
|
root.unsubscribed(subscriptionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerToManagedSubscriptions(subscriptionObject) {
|
||||||
|
d.managedSubscriptions[subscriptionObject.subscriptionId] = {
|
||||||
|
subscription: subscriptionObject,
|
||||||
|
topic: subscriptionObject.topic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseManagedSubscription(subscriptionId) {
|
||||||
|
if(!subscriptionId || !d.managedSubscriptions.hasOwnProperty(subscriptionId)) return
|
||||||
|
|
||||||
|
const subscriptionInfo = d.managedSubscriptions[subscriptionId]
|
||||||
|
|
||||||
|
unregisterFromTopic(subscriptionInfo.topic, subscriptionId)
|
||||||
|
delete d.managedSubscriptions[subscriptionId]
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToSubscriptionEvents(subscription) {
|
||||||
|
const subscriptionId = subscription.subscriptionId
|
||||||
|
const topic = subscription.topic
|
||||||
|
|
||||||
|
const onTopicChangeHandler = () => {
|
||||||
|
if(!subscription.isReady || !d.managedSubscriptions.hasOwnProperty(subscriptionId)) return
|
||||||
|
|
||||||
|
const newTopic = subscription.topic
|
||||||
|
const oldTopic = d.managedSubscriptions[subscriptionId].topic
|
||||||
|
|
||||||
|
if(newTopic === oldTopic) return
|
||||||
|
|
||||||
|
d.unregisterFromTopic(oldTopic, subscriptionId)
|
||||||
|
d.registerToTopic(newTopic, subscriptionId)
|
||||||
|
d.managedSubscriptions[subscriptionId].topic = newTopic
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReadyChangeHandler = () => {
|
||||||
|
if(!d.managedSubscriptions.hasOwnProperty(subscriptionId)) return
|
||||||
|
|
||||||
|
if(subscription.isReady) {
|
||||||
|
d.registerToTopic(subscription.topic, subscription.subscriptionId)
|
||||||
|
} else {
|
||||||
|
const subscriptionTopic = d.managedSubscriptions[subscriptionId].topic
|
||||||
|
d.unregisterFromTopic(subscriptionTopic, subscriptionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUnsubscribedHandler = (subscriptionId) => {
|
||||||
|
if(subscriptionId !== subscription.subscriptionId)
|
||||||
|
return
|
||||||
|
|
||||||
|
subscription.Component.onDestruction.disconnect(onDestructionHandler)
|
||||||
|
subscription.isReadyChanged.disconnect(onReadyChangeHandler)
|
||||||
|
subscription.topicChanged.disconnect(onTopicChangeHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDestructionHandler = () => {
|
||||||
|
if(!d.managedSubscriptions.hasOwnProperty(subscriptionId))
|
||||||
|
return
|
||||||
|
|
||||||
|
root.unsubscribed.disconnect(onUnsubscribedHandler) //object is destroyed, no need to listen to the signal anymore
|
||||||
|
unsubscribe(subscriptionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.Component.onDestruction.connect(onDestructionHandler)
|
||||||
|
subscription.isReadyChanged.connect(onReadyChangeHandler)
|
||||||
|
subscription.topicChanged.connect(onTopicChangeHandler)
|
||||||
|
root.unsubscribed.connect(onUnsubscribedHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerToTopic(topic, subscriptionId) {
|
||||||
|
if(!d.topics.hasOwnProperty(topic)) {
|
||||||
|
d.topics[topic] = {
|
||||||
|
requestInterval: d.managedSubscriptions[subscriptionId].subscription.notificationInterval,
|
||||||
|
nextRequestTimestamp: Date.now() + d.managedSubscriptions[subscriptionId].subscription.notificationInterval,
|
||||||
|
subscriptions: [],
|
||||||
|
response: null,
|
||||||
|
requestPending: false
|
||||||
|
}
|
||||||
|
d.topicsCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = d.topics[topic].subscriptions.indexOf(subscriptionId)
|
||||||
|
if(index !== -1) {
|
||||||
|
console.assert("Duplicate subscription: " + subscriptionId + " " + topic)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriptionsCount = d.topics[topic].subscriptions.push(subscriptionId)
|
||||||
|
if(subscriptionsCount === 1 && root.active) {
|
||||||
|
d.request(topic)
|
||||||
|
}
|
||||||
|
d.managedSubscriptions[subscriptionId].subscription.response = d.topics[topic].response
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterFromTopic(topic, subscriptionId) {
|
||||||
|
if(!d.topics.hasOwnProperty(topic)) return
|
||||||
|
|
||||||
|
const index = d.topics[topic].subscriptions.indexOf(subscriptionId)
|
||||||
|
if(index === -1) return
|
||||||
|
|
||||||
|
d.topics[topic].subscriptions.splice(index, 1)
|
||||||
|
if(d.topics[topic].subscriptions.length === 0) {
|
||||||
|
delete d.topics[topic]
|
||||||
|
d.topicsCount--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function periodicRequest() {
|
||||||
|
if(!d.topics || !d.topicsCount) return
|
||||||
|
|
||||||
|
Object.entries(d.topics).forEach(function(entry) {
|
||||||
|
const topic = entry[0]
|
||||||
|
const topicInfo = entry[1]
|
||||||
|
|
||||||
|
if(!topicInfo ||
|
||||||
|
!topicInfo.subscriptions ||
|
||||||
|
!topicInfo.subscriptions.length ||
|
||||||
|
topicInfo.requestPending ||
|
||||||
|
topicInfo.nextRequestTimestamp > Date.now())
|
||||||
|
return
|
||||||
|
|
||||||
|
d.request(topic)
|
||||||
|
})
|
||||||
|
d.requestIntervalTriggered = !d.requestIntervalTriggered
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(topic) {
|
||||||
|
if(!d.topics.hasOwnProperty(topic)) return
|
||||||
|
|
||||||
|
d.topics[topic].requestPending = true
|
||||||
|
d.topics[topic].nextRequestTimestamp = Date.now() + d.topics[topic].requestInterval
|
||||||
|
|
||||||
|
root.request(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResponse(topic, responseObj) {
|
||||||
|
if(!d.topics.hasOwnProperty(topic)) return
|
||||||
|
|
||||||
|
d.topics[topic].response = responseObj
|
||||||
|
d.topics[topic].subscriptions.forEach(function(subscriptionId) {
|
||||||
|
d.managedSubscriptions[subscriptionId].subscription.response = responseObj
|
||||||
|
})
|
||||||
|
d.topics[topic].requestPending = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ ModelChangeTracker 0.1 ModelChangeTracker.qml
|
||||||
ModelsComparator 0.1 ModelsComparator.qml
|
ModelsComparator 0.1 ModelsComparator.qml
|
||||||
StackViewStates 0.1 StackViewStates.qml
|
StackViewStates 0.1 StackViewStates.qml
|
||||||
StatesStack 0.1 StatesStack.qml
|
StatesStack 0.1 StatesStack.qml
|
||||||
|
Subscription 0.1 Subscription.qml
|
||||||
|
SubscriptionBroker 0.1 SubscriptionBroker.qml
|
||||||
XSS 1.0 xss.js
|
XSS 1.0 xss.js
|
||||||
singleton AmountsArithmetic 0.1 AmountsArithmetic.qml
|
singleton AmountsArithmetic 0.1 AmountsArithmetic.qml
|
||||||
singleton Emoji 0.1 Emoji.qml
|
singleton Emoji 0.1 Emoji.qml
|
||||||
|
|
|
@ -226,5 +226,7 @@
|
||||||
<file>StatusQ/Controls/StatusBlockProgressBar.qml</file>
|
<file>StatusQ/Controls/StatusBlockProgressBar.qml</file>
|
||||||
<file>StatusQ/Components/StatusInfoBoxPanel.qml</file>
|
<file>StatusQ/Components/StatusInfoBoxPanel.qml</file>
|
||||||
<file>StatusQ/Controls/StatusWarningBox.qml</file>
|
<file>StatusQ/Controls/StatusWarningBox.qml</file>
|
||||||
|
<file>StatusQ/Core/Utils/Subscription.qml</file>
|
||||||
|
<file>StatusQ/Core/Utils/SubscriptionBroker.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -0,0 +1,277 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtTest 1.0
|
||||||
|
|
||||||
|
import StatusQ.Core.Utils 0.1
|
||||||
|
|
||||||
|
import StatusQ.TestHelpers 0.1
|
||||||
|
|
||||||
|
TestCase {
|
||||||
|
id: testCase
|
||||||
|
name: "SubscriptionBroker"
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: subscriptionBrokerComponent
|
||||||
|
SubscriptionBroker {
|
||||||
|
id: subscriptionBroker
|
||||||
|
|
||||||
|
//Signal spies
|
||||||
|
readonly property SignalSpy requestSignalSpy: SignalSpy {
|
||||||
|
target: subscriptionBroker
|
||||||
|
signalName: "request"
|
||||||
|
}
|
||||||
|
readonly property SignalSpy subscribedSignalSpy: SignalSpy {
|
||||||
|
target: subscriptionBroker
|
||||||
|
signalName: "subscribed"
|
||||||
|
}
|
||||||
|
readonly property SignalSpy unsubscribedSignalSpy: SignalSpy {
|
||||||
|
target: subscriptionBroker
|
||||||
|
signalName: "unsubscribed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: subscriptionComponent
|
||||||
|
Subscription {
|
||||||
|
id: subscription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MonitorQtOutput {
|
||||||
|
id: qtOuput
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
qtOuput.restartCapturing()
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_new_instance() {
|
||||||
|
const subscriptionBroker = createTemporaryObject(subscriptionBrokerComponent, testCase)
|
||||||
|
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||||
|
verify(subscriptionBroker.active, "SubscriptionBroker should be active by default")
|
||||||
|
verify(subscriptionBroker.requestSignalSpy.valid == true, "request signal should be defined")
|
||||||
|
verify(subscriptionBroker.subscribedSignalSpy.valid == true, "subscribed signal should be defined")
|
||||||
|
verify(subscriptionBroker.unsubscribedSignalSpy.valid == true, "unsubscribed signal should be defined")
|
||||||
|
verify(subscriptionBroker.response != undefined, "response function should be defined")
|
||||||
|
verify(subscriptionBroker.subscribe != undefined, "subscribe function should be defined")
|
||||||
|
verify(subscriptionBroker.unsubscribe != undefined, "unsubscribe function should be defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_subscribe_invalid_subscription() {
|
||||||
|
const subscriptionBroker = createTemporaryObject(subscriptionBrokerComponent, testCase)
|
||||||
|
subscriptionBroker.subscribe(undefined)
|
||||||
|
compare(qtOuput.qtOuput().length, 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 0, "request signal should not be emitted")
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 0, "subscribed signal should not be emitted")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.count, 0, "unsubscribed signal should not be emitted")
|
||||||
|
|
||||||
|
const subscriptionAsEmptyObject = {}
|
||||||
|
subscriptionBroker.subscribe(subscriptionAsEmptyObject)
|
||||||
|
|
||||||
|
compare(qtOuput.qtOuput().length, 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 0, "request signal should not be emitted")
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 0, "subscribed signal should not be emitted")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.count, 0, "unsubscribed signal should not be emitted")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_subscribe_valid_subscription_object() {
|
||||||
|
const subscriptionBroker = createTemporaryObject(subscriptionBrokerComponent, testCase)
|
||||||
|
const subscription = createTemporaryObject(subscriptionComponent, testCase)
|
||||||
|
verify(subscription.subscriptionId != "", "subscription should have an id")
|
||||||
|
|
||||||
|
subscriptionBroker.subscribe(subscription)
|
||||||
|
compare(qtOuput.qtOuput().length, 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 1, "subscribed signal should be emitted")
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.signalArguments[0][0], subscription.subscriptionId, "subscribed signal should be emitted with the subscription id")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.count, 0, "unsubscribed signal should not be emitted")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 0, "request signal should not be emitted. Subscription is inactive by default. Broker is inactive by default.")
|
||||||
|
|
||||||
|
subscriptionBroker.unsubscribe(subscription.subscriptionId)
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 1, "subscribed signal should not be emitted for unsunbscribe")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.count, 1, "unsubscribed signal should be emitted")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.signalArguments[0][0], subscription.subscriptionId, "unsubscribed signal should be emitted with the subscription id")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 0, "request signal should not be emitted")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_periodic_request_one_subscription() {
|
||||||
|
const subscriptionBroker = createTemporaryObject(subscriptionBrokerComponent, testCase)
|
||||||
|
const subscription = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic1", isReady: true, notificationInterval: 50})
|
||||||
|
|
||||||
|
//Enable broker and subscription
|
||||||
|
//Verify that request signal is emitted after subscription
|
||||||
|
subscriptionBroker.active = true
|
||||||
|
subscriptionBroker.subscribe(subscription)
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 1, "subscribed signal should be emitted")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 1, "request signal should be emitted")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.signalArguments[0][0], subscription.topic, "request signal should be emitted with the subscription topic")
|
||||||
|
|
||||||
|
//Verify that request signal is emitted after notificationInterval
|
||||||
|
//The broker expects a response before sending another request
|
||||||
|
subscriptionBroker.response(subscription.topic, "responseAsString")
|
||||||
|
compare(subscription.response, "responseAsString", "subscription response should be updated")
|
||||||
|
|
||||||
|
//first interval - check for one request every 50ms:
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 1, "request signal should not be emitted")
|
||||||
|
tryCompare(subscriptionBroker.requestSignalSpy, "count", 2, 90 /*40ms error margin*/, "request signal should be emitted after 50ms. Actual signal count: " + subscriptionBroker.requestSignalSpy.count)
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.signalArguments[1][0], subscription.topic, "request signal should be emitted with the subscription topic")
|
||||||
|
|
||||||
|
subscriptionBroker.response(subscription.topic, "responseAsString2")
|
||||||
|
compare(subscription.response, "responseAsString2", "subscription response should be updated")
|
||||||
|
|
||||||
|
//second interval - check for one request every 50ms:
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 2, "request was emitted before 50ms interval")
|
||||||
|
tryCompare(subscriptionBroker.requestSignalSpy, "count", 3, 90 /*40ms error margin*/, "request signal should be emitted after 50ms")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.signalArguments[2][0], subscription.topic, "request signal should be emitted with the subscription topic")
|
||||||
|
subscriptionBroker.response(subscription.topic, "responseAsString3")
|
||||||
|
|
||||||
|
//Verify the request is not sent again after unsubscribe
|
||||||
|
subscriptionBroker.unsubscribe(subscription.subscriptionId)
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 1, "subscribed signal should not be emitted for unsunbscribe")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.count, 1, "unsubscribed signal should be emitted")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.signalArguments[0][0], subscription.subscriptionId, "unsubscribed signal should be emitted with the subscription id")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 3, "request signal should not be emitted on unsubscribe")
|
||||||
|
wait(90)/*40ms error margin*/
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 3, "request signal should not be emitted again after unsubscribe")
|
||||||
|
|
||||||
|
//Verify the request is not sent again after disabling the broker
|
||||||
|
subscriptionBroker.subscribe(subscription)
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 2, "subscribed signal should be emitted")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 4, "request signal should be emitted on subscribe")
|
||||||
|
subscriptionBroker.response(subscription.topic, "responseAsString4")
|
||||||
|
tryCompare(subscriptionBroker.requestSignalSpy, "count", 5, 90 /*40ms error margin*/, "request signal should be emitted")
|
||||||
|
subscriptionBroker.response(subscription.topic, "responseAsString5")
|
||||||
|
|
||||||
|
subscriptionBroker.active = false
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 5, "request signal should not be emitted after disabling the broker")
|
||||||
|
wait(90)/*40ms error margin*/
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 5, "request signal should not be emitted again after disabling the broker")
|
||||||
|
|
||||||
|
//Verify the request can be unsubsribed with a disabled broker
|
||||||
|
subscriptionBroker.unsubscribe(subscription.subscriptionId)
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 2, "subscribed signal should not be emitted for unsunbscribe")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.count, 2, "unsubscribed signal should be emitted")
|
||||||
|
compare(subscriptionBroker.unsubscribedSignalSpy.signalArguments[1][0], subscription.subscriptionId, "unsubscribed signal should be emitted with the subscription id")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 5, "request signal should not be emitted on unsubscribe")
|
||||||
|
|
||||||
|
//Verify the request can be subscribed with a disabled broker
|
||||||
|
subscriptionBroker.subscribe(subscription)
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 3, "subscribed signal should be emitted")
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 5, "request signal should not be emitted on subscribe")
|
||||||
|
wait(90)/*40ms error margin*/
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 5, "request signal should not be emitted with a disabled broker")
|
||||||
|
|
||||||
|
//verify the request is sent after enabling the broker
|
||||||
|
subscriptionBroker.active = true
|
||||||
|
tryCompare(subscriptionBroker.requestSignalSpy, "count", 6, 1/*allow the event loop to be processed and the subscriptionBroker.active = true to be processed */, "request signal should be emitted after enabling the broker")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_periodic_request_multiple_subscriptions() {
|
||||||
|
const subscriptionBroker = createTemporaryObject(subscriptionBrokerComponent, testCase)
|
||||||
|
const subscription1 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic1", isReady: true, notificationInterval: 50}) //10 requests in 500ms
|
||||||
|
const subscription2 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic2", isReady: true, notificationInterval: 90}) //5 requests in 500ms
|
||||||
|
const subscription3 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic3", isReady: true, notificationInterval: 130}) //3 requests in 500ms
|
||||||
|
const subscription4 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic4", isReady: true, notificationInterval: 170}) //2 requests in 500ms
|
||||||
|
const subscription5 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic5", isReady: true, notificationInterval: 210}) //2 requests in 500ms
|
||||||
|
//TOTAL: 22 requests in 500ms
|
||||||
|
var subscription1RequestTimestamp = 0
|
||||||
|
var subscription2RequestTimestamp = 0
|
||||||
|
var subscription3RequestTimestamp = 0
|
||||||
|
var subscription4RequestTimestamp = 0
|
||||||
|
var subscription5RequestTimestamp = 0
|
||||||
|
|
||||||
|
var requestCount = 0
|
||||||
|
subscriptionBroker.request.connect(function(topic) {
|
||||||
|
//sending a unique response for each request
|
||||||
|
subscriptionBroker.response(topic, "responseAsString" + Date.now())
|
||||||
|
})
|
||||||
|
|
||||||
|
//Enable broker and subscription
|
||||||
|
subscriptionBroker.active = true
|
||||||
|
subscriptionBroker.subscribe(subscription1)
|
||||||
|
subscriptionBroker.subscribe(subscription2)
|
||||||
|
subscriptionBroker.subscribe(subscription3)
|
||||||
|
subscriptionBroker.subscribe(subscription4)
|
||||||
|
subscriptionBroker.subscribe(subscription5)
|
||||||
|
|
||||||
|
//Make sure the interval difference between the subscriptions is at least 30ms
|
||||||
|
//This is to make sure interval computation deffects are not hidden by the error margin
|
||||||
|
//The usual error margin on Timer is < 10 ms. Setting it to 30 ms should be enough
|
||||||
|
const requestIntervalErrorMargin = 30
|
||||||
|
|
||||||
|
subscription1.responseChanged.connect(function() {
|
||||||
|
if(subscription1RequestTimestamp !== 0)
|
||||||
|
fuzzyCompare(Date.now() - subscription1RequestTimestamp, subscription1.notificationInterval, requestIntervalErrorMargin, "subscription1 request should be sent after notificationInterval")
|
||||||
|
|
||||||
|
subscription1RequestTimestamp = Date.now()
|
||||||
|
})
|
||||||
|
subscription2.responseChanged.connect(function() {
|
||||||
|
if(subscription2RequestTimestamp !== 0)
|
||||||
|
fuzzyCompare(Date.now() - subscription2RequestTimestamp, subscription2.notificationInterval, requestIntervalErrorMargin, "subscription2 request should be sent after notificationInterval")
|
||||||
|
|
||||||
|
subscription2RequestTimestamp = Date.now()
|
||||||
|
})
|
||||||
|
subscription3.responseChanged.connect(function() {
|
||||||
|
if(subscription3RequestTimestamp !== 0)
|
||||||
|
fuzzyCompare(Date.now() - subscription3RequestTimestamp, subscription3.notificationInterval, requestIntervalErrorMargin, "subscription3 request should be sent after notificationInterval")
|
||||||
|
|
||||||
|
subscription3RequestTimestamp = Date.now()
|
||||||
|
})
|
||||||
|
subscription4.responseChanged.connect(function() {
|
||||||
|
if(subscription4RequestTimestamp !== 0)
|
||||||
|
fuzzyCompare(Date.now() - subscription4RequestTimestamp, subscription4.notificationInterval, requestIntervalErrorMargin, "subscription4 request should be sent after notificationInterval")
|
||||||
|
|
||||||
|
subscription4RequestTimestamp = Date.now()
|
||||||
|
})
|
||||||
|
subscription5.responseChanged.connect(function() {
|
||||||
|
if(subscription5RequestTimestamp !== 0)
|
||||||
|
fuzzyCompare(Date.now() - subscription5RequestTimestamp, subscription5.notificationInterval, requestIntervalErrorMargin, "subscription5 request should be sent after notificationInterval")
|
||||||
|
|
||||||
|
subscription5RequestTimestamp = Date.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
///Verify the request is sent periodically for 500 ms
|
||||||
|
///The test is fuzzy because the timer is not precise
|
||||||
|
///After each wait() the test error margin increases
|
||||||
|
|
||||||
|
//We should have 27 requests in 500ms. Adding an error margin of 100ms => 600ms total
|
||||||
|
tryVerify(() => subscriptionBroker.requestSignalSpy.count > 26, 600, "request signal should be emitted more than 27 times. Actual: " + subscriptionBroker.requestSignalSpy.count)
|
||||||
|
|
||||||
|
//Disable one subscription and verify the request count is reduced
|
||||||
|
subscription5.isReady = false
|
||||||
|
subscription4.isReady = false
|
||||||
|
|
||||||
|
let previousRequestCount = subscriptionBroker.requestSignalSpy.count
|
||||||
|
|
||||||
|
//We should have 18 requests in 500ms. Adding an error margin of 100ms => 600ms total
|
||||||
|
tryVerify(() => subscriptionBroker.requestSignalSpy.count > previousRequestCount + 17/*fuzzy compare. Exact number should be 18*/, 600, "request signal should be emitted more than 14 times. Actual: " + subscriptionBroker.requestSignalSpy.count)
|
||||||
|
|
||||||
|
previousRequestCount = subscriptionBroker.requestSignalSpy.count
|
||||||
|
|
||||||
|
//Leave just one subscription and verify the request count is reduced
|
||||||
|
subscription3.isReady = false
|
||||||
|
subscription2.isReady = false
|
||||||
|
|
||||||
|
//We should have 10 requests in 500ms. Adding an error margin of 100ms => 600ms total
|
||||||
|
tryVerify(() => subscriptionBroker.requestSignalSpy.count > previousRequestCount + 9 /*fuzzy compare. Exact number should be 10*/, 600, "request signal should be emitted more than 8 times. Actual: " + subscriptionBroker.requestSignalSpy.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Testing how the subscription broker handles the topic changes
|
||||||
|
function test_topic_changes() {
|
||||||
|
const subscriptionBroker = createTemporaryObject(subscriptionBrokerComponent, testCase)
|
||||||
|
const subscription1 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic1", isReady: true, notificationInterval: 50}) //10 requests in 500ms
|
||||||
|
const subscription2 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic2", isReady: true, notificationInterval: 90}) //5 requests in 500ms
|
||||||
|
const subscription3 = createTemporaryObject(subscriptionComponent, testCase, {topic: "topic3", isReady: true, notificationInterval: 130}) //3 requests in 500ms
|
||||||
|
|
||||||
|
subscriptionBroker.active = true
|
||||||
|
subscriptionBroker.subscribe(subscription1)
|
||||||
|
subscriptionBroker.subscribe(subscription2)
|
||||||
|
subscriptionBroker.subscribe(subscription3)
|
||||||
|
|
||||||
|
compare(subscriptionBroker.subscribedSignalSpy.count, 3, "subscribed signal should be emitted")
|
||||||
|
|
||||||
|
subscription1.topic = "topic1Changed"
|
||||||
|
compare(subscriptionBroker.requestSignalSpy.count, 4, "request signal should be emitted after changing the topic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
/*!
|
||||||
|
\qmltype BurnTokenFeesSubscriber
|
||||||
|
\inherits QtObject
|
||||||
|
\brief Helper object that holds the subscriber properties and the published properties for the fee computation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SingleFeeSubscriber {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string tokenKey
|
||||||
|
required property string amount
|
||||||
|
required property string accountAddress
|
||||||
|
required property bool enabled
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
/*!
|
||||||
|
\qmltype DeployFeesSubscriber
|
||||||
|
\inherits QtObject
|
||||||
|
\brief Helper object that holds the subscriber properties and the published properties for the fee computation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SingleFeeSubscriber {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property int chainId
|
||||||
|
required property int tokenType
|
||||||
|
required property bool isOwnerDeployment
|
||||||
|
required property string accountAddress
|
||||||
|
required property bool enabled
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
/*!
|
||||||
|
\qmltype SelfDestructFeesSubscriber
|
||||||
|
\inherits QtObject
|
||||||
|
\brief Helper object that holds the subscriber properties and the published properties for the fee computation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SingleFeeSubscriber {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string tokenKey
|
||||||
|
/**
|
||||||
|
* walletsAndAmounts - array of following structure is expected:
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* walletAddress: string
|
||||||
|
* amount: int
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
required property var walletsAndAmounts
|
||||||
|
required property string accountAddress
|
||||||
|
required property bool enabled
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import utils 1.0
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmltype SingleFeeSubscriber
|
||||||
|
\inherits QtObject
|
||||||
|
\brief Helper object that parses fees response and provides fee text and error text for single fee respnse
|
||||||
|
*/
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
// Published properties
|
||||||
|
property var feesResponse
|
||||||
|
|
||||||
|
// Internal properties based on response
|
||||||
|
readonly property string feeText: {
|
||||||
|
if (!feesResponse || !Object.values(feesResponse.ethCurrency).length || !Object.values(feesResponse.fiatCurrency).length) return ""
|
||||||
|
|
||||||
|
if (feesResponse.errorCode !== Constants.ComputeFeeErrorCode.Success && feesResponse.errorCode !== Constants.ComputeFeeErrorCode.Balance)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return LocaleUtils.currencyAmountToLocaleString(feesResponse.ethCurrency)
|
||||||
|
+ " (" + LocaleUtils.currencyAmountToLocaleString(feesResponse.fiatCurrency) + ")"
|
||||||
|
}
|
||||||
|
readonly property string feeErrorText: {
|
||||||
|
if (!feesResponse) return ""
|
||||||
|
if (feesResponse.errorCode === Constants.ComputeFeeErrorCode.Success) return ""
|
||||||
|
|
||||||
|
if (feesResponse.errorCode === Constants.ComputeFeeErrorCode.Balance)
|
||||||
|
return qsTr("Not enough funds to make transaction")
|
||||||
|
|
||||||
|
if (feesResponse.errorCode === Constants.ComputeFeeErrorCode.Infura)
|
||||||
|
return qsTr("Infura error")
|
||||||
|
|
||||||
|
return qsTr("Unknown error")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import shared.stores 1.0
|
||||||
|
import utils 1.0
|
||||||
|
|
||||||
|
import StatusQ.Core.Utils 0.1
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
enum FeeType {
|
||||||
|
Airdrop,
|
||||||
|
Deploy,
|
||||||
|
SelfDestruct,
|
||||||
|
Burn
|
||||||
|
}
|
||||||
|
|
||||||
|
property CommunityTokensStore communityTokensStore
|
||||||
|
|
||||||
|
property QtObject d: QtObject {
|
||||||
|
id: internal
|
||||||
|
|
||||||
|
component AirdropFeeSubscription: Subscription {
|
||||||
|
required property AirdropFeesSubscriber subscriber
|
||||||
|
readonly property var requestArgs: ({
|
||||||
|
type: TransactionFeesBroker.FeeType.Airdrop,
|
||||||
|
communityId: subscriber.communityId,
|
||||||
|
contractKeysAndAmounts: subscriber.contractKeysAndAmounts,
|
||||||
|
addressesToAirdrop: subscriber.addressesToAirdrop,
|
||||||
|
feeAccountAddress: subscriber.feeAccountAddress
|
||||||
|
})
|
||||||
|
|
||||||
|
isReady: !!subscriber.communityId &&
|
||||||
|
!!subscriber.contractKeysAndAmounts &&
|
||||||
|
!!subscriber.addressesToAirdrop &&
|
||||||
|
!!subscriber.feeAccountAddress &&
|
||||||
|
subscriber.contractKeysAndAmounts.length &&
|
||||||
|
subscriber.addressesToAirdrop.length &&
|
||||||
|
subscriber.enabled
|
||||||
|
|
||||||
|
topic: isReady ? JSON.stringify(requestArgs) : ""
|
||||||
|
onResponseChanged: subscriber.airdropFeesResponse = response
|
||||||
|
}
|
||||||
|
|
||||||
|
component DeployFeeSubscription: Subscription {
|
||||||
|
required property DeployFeesSubscriber subscriber
|
||||||
|
readonly property var requestArgs: ({
|
||||||
|
type: TransactionFeesBroker.FeeType.Deploy,
|
||||||
|
chainId: subscriber.chainId,
|
||||||
|
accountAddress: subscriber.accountAddress,
|
||||||
|
tokenType: subscriber.tokenType,
|
||||||
|
isOwnerDeployment: subscriber.isOwnerDeployment
|
||||||
|
})
|
||||||
|
|
||||||
|
isReady: !!subscriber.chainId &&
|
||||||
|
!!subscriber.accountAddress &&
|
||||||
|
!!subscriber.tokenType &&
|
||||||
|
subscriber.enabled
|
||||||
|
|
||||||
|
topic: isReady ? JSON.stringify(requestArgs) : ""
|
||||||
|
onResponseChanged: subscriber.feesResponse = response
|
||||||
|
}
|
||||||
|
|
||||||
|
component SelfDestructFeeSubscription: Subscription {
|
||||||
|
required property SelfDestructFeesSubscriber subscriber
|
||||||
|
readonly property var requestArgs: ({
|
||||||
|
type: TransactionFeesBroker.FeeType.SelfDestruct,
|
||||||
|
walletsAndAmounts:subscriber.walletsAndAmounts,
|
||||||
|
tokenKey: subscriber.tokenKey,
|
||||||
|
accountAddress: subscriber.accountAddress,
|
||||||
|
})
|
||||||
|
isReady: !!subscriber.walletsAndAmounts &&
|
||||||
|
!!subscriber.tokenKey &&
|
||||||
|
!!subscriber.accountAddress &&
|
||||||
|
subscriber.walletsAndAmounts.length &&
|
||||||
|
subscriber.enabled
|
||||||
|
|
||||||
|
topic: isReady ? JSON.stringify(requestArgs) : ""
|
||||||
|
onResponseChanged: subscriber.feesResponse = response
|
||||||
|
}
|
||||||
|
|
||||||
|
component BurnTokenFeeSubscription: Subscription {
|
||||||
|
required property BurnTokenFeesSubscriber subscriber
|
||||||
|
readonly property var requestArgs: ({
|
||||||
|
type: TransactionFeesBroker.FeeType.Burn,
|
||||||
|
tokenKey: subscriber.tokenKey,
|
||||||
|
amount: subscriber.amount,
|
||||||
|
accountAddress: subscriber.accountAddress
|
||||||
|
})
|
||||||
|
isReady: !!subscriber.tokenKey &&
|
||||||
|
!!subscriber.amount &&
|
||||||
|
!!subscriber.accountAddress &&
|
||||||
|
subscriber.enabled
|
||||||
|
|
||||||
|
topic: isReady ? JSON.stringify(requestArgs) : ""
|
||||||
|
onResponseChanged: subscriber.feesResponse = response
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property Component airdropFeeSubscriptionComponent: AirdropFeeSubscription {}
|
||||||
|
readonly property Component deployFeeSubscriptionComponent: DeployFeeSubscription {}
|
||||||
|
readonly property Component selfDestructFeeSubscriptionComponent: SelfDestructFeeSubscription {}
|
||||||
|
readonly property Component burnFeeSubscriptionComponent: BurnTokenFeeSubscription {}
|
||||||
|
|
||||||
|
readonly property SubscriptionBroker feesBroker: SubscriptionBroker {
|
||||||
|
active: Global.applicationWindow.active
|
||||||
|
onRequest: internal.computeFee(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
property Connections communityTokensStoreConnections: Connections {
|
||||||
|
target: communityTokensStore
|
||||||
|
|
||||||
|
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
|
||||||
|
d.feesBroker.response(responseId, { ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode })
|
||||||
|
}
|
||||||
|
function onAirdropFeeUpdated(response) {
|
||||||
|
d.feesBroker.response(response.requestId, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
|
||||||
|
d.feesBroker.response(responseId, { ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBurnFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
|
||||||
|
d.feesBroker.response(responseId, { ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeFee(topic) {
|
||||||
|
const args = JSON.parse(topic)
|
||||||
|
switch (args.type) {
|
||||||
|
case TransactionFeesBroker.FeeType.Airdrop:
|
||||||
|
computeAirdropFee(args, topic)
|
||||||
|
break
|
||||||
|
case TransactionFeesBroker.FeeType.Deploy:
|
||||||
|
computeDeployFee(args, topic)
|
||||||
|
break
|
||||||
|
case TransactionFeesBroker.FeeType.SelfDestruct:
|
||||||
|
computeSelfDestructFee(args, topic)
|
||||||
|
break
|
||||||
|
case TransactionFeesBroker.FeeType.Burn:
|
||||||
|
computeBurnFee(args, topic)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.error("Unknown fee type: " + args.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeAirdropFee(args, topic) {
|
||||||
|
communityTokensStore.computeAirdropFee(
|
||||||
|
args.communityId,
|
||||||
|
args.contractKeysAndAmounts,
|
||||||
|
args.addressesToAirdrop,
|
||||||
|
args.feeAccountAddress,
|
||||||
|
topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeDeployFee(args, topic) {
|
||||||
|
communityTokensStore.computeDeployFee(args.chainId, args.accountAddress, args.tokenType, args.isOwnerDeployment, topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeSelfDestructFee(args, topic) {
|
||||||
|
communityTokensStore.computeSelfDestructFee(args.walletsAndAmounts, args.tokenKey, args.accountAddress, topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeBurnFee(args, topic) {
|
||||||
|
console.assert(typeof args.amount === "string")
|
||||||
|
communityTokensStore.computeBurnFee(args.tokenKey, args.amount, args.accountAddress, topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerAirdropFeesSubscriber(subscriberObj) {
|
||||||
|
const subscription = d.airdropFeeSubscriptionComponent.createObject(subscriberObj, { subscriber: subscriberObj })
|
||||||
|
d.feesBroker.subscribe(subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerDeployFeesSubscriber(subscriberObj) {
|
||||||
|
const subscription = d.deployFeeSubscriptionComponent.createObject(subscriberObj, { subscriber: subscriberObj })
|
||||||
|
d.feesBroker.subscribe(subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerSelfDestructFeesSubscriber(subscriberObj) {
|
||||||
|
const subscription = d.selfDestructFeeSubscriptionComponent.createObject(subscriberObj, { subscriber: subscriberObj })
|
||||||
|
d.feesBroker.subscribe(subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerBurnFeesSubscriber(subscriberObj) {
|
||||||
|
const subscription = d.burnFeeSubscriptionComponent.createObject(subscriberObj, { subscriber: subscriberObj })
|
||||||
|
d.feesBroker.subscribe(subscription)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,8 @@
|
||||||
singleton PermissionsHelpers 1.0 PermissionsHelpers.qml
|
singleton PermissionsHelpers 1.0 PermissionsHelpers.qml
|
||||||
|
AirdropFeesSubscriber 1.0 AirdropFeesSubscriber.qml
|
||||||
|
BurnTokenFeesSubscriber 1.0 BurnTokenFeesSubscriber.qml
|
||||||
|
DeployFeesSubscriber 1.0 DeployFeesSubscriber.qml
|
||||||
|
SelfDestructFeesSubscriber 1.0 SelfDestructFeesSubscriber.qml
|
||||||
|
SingleFeeSubscriber 1.0 SingleFeeSubscriber.qml
|
||||||
TokenObject 1.0 TokenObject.qml
|
TokenObject 1.0 TokenObject.qml
|
||||||
|
TransactionFeesBroker 1.0 TransactionFeesBroker.qml
|
||||||
|
|
|
@ -20,7 +20,6 @@ StackView {
|
||||||
required property bool isTokenMasterOwner
|
required property bool isTokenMasterOwner
|
||||||
required property bool isAdmin
|
required property bool isAdmin
|
||||||
readonly property bool isPrivilegedTokenOwnerProfile: root.isOwner || root.isTokenMasterOwner
|
readonly property bool isPrivilegedTokenOwnerProfile: root.isOwner || root.isTokenMasterOwner
|
||||||
readonly property alias airdropFeesSubscriber: d.aidropFeeSubscriber
|
|
||||||
|
|
||||||
// Owner and TMaster token related properties:
|
// Owner and TMaster token related properties:
|
||||||
readonly property bool arePrivilegedTokensDeployed: root.isOwnerTokenDeployed && root.isTMasterTokenDeployed
|
readonly property bool arePrivilegedTokensDeployed: root.isOwnerTokenDeployed && root.isTMasterTokenDeployed
|
||||||
|
@ -38,8 +37,8 @@ StackView {
|
||||||
property string previousPageName: depth > 1 ? qsTr("Airdrops") : ""
|
property string previousPageName: depth > 1 ? qsTr("Airdrops") : ""
|
||||||
|
|
||||||
signal airdropClicked(var airdropTokens, var addresses, string feeAccountAddress)
|
signal airdropClicked(var airdropTokens, var addresses, string feeAccountAddress)
|
||||||
signal airdropFeesRequested(var contractKeysAndAmounts, var addresses, string feeAccountAddress)
|
|
||||||
signal navigateToMintTokenSettings(bool isAssetType)
|
signal navigateToMintTokenSettings(bool isAssetType)
|
||||||
|
signal registerAirdropFeeSubscriber(var feeSubscriber)
|
||||||
|
|
||||||
function navigateBack() {
|
function navigateBack() {
|
||||||
pop(StackView.Immediate)
|
pop(StackView.Immediate)
|
||||||
|
@ -132,7 +131,6 @@ StackView {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
d.selectToken.connect(view.selectToken)
|
d.selectToken.connect(view.selectToken)
|
||||||
d.addAddresses.connect(view.addAddresses)
|
d.addAddresses.connect(view.addAddresses)
|
||||||
d.aidropFeeSubscriber = feesSubscriber
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AirdropFeesSubscriber {
|
AirdropFeesSubscriber {
|
||||||
|
@ -142,6 +140,7 @@ StackView {
|
||||||
contractKeysAndAmounts: view.selectedContractKeysAndAmounts
|
contractKeysAndAmounts: view.selectedContractKeysAndAmounts
|
||||||
addressesToAirdrop: view.selectedAddressesToAirdrop
|
addressesToAirdrop: view.selectedAddressesToAirdrop
|
||||||
feeAccountAddress: view.selectedFeeAccount
|
feeAccountAddress: view.selectedFeeAccount
|
||||||
|
Component.onCompleted: root.registerAirdropFeeSubscriber(feesSubscriber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,11 +50,6 @@ StackView {
|
||||||
property var tokensModelWallet
|
property var tokensModelWallet
|
||||||
property var accounts // Expected roles: address, name, color, emoji, walletType
|
property var accounts // Expected roles: address, name, color, emoji, walletType
|
||||||
|
|
||||||
// Transaction related properties:
|
|
||||||
property string feeText
|
|
||||||
property string feeErrorText
|
|
||||||
property bool isFeeLoading: true
|
|
||||||
|
|
||||||
// Network related properties:
|
// Network related properties:
|
||||||
property var layer1Networks
|
property var layer1Networks
|
||||||
property var layer2Networks
|
property var layer2Networks
|
||||||
|
@ -67,25 +62,15 @@ StackView {
|
||||||
|
|
||||||
signal kickUserRequested(string contactId)
|
signal kickUserRequested(string contactId)
|
||||||
signal banUserRequested(string contactId)
|
signal banUserRequested(string contactId)
|
||||||
|
|
||||||
signal deployFeesRequested(int chainId, string accountAddress, int tokenType)
|
|
||||||
signal burnFeesRequested(string tokenKey, string amount, string accountAddress)
|
|
||||||
signal remotelyDestructFeesRequest(var walletsAndAmounts, // { [walletAddress (string), amount (int)] }
|
|
||||||
string tokenKey,
|
|
||||||
string accountAddress)
|
|
||||||
signal remotelyDestructCollectibles(var walletsAndAmounts, // { [walletAddress (string), amount (int)] }
|
signal remotelyDestructCollectibles(var walletsAndAmounts, // { [walletAddress (string), amount (int)] }
|
||||||
string tokenKey,
|
string tokenKey,
|
||||||
string accountAddress)
|
string accountAddress)
|
||||||
signal signBurnTransactionOpened(string tokenKey, string amount, string accountAddress)
|
|
||||||
signal burnToken(string tokenKey, string amount, string accountAddress)
|
signal burnToken(string tokenKey, string amount, string accountAddress)
|
||||||
signal airdropToken(string tokenKey, string amount, int type, var addresses)
|
signal airdropToken(string tokenKey, string amount, int type, var addresses)
|
||||||
signal deleteToken(string tokenKey)
|
signal deleteToken(string tokenKey)
|
||||||
|
signal registerDeployFeesSubscriber(var feeSubscriber)
|
||||||
function setFeeLoading() {
|
signal registerSelfDestructFeesSubscriber(var feeSubscriber)
|
||||||
root.isFeeLoading = true
|
signal registerBurnTokenFeesSubscriber(var feeSubscriber)
|
||||||
root.feeText = ""
|
|
||||||
root.feeErrorText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateBack() {
|
function navigateBack() {
|
||||||
pop(StackView.Immediate)
|
pop(StackView.Immediate)
|
||||||
|
@ -138,7 +123,6 @@ StackView {
|
||||||
root.push(ownerTokenEditViewComponent, properties,
|
root.push(ownerTokenEditViewComponent, properties,
|
||||||
StackView.Immediate)
|
StackView.Immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialItem: SettingsPage {
|
initialItem: SettingsPage {
|
||||||
|
@ -243,31 +227,36 @@ StackView {
|
||||||
allNetworks: root.allNetworks
|
allNetworks: root.allNetworks
|
||||||
accounts: root.accounts
|
accounts: root.accounts
|
||||||
|
|
||||||
|
feeText: feeSubscriber.feeText
|
||||||
|
feeErrorText: feeSubscriber.feeErrorText
|
||||||
|
isFeeLoading: !feeSubscriber.feesResponse
|
||||||
|
|
||||||
onMintClicked: signMintPopup.open()
|
onMintClicked: signMintPopup.open()
|
||||||
|
|
||||||
onDeployFeesRequested: root.deployFeesRequested(
|
DeployFeesSubscriber {
|
||||||
ownerToken.chainId,
|
id: feeSubscriber
|
||||||
ownerToken.accountAddress,
|
chainId: editOwnerTokenView.ownerToken.chainId
|
||||||
Constants.TokenType.ERC721)
|
tokenType: editOwnerTokenView.ownerToken.type
|
||||||
|
isOwnerDeployment: editOwnerTokenView.ownerToken.isPrivilegedToken
|
||||||
feeText: root.feeText
|
accountAddress: editOwnerTokenView.ownerToken.accountAddress
|
||||||
feeErrorText: root.feeErrorText
|
enabled: editOwnerTokenView.visible || signMintPopup.visible
|
||||||
isFeeLoading: root.isFeeLoading
|
Component.onCompleted: root.registerDeployFeesSubscriber(feeSubscriber)
|
||||||
|
}
|
||||||
|
|
||||||
SignMultiTokenTransactionsPopup {
|
SignMultiTokenTransactionsPopup {
|
||||||
id: signMintPopup
|
id: signMintPopup
|
||||||
|
|
||||||
title: qsTr("Sign transaction - Mint %1 tokens").arg(
|
title: qsTr("Sign transaction - Mint %1 tokens").arg(
|
||||||
editOwnerTokenView.communityName)
|
editOwnerTokenView.communityName)
|
||||||
totalFeeText: root.isFeeLoading ?
|
totalFeeText: editOwnerTokenView.isFeeLoading ?
|
||||||
"" : root.feeText
|
"" : editOwnerTokenView.feeText
|
||||||
errorText: root.feeErrorText
|
errorText: editOwnerTokenView.feeErrorText
|
||||||
accountName: editOwnerTokenView.ownerToken.accountName
|
accountName: editOwnerTokenView.ownerToken.accountName
|
||||||
|
|
||||||
model: QtObject {
|
model: QtObject {
|
||||||
readonly property string title: editOwnerTokenView.feeLabel
|
readonly property string title: editOwnerTokenView.feeLabel
|
||||||
readonly property string feeText: signMintPopup.totalFeeText
|
readonly property string feeText: signMintPopup.totalFeeText
|
||||||
readonly property bool error: root.feeErrorText !== ""
|
readonly property bool error: editOwnerTokenView.feeErrorText !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
onSignTransactionClicked: editOwnerTokenView.signMintTransaction()
|
onSignTransactionClicked: editOwnerTokenView.signMintTransaction()
|
||||||
|
@ -335,7 +324,7 @@ StackView {
|
||||||
validationMode: !newTokenPage.isAssetView
|
validationMode: !newTokenPage.isAssetView
|
||||||
? newTokenPage.validationMode
|
? newTokenPage.validationMode
|
||||||
: StatusInput.ValidationMode.OnlyWhenDirty
|
: StatusInput.ValidationMode.OnlyWhenDirty
|
||||||
collectible: newTokenPage.collectible
|
token: newTokenPage.collectible
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomEditCommunityTokenView {
|
CustomEditCommunityTokenView {
|
||||||
|
@ -345,10 +334,12 @@ StackView {
|
||||||
validationMode: newTokenPage.isAssetView
|
validationMode: newTokenPage.isAssetView
|
||||||
? newTokenPage.validationMode
|
? newTokenPage.validationMode
|
||||||
: StatusInput.ValidationMode.OnlyWhenDirty
|
: StatusInput.ValidationMode.OnlyWhenDirty
|
||||||
asset: newTokenPage.asset
|
token: newTokenPage.asset
|
||||||
}
|
}
|
||||||
|
|
||||||
component CustomEditCommunityTokenView: EditCommunityTokenView {
|
component CustomEditCommunityTokenView: EditCommunityTokenView {
|
||||||
|
id: editView
|
||||||
|
|
||||||
viewWidth: root.viewWidth
|
viewWidth: root.viewWidth
|
||||||
layer1Networks: root.layer1Networks
|
layer1Networks: root.layer1Networks
|
||||||
layer2Networks: root.layer2Networks
|
layer2Networks: root.layer2Networks
|
||||||
|
@ -361,28 +352,27 @@ StackView {
|
||||||
referenceName: newTokenPage.referenceName
|
referenceName: newTokenPage.referenceName
|
||||||
referenceSymbol: newTokenPage.referenceSymbol
|
referenceSymbol: newTokenPage.referenceSymbol
|
||||||
|
|
||||||
feeText: root.feeText
|
feeText: deployFeeSubscriber.feeText
|
||||||
feeErrorText: root.feeErrorText
|
feeErrorText: deployFeeSubscriber.feeErrorText
|
||||||
isFeeLoading: root.isFeeLoading
|
isFeeLoading: !deployFeeSubscriber.feesResponse
|
||||||
|
|
||||||
onPreviewClicked: {
|
onPreviewClicked: {
|
||||||
const properties = {
|
const properties = {
|
||||||
token: isAssetView ? asset : collectible
|
token: token
|
||||||
}
|
}
|
||||||
|
|
||||||
root.push(previewTokenViewComponent, properties,
|
root.push(previewTokenViewComponent, properties,
|
||||||
StackView.Immediate)
|
StackView.Immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeployFeesRequested: {
|
DeployFeesSubscriber {
|
||||||
if (isAssetView)
|
id: deployFeeSubscriber
|
||||||
root.deployFeesRequested(asset.chainId,
|
chainId: editView.token.chainId
|
||||||
asset.accountAddress,
|
tokenType: editView.token.type
|
||||||
Constants.TokenType.ERC20)
|
isOwnerDeployment: editView.token.isPrivilegedToken
|
||||||
else
|
accountAddress: editView.token.accountAddress
|
||||||
root.deployFeesRequested(collectible.chainId,
|
enabled: editView.visible
|
||||||
collectible.accountAddress,
|
Component.onCompleted: root.registerDeployFeesSubscriber(deployFeeSubscriber)
|
||||||
Constants.TokenType.ERC721)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,20 +397,14 @@ StackView {
|
||||||
viewWidth: root.viewWidth
|
viewWidth: root.viewWidth
|
||||||
preview: true
|
preview: true
|
||||||
|
|
||||||
feeText: root.feeText
|
|
||||||
feeErrorText: root.feeErrorText
|
|
||||||
isFeeLoading: root.isFeeLoading
|
|
||||||
accounts: root.accounts
|
accounts: root.accounts
|
||||||
|
feeText: feeSubscriber.feeText
|
||||||
onDeployFeesRequested: root.deployFeesRequested(
|
feeErrorText: feeSubscriber.feeErrorText
|
||||||
token.chainId, token.accountAddress,
|
isFeeLoading: !feeSubscriber.feesResponse
|
||||||
token.type)
|
|
||||||
|
|
||||||
onMintClicked: signMintPopup.open()
|
onMintClicked: signMintPopup.open()
|
||||||
|
|
||||||
function signMintTransaction() {
|
function signMintTransaction() {
|
||||||
root.setFeeLoading()
|
|
||||||
|
|
||||||
if(preview.isAssetView)
|
if(preview.isAssetView)
|
||||||
root.mintAsset(token)
|
root.mintAsset(token)
|
||||||
else
|
else
|
||||||
|
@ -429,18 +413,28 @@ StackView {
|
||||||
root.resetNavigation()
|
root.resetNavigation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeployFeesSubscriber {
|
||||||
|
id: feeSubscriber
|
||||||
|
chainId: preview.token.chainId
|
||||||
|
tokenType: preview.token.type
|
||||||
|
isOwnerDeployment: preview.token.isPrivilegedToken
|
||||||
|
accountAddress: preview.token.accountAddress
|
||||||
|
enabled: preview.visible || signMintPopup.visible
|
||||||
|
Component.onCompleted: root.registerDeployFeesSubscriber(feeSubscriber)
|
||||||
|
}
|
||||||
|
|
||||||
SignMultiTokenTransactionsPopup {
|
SignMultiTokenTransactionsPopup {
|
||||||
id: signMintPopup
|
id: signMintPopup
|
||||||
|
|
||||||
title: qsTr("Sign transaction - Mint %1 token").arg(
|
title: qsTr("Sign transaction - Mint %1 token").arg(
|
||||||
preview.token.name)
|
preview.token.name)
|
||||||
totalFeeText: root.isFeeLoading ? "" : root.feeText
|
totalFeeText: preview.isFeeLoading ? "" : preview.feeText
|
||||||
accountName: preview.token.accountName
|
accountName: preview.token.accountName
|
||||||
|
|
||||||
model: QtObject {
|
model: QtObject {
|
||||||
readonly property string title: preview.feeLabel
|
readonly property string title: preview.feeLabel
|
||||||
readonly property string feeText: signMintPopup.totalFeeText
|
readonly property string feeText: signMintPopup.totalFeeText
|
||||||
readonly property bool error: root.feeErrorText !== ""
|
readonly property bool error: preview.feeErrorText !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
onSignTransactionClicked: preview.signMintTransaction()
|
onSignTransactionClicked: preview.signMintTransaction()
|
||||||
|
@ -452,12 +446,11 @@ StackView {
|
||||||
component TokenViewPage: SettingsPage {
|
component TokenViewPage: SettingsPage {
|
||||||
id: tokenViewPage
|
id: tokenViewPage
|
||||||
|
|
||||||
readonly property alias token: view.token
|
property TokenObject token: TokenObject {}
|
||||||
readonly property bool deploymentFailed: view.deployState === Constants.ContractTransactionStatus.Failed
|
readonly property bool deploymentFailed: view.deployState === Constants.ContractTransactionStatus.Failed
|
||||||
|
|
||||||
property alias tokenOwnersModel: view.tokenOwnersModel
|
property var tokenOwnersModel
|
||||||
property alias airdropKey: view.airdropKey
|
property string airdropKey
|
||||||
|
|
||||||
// Owner and TMaster related props
|
// Owner and TMaster related props
|
||||||
readonly property bool isPrivilegedTokenItem: isOwnerTokenItem || isTMasterTokenItem
|
readonly property bool isPrivilegedTokenItem: isOwnerTokenItem || isTMasterTokenItem
|
||||||
readonly property bool isOwnerTokenItem: token.privilegesLevel === Constants.TokenPrivilegesLevel.Owner
|
readonly property bool isOwnerTokenItem: token.privilegesLevel === Constants.TokenPrivilegesLevel.Owner
|
||||||
|
@ -517,11 +510,12 @@ StackView {
|
||||||
contentItem: CommunityTokenView {
|
contentItem: CommunityTokenView {
|
||||||
id: view
|
id: view
|
||||||
|
|
||||||
property string airdropKey // TO REMOVE: Temporal property until airdrop backend is not ready to use token key instead of symbol
|
property string airdropKey: tokenViewPage.airdropKey // TO REMOVE: Temporal property until airdrop backend is not ready to use token key instead of symbol
|
||||||
|
|
||||||
viewWidth: root.viewWidth
|
viewWidth: root.viewWidth
|
||||||
|
|
||||||
token: TokenObject {}
|
token: tokenViewPage.token
|
||||||
|
tokenOwnersModel: tokenViewPage.tokenOwnersModel
|
||||||
|
|
||||||
onGeneralAirdropRequested: {
|
onGeneralAirdropRequested: {
|
||||||
root.airdropToken(view.airdropKey,
|
root.airdropToken(view.airdropKey,
|
||||||
|
@ -538,7 +532,7 @@ StackView {
|
||||||
onRemoteDestructRequested: {
|
onRemoteDestructRequested: {
|
||||||
if (token.isPrivilegedToken) {
|
if (token.isPrivilegedToken) {
|
||||||
tokenMasterActionPopup.openPopup(
|
tokenMasterActionPopup.openPopup(
|
||||||
TokenMasterActionPopup.ActionType.RemotelyDestruct, name)
|
TokenMasterActionPopup.ActionType.RemotelyDestruct, name, address)
|
||||||
} else {
|
} else {
|
||||||
remotelyDestructPopup.open()
|
remotelyDestructPopup.open()
|
||||||
// TODO: set the address selected in the popup's list
|
// TODO: set the address selected in the popup's list
|
||||||
|
@ -572,16 +566,34 @@ StackView {
|
||||||
TokenMasterActionPopup {
|
TokenMasterActionPopup {
|
||||||
id: tokenMasterActionPopup
|
id: tokenMasterActionPopup
|
||||||
|
|
||||||
|
property string address: ""
|
||||||
|
|
||||||
communityName: root.communityName
|
communityName: root.communityName
|
||||||
networkName: view.token.chainName
|
networkName: view.token.chainName
|
||||||
|
|
||||||
accountsModel: root.accounts
|
accountsModel: root.accounts
|
||||||
|
feeText: selfDestructFeesSubscriber.feeText
|
||||||
|
feeErrorText: selfDestructFeesSubscriber.feeErrorText
|
||||||
|
isFeeLoading: !selfDestructFeesSubscriber.feesResponse
|
||||||
|
|
||||||
function openPopup(type, userName) {
|
function openPopup(type, userName, address) {
|
||||||
tokenMasterActionPopup.actionType = type
|
tokenMasterActionPopup.actionType = type
|
||||||
tokenMasterActionPopup.userName = userName
|
tokenMasterActionPopup.userName = userName
|
||||||
|
tokenMasterActionPopup.address = address
|
||||||
open()
|
open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelfDestructFeesSubscriber {
|
||||||
|
id: selfDestructFeesSubscriber
|
||||||
|
walletsAndAmounts: [{
|
||||||
|
walletAddress: tokenMasterActionPopup.address,
|
||||||
|
amount: 1
|
||||||
|
}]
|
||||||
|
accountAddress: tokenMasterActionPopup.selectedAccount
|
||||||
|
tokenKey: view.token.key
|
||||||
|
enabled: tokenMasterActionPopup.opened
|
||||||
|
Component.onCompleted: root.registerSelfDestructFeesSubscriber(selfDestructFeesSubscriber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KickBanPopup {
|
KickBanPopup {
|
||||||
|
@ -661,20 +673,16 @@ StackView {
|
||||||
RemotelyDestructPopup {
|
RemotelyDestructPopup {
|
||||||
id: remotelyDestructPopup
|
id: remotelyDestructPopup
|
||||||
|
|
||||||
|
property alias feeSubscriber: remotelyDestructFeeSubscriber
|
||||||
|
|
||||||
collectibleName: view.token.name
|
collectibleName: view.token.name
|
||||||
model: view.tokenOwnersModel || null
|
model: view.tokenOwnersModel || null
|
||||||
accounts: root.accounts
|
accounts: root.accounts
|
||||||
chainName: view.token.chainName
|
chainName: view.token.chainName
|
||||||
|
|
||||||
feeText: root.feeText
|
feeText: remotelyDestructFeeSubscriber.feeText
|
||||||
isFeeLoading: root.isFeeLoading
|
feeErrorText: remotelyDestructFeeSubscriber.feeErrorText
|
||||||
feeErrorText: root.feeErrorText
|
isFeeLoading: !remotelyDestructFeeSubscriber.feesResponse
|
||||||
|
|
||||||
onRemotelyDestructFeesRequested: {
|
|
||||||
root.remotelyDestructFeesRequest(walletsAndAmounts,
|
|
||||||
view.token.key,
|
|
||||||
accountAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
onRemotelyDestructClicked: {
|
onRemotelyDestructClicked: {
|
||||||
remotelyDestructPopup.close()
|
remotelyDestructPopup.close()
|
||||||
|
@ -682,6 +690,15 @@ StackView {
|
||||||
footer.walletsAndAmounts = walletsAndAmounts
|
footer.walletsAndAmounts = walletsAndAmounts
|
||||||
alertPopup.open()
|
alertPopup.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelfDestructFeesSubscriber {
|
||||||
|
id: remotelyDestructFeeSubscriber
|
||||||
|
walletsAndAmounts: remotelyDestructPopup.selectedWalletsAndAmounts
|
||||||
|
accountAddress: remotelyDestructPopup.selectedAccount
|
||||||
|
tokenKey: view.token.key
|
||||||
|
enabled: remotelyDestructPopup.tokenCount > 0 && accountAddress !== "" && (remotelyDestructPopup.opened || signTransactionPopup.opened)
|
||||||
|
Component.onCompleted: root.registerSelfDestructFeesSubscriber(remotelyDestructFeeSubscriber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertPopup {
|
AlertPopup {
|
||||||
|
@ -702,11 +719,13 @@ StackView {
|
||||||
id: signTransactionPopup
|
id: signTransactionPopup
|
||||||
|
|
||||||
property bool isRemotelyDestructTransaction
|
property bool isRemotelyDestructTransaction
|
||||||
|
property var feeSubscriber: isRemotelyDestructTransaction
|
||||||
|
? remotelyDestructPopup.feeSubscriber
|
||||||
|
: burnTokensPopup.feeSubscriber
|
||||||
|
|
||||||
readonly property string tokenKey: tokenViewPage.token.key
|
readonly property string tokenKey: tokenViewPage.token.key
|
||||||
|
|
||||||
function signTransaction() {
|
function signTransaction() {
|
||||||
root.setFeeLoading()
|
|
||||||
|
|
||||||
if(signTransactionPopup.isRemotelyDestructTransaction)
|
if(signTransactionPopup.isRemotelyDestructTransaction)
|
||||||
root.remotelyDestructCollectibles(footer.walletsAndAmounts,
|
root.remotelyDestructCollectibles(footer.walletsAndAmounts,
|
||||||
tokenKey, footer.accountAddress)
|
tokenKey, footer.accountAddress)
|
||||||
|
@ -723,23 +742,17 @@ StackView {
|
||||||
tokenName: footer.token.name
|
tokenName: footer.token.name
|
||||||
accountName: footer.token.accountName
|
accountName: footer.token.accountName
|
||||||
networkName: footer.token.chainName
|
networkName: footer.token.chainName
|
||||||
feeText: root.feeText
|
feeText: feeSubscriber.feeText
|
||||||
isFeeLoading: root.isFeeLoading
|
isFeeLoading: feeSubscriber.feeText === "" && feeSubscriber.feeErrorText === ""
|
||||||
errorText: root.feeErrorText
|
errorText: feeSubscriber.feeErrorText
|
||||||
|
|
||||||
onOpened: {
|
|
||||||
root.setFeeLoading()
|
|
||||||
|
|
||||||
signTransactionPopup.isRemotelyDestructTransaction
|
|
||||||
? root.remotelyDestructFeesRequest(footer.walletsAndAmounts, tokenKey, footer.accountAddress)
|
|
||||||
: root.signBurnTransactionOpened(tokenKey, footer.burnAmount, footer.accountAddress)
|
|
||||||
}
|
|
||||||
onSignTransactionClicked: signTransaction()
|
onSignTransactionClicked: signTransaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
BurnTokensPopup {
|
BurnTokensPopup {
|
||||||
id: burnTokensPopup
|
id: burnTokensPopup
|
||||||
|
|
||||||
|
property alias feeSubscriber: burnTokensFeeSubscriber
|
||||||
communityName: root.communityName
|
communityName: root.communityName
|
||||||
tokenName: footer.token.name
|
tokenName: footer.token.name
|
||||||
remainingTokens: footer.token.remainingTokens
|
remainingTokens: footer.token.remainingTokens
|
||||||
|
@ -747,12 +760,12 @@ StackView {
|
||||||
tokenSource: footer.token.artworkSource
|
tokenSource: footer.token.artworkSource
|
||||||
chainName: footer.token.chainName
|
chainName: footer.token.chainName
|
||||||
|
|
||||||
feeText: root.feeText
|
onAmountToBurnChanged: burnTokensFeeSubscriber.updateAmount()
|
||||||
feeErrorText: root.feeErrorText
|
|
||||||
isFeeLoading: root.isFeeLoading
|
|
||||||
accounts: root.accounts
|
|
||||||
|
|
||||||
onBurnFeesRequested: root.burnFeesRequested(footer.token.key, burnAmount, accountAddress)
|
feeText: burnTokensFeeSubscriber.feeText
|
||||||
|
feeErrorText: burnTokensFeeSubscriber.feeErrorText
|
||||||
|
isFeeLoading: burnTokensFeeSubscriber.feeText === "" && burnTokensFeeSubscriber.feeErrorText === ""
|
||||||
|
accounts: root.accounts
|
||||||
|
|
||||||
onBurnClicked: {
|
onBurnClicked: {
|
||||||
burnTokensPopup.close()
|
burnTokensPopup.close()
|
||||||
|
@ -761,13 +774,25 @@ StackView {
|
||||||
signTransactionPopup.isRemotelyDestructTransaction = false
|
signTransactionPopup.isRemotelyDestructTransaction = false
|
||||||
signTransactionPopup.open()
|
signTransactionPopup.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BurnTokenFeesSubscriber {
|
||||||
|
id: burnTokensFeeSubscriber
|
||||||
|
readonly property var updateAmount: Backpressure.debounce(burnTokensFeeSubscriber, 500, () => {
|
||||||
|
burnTokensFeeSubscriber.amount = burnTokensPopup.amountToBurn
|
||||||
|
})
|
||||||
|
amount: ""
|
||||||
|
tokenKey: tokenViewPage.token.key
|
||||||
|
accountAddress: burnTokensPopup.selectedAccountAddress
|
||||||
|
enabled: burnTokensPopup.visible || signTransactionPopup.visible
|
||||||
|
Component.onCompleted: root.registerBurnTokenFeesSubscriber(burnTokensFeeSubscriber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertPopup {
|
AlertPopup {
|
||||||
id: deleteTokenAlertPopup
|
id: deleteTokenAlertPopup
|
||||||
|
|
||||||
readonly property alias tokenName: view.token.name
|
readonly property string tokenName: view.token.name
|
||||||
|
|
||||||
width: 521
|
width: 521
|
||||||
title: qsTr("Delete %1").arg(tokenName)
|
title: qsTr("Delete %1").arg(tokenName)
|
||||||
|
@ -799,6 +824,7 @@ StackView {
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: TokenViewPage {
|
delegate: TokenViewPage {
|
||||||
|
required property var model
|
||||||
implicitWidth: 0
|
implicitWidth: 0
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,9 @@ StatusDialog {
|
||||||
property url tokenSource
|
property url tokenSource
|
||||||
property string chainName
|
property string chainName
|
||||||
|
|
||||||
|
readonly property alias amountToBurn: d.amountToBurn
|
||||||
|
readonly property alias selectedAccountAddress: d.accountAddress
|
||||||
|
|
||||||
// Fees related properties:
|
// Fees related properties:
|
||||||
property string feeText
|
property string feeText
|
||||||
property string feeErrorText: ""
|
property string feeErrorText: ""
|
||||||
|
@ -38,7 +41,6 @@ StatusDialog {
|
||||||
|
|
||||||
signal burnClicked(string burnAmount, string accountAddress)
|
signal burnClicked(string burnAmount, string accountAddress)
|
||||||
signal cancelClicked
|
signal cancelClicked
|
||||||
signal burnFeesRequested(string burnAmount, string accountAddress)
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
@ -51,6 +53,8 @@ StatusDialog {
|
||||||
LocaleUtils.numberToLocaleString(remainingTokensFloat)
|
LocaleUtils.numberToLocaleString(remainingTokensFloat)
|
||||||
|
|
||||||
property string accountAddress
|
property string accountAddress
|
||||||
|
property string amountToBurn: !isFormValid ? "" :
|
||||||
|
specificAmountButton.checked ? amountInput.amount : root.remainingTokens
|
||||||
|
|
||||||
readonly property bool isFeeError: root.feeErrorText !== ""
|
readonly property bool isFeeError: root.feeErrorText !== ""
|
||||||
|
|
||||||
|
@ -119,7 +123,7 @@ StatusDialog {
|
||||||
font.pixelSize: Style.current.primaryTextFontSize
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
ButtonGroup.group: radioGroup
|
ButtonGroup.group: radioGroup
|
||||||
|
|
||||||
onToggled: if(checked) amountToBurnInput.forceActiveFocus()
|
onToggled: if(checked) amountInput.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
AmountInput {
|
AmountInput {
|
||||||
|
@ -164,18 +168,6 @@ StatusDialog {
|
||||||
|
|
||||||
FeesBox {
|
FeesBox {
|
||||||
id: feesBox
|
id: feesBox
|
||||||
|
|
||||||
readonly property bool triggerFeeReevaluation: {
|
|
||||||
specificAmountButton.checked
|
|
||||||
amountInput.amount
|
|
||||||
feesBox.accountsSelector.currentIndex
|
|
||||||
|
|
||||||
if (root.opened)
|
|
||||||
requestFeeDelayTimer.restart()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
placeholderText: qsTr("Choose number of tokens to burn to see gas fees")
|
placeholderText: qsTr("Choose number of tokens to burn to see gas fees")
|
||||||
|
@ -195,24 +187,6 @@ StatusDialog {
|
||||||
d.accountAddress = item.address
|
d.accountAddress = item.address
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: requestFeeDelayTimer
|
|
||||||
|
|
||||||
interval: 500
|
|
||||||
onTriggered: {
|
|
||||||
if (specificAmountButton.checked) {
|
|
||||||
if (!amountInput.valid)
|
|
||||||
return
|
|
||||||
|
|
||||||
root.burnFeesRequested(amountInput.amount,
|
|
||||||
d.accountAddress)
|
|
||||||
} else {
|
|
||||||
root.burnFeesRequested(root.remainingTokens,
|
|
||||||
d.accountAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: singleFeeModel
|
id: singleFeeModel
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,17 @@ StatusDialog {
|
||||||
property string feeLabel: qsTr("Remotely destruct %1 token on %2").arg(root.collectibleName).arg(root.chainName)
|
property string feeLabel: qsTr("Remotely destruct %1 token on %2").arg(root.collectibleName).arg(root.chainName)
|
||||||
|
|
||||||
readonly property alias tokenCount: d.tokenCount
|
readonly property alias tokenCount: d.tokenCount
|
||||||
|
readonly property string selectedAccount: d.accountAddress
|
||||||
|
readonly property var selectedWalletsAndAmounts: {
|
||||||
|
//depedency
|
||||||
|
d.tokenCount
|
||||||
|
return ModelUtils.modelToArray(d.walletsAndAmountsList)
|
||||||
|
}
|
||||||
|
|
||||||
// Account expected roles: address, name, color, emoji, walletType
|
// Account expected roles: address, name, color, emoji, walletType
|
||||||
property var accounts
|
property var accounts
|
||||||
|
|
||||||
signal remotelyDestructClicked(var walletsAndAmounts, string accountAddress)
|
signal remotelyDestructClicked(var walletsAndAmounts, string accountAddress)
|
||||||
signal remotelyDestructFeesRequested(var walletsAndAmounts, string accountAddress)
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
@ -74,17 +80,6 @@ StatusDialog {
|
||||||
d.walletsAndAmountsList, "amount")
|
d.walletsAndAmountsList, "amount")
|
||||||
const sum = amounts.reduce((a, b) => a + b, 0)
|
const sum = amounts.reduce((a, b) => a + b, 0)
|
||||||
d.tokenCount = sum
|
d.tokenCount = sum
|
||||||
|
|
||||||
if (sum > 0)
|
|
||||||
sendFeeRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendFeeRequest() {
|
|
||||||
const walletsAndAmounts = ModelUtils.modelToArray(
|
|
||||||
d.walletsAndAmountsList)
|
|
||||||
|
|
||||||
root.remotelyDestructFeesRequested(walletsAndAmounts,
|
|
||||||
d.accountAddress)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,10 +121,6 @@ StatusDialog {
|
||||||
const item = ModelUtils.get(accountsSelector.model,
|
const item = ModelUtils.get(accountsSelector.model,
|
||||||
accountsSelector.currentIndex)
|
accountsSelector.currentIndex)
|
||||||
d.accountAddress = item.address
|
d.accountAddress = item.address
|
||||||
|
|
||||||
// Whenever a change in the form happens, new fee calculation:
|
|
||||||
if (d.tokenCount > 0)
|
|
||||||
d.sendFeeRequest()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import StatusQ.Core 0.1
|
||||||
import StatusQ.Controls 0.1
|
import StatusQ.Controls 0.1
|
||||||
import StatusQ.Popups.Dialog 0.1
|
import StatusQ.Popups.Dialog 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Core.Utils 0.1
|
||||||
|
|
||||||
import AppLayouts.Communities.panels 1.0
|
import AppLayouts.Communities.panels 1.0
|
||||||
|
|
||||||
|
@ -40,7 +41,10 @@ StatusDialog {
|
||||||
property string feeText
|
property string feeText
|
||||||
property string feeErrorText
|
property string feeErrorText
|
||||||
property bool isFeeLoading
|
property bool isFeeLoading
|
||||||
|
|
||||||
|
|
||||||
property var accountsModel
|
property var accountsModel
|
||||||
|
readonly property alias selectedAccount: d.accountAddress
|
||||||
|
|
||||||
readonly property string feeLabel: qsTr("Remotely destruct 1 TokenMaster token on %1").arg(
|
readonly property string feeLabel: qsTr("Remotely destruct 1 TokenMaster token on %1").arg(
|
||||||
root.networkName)
|
root.networkName)
|
||||||
|
@ -143,6 +147,15 @@ StatusDialog {
|
||||||
"" : root.feeText
|
"" : root.feeText
|
||||||
readonly property bool error: root.feeErrorText !== ""
|
readonly property bool error: root.feeErrorText !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountsSelector.onCurrentIndexChanged: {
|
||||||
|
if (accountsSelector.currentIndex < 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
const item = ModelUtils.get(accountsSelector.model,
|
||||||
|
accountsSelector.currentIndex)
|
||||||
|
d.accountAddress = item.address
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +169,6 @@ StatusDialog {
|
||||||
}
|
}
|
||||||
StatusButton {
|
StatusButton {
|
||||||
enabled: !root.isFeeLoading && root.feeErrorText === ""
|
enabled: !root.isFeeLoading && root.feeErrorText === ""
|
||||||
&& root.feeText !== ""
|
|
||||||
text: {
|
text: {
|
||||||
if (root.actionType === TokenMasterActionPopup.ActionType.Ban)
|
if (root.actionType === TokenMasterActionPopup.ActionType.Ban)
|
||||||
return qsTr("Ban %1 and remotely destruct 1 token").arg(root.userName)
|
return qsTr("Ban %1 and remotely destruct 1 token").arg(root.userName)
|
||||||
|
@ -179,4 +191,10 @@ StatusDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
property string accountAddress: ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,29 +309,6 @@ StatusSectionLayout {
|
||||||
readonly property CommunityTokensStore communityTokensStore:
|
readonly property CommunityTokensStore communityTokensStore:
|
||||||
rootStore.communityTokensStore
|
rootStore.communityTokensStore
|
||||||
|
|
||||||
function setFeesInfo(ethCurrency, fiatCurrency, errorCode) {
|
|
||||||
if (errorCode === Constants.ComputeFeeErrorCode.Success
|
|
||||||
|| errorCode === Constants.ComputeFeeErrorCode.Balance) {
|
|
||||||
|
|
||||||
const valueStr = LocaleUtils.currencyAmountToLocaleString(ethCurrency)
|
|
||||||
+ "(" + LocaleUtils.currencyAmountToLocaleString(fiatCurrency) + ")"
|
|
||||||
mintPanel.feeText = valueStr
|
|
||||||
|
|
||||||
if (errorCode === Constants.ComputeFeeErrorCode.Balance)
|
|
||||||
mintPanel.feeErrorText = qsTr("Not enough funds to make transaction")
|
|
||||||
|
|
||||||
mintPanel.isFeeLoading = false
|
|
||||||
|
|
||||||
return
|
|
||||||
} else if (errorCode === Constants.ComputeFeeErrorCode.Infura) {
|
|
||||||
mintPanel.feeErrorText = qsTr("Infura error")
|
|
||||||
mintPanel.isFeeLoading = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mintPanel.feeErrorText = qsTr("Unknown error")
|
|
||||||
mintPanel.isFeeLoading = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// General community props
|
// General community props
|
||||||
communityName: root.community.name
|
communityName: root.community.name
|
||||||
communityLogo: root.community.image
|
communityLogo: root.community.image
|
||||||
|
@ -356,22 +333,11 @@ StatusSectionLayout {
|
||||||
allNetworks: communityTokensStore.allNetworks
|
allNetworks: communityTokensStore.allNetworks
|
||||||
accounts: root.walletAccountsModel
|
accounts: root.walletAccountsModel
|
||||||
|
|
||||||
onDeployFeesRequested: {
|
onRegisterDeployFeesSubscriber: d.feesBroker.registerDeployFeesSubscriber(feeSubscriber)
|
||||||
feeText = ""
|
|
||||||
feeErrorText = ""
|
|
||||||
isFeeLoading = true
|
|
||||||
|
|
||||||
communityTokensStore.computeDeployFee(
|
onRegisterSelfDestructFeesSubscriber: d.feesBroker.registerSelfDestructFeesSubscriber(feeSubscriber)
|
||||||
chainId, accountAddress, tokenType, !(mintPanel.isOwnerTokenDeployed && mintPanel.isTMasterTokenDeployed))
|
|
||||||
}
|
|
||||||
|
|
||||||
onBurnFeesRequested: {
|
onRegisterBurnTokenFeesSubscriber: d.feesBroker.registerBurnFeesSubscriber(feeSubscriber)
|
||||||
feeText = ""
|
|
||||||
feeErrorText = ""
|
|
||||||
isFeeLoading = true
|
|
||||||
|
|
||||||
communityTokensStore.computeBurnFee(tokenKey, amount, accountAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMintCollectible:
|
onMintCollectible:
|
||||||
communityTokensStore.deployCollectible(
|
communityTokensStore.deployCollectible(
|
||||||
|
@ -384,17 +350,10 @@ StatusSectionLayout {
|
||||||
communityTokensStore.deployOwnerToken(
|
communityTokensStore.deployOwnerToken(
|
||||||
root.community.id, ownerToken, tMasterToken)
|
root.community.id, ownerToken, tMasterToken)
|
||||||
|
|
||||||
onRemotelyDestructFeesRequest:
|
|
||||||
communityTokensStore.computeSelfDestructFee(
|
|
||||||
walletsAndAmounts, tokenKey, accountAddress)
|
|
||||||
|
|
||||||
onRemotelyDestructCollectibles:
|
onRemotelyDestructCollectibles:
|
||||||
communityTokensStore.remoteSelfDestructCollectibles(
|
communityTokensStore.remoteSelfDestructCollectibles(
|
||||||
root.community.id, walletsAndAmounts, tokenKey, accountAddress)
|
root.community.id, walletsAndAmounts, tokenKey, accountAddress)
|
||||||
|
|
||||||
onSignBurnTransactionOpened:
|
|
||||||
communityTokensStore.computeBurnFee(tokenKey, amount, accountAddress)
|
|
||||||
|
|
||||||
onBurnToken:
|
onBurnToken:
|
||||||
communityTokensStore.burnToken(root.community.id, tokenKey, amount, accountAddress)
|
communityTokensStore.burnToken(root.community.id, tokenKey, amount, accountAddress)
|
||||||
|
|
||||||
|
@ -524,7 +483,6 @@ StatusSectionLayout {
|
||||||
membersModel: community.members
|
membersModel: community.members
|
||||||
|
|
||||||
accountsModel: root.walletAccountsModel
|
accountsModel: root.walletAccountsModel
|
||||||
|
|
||||||
onAirdropClicked: communityTokensStore.airdrop(
|
onAirdropClicked: communityTokensStore.airdrop(
|
||||||
root.community.id, airdropTokens, addresses,
|
root.community.id, airdropTokens, addresses,
|
||||||
feeAccountAddress)
|
feeAccountAddress)
|
||||||
|
@ -534,10 +492,7 @@ StatusSectionLayout {
|
||||||
mintPanel.openNewTokenForm(isAssetType)
|
mintPanel.openNewTokenForm(isAssetType)
|
||||||
}
|
}
|
||||||
|
|
||||||
onAirdropFeesRequested:
|
onRegisterAirdropFeeSubscriber: d.feesBroker.registerAirdropFeesSubscriber(feeSubscriber)
|
||||||
communityTokensStore.computeAirdropFee(
|
|
||||||
root.community.id, contractKeysAndAmounts, addresses,
|
|
||||||
feeAccountAddress)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,6 +511,10 @@ StatusSectionLayout {
|
||||||
readonly property bool tokenMaster: root.community.memberRole === Constants.memberRole.tokenMaster
|
readonly property bool tokenMaster: root.community.memberRole === Constants.memberRole.tokenMaster
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property TransactionFeesBroker feesBroker: TransactionFeesBroker {
|
||||||
|
communityTokensStore: root.rootStore.communityTokensStore
|
||||||
|
}
|
||||||
|
|
||||||
function goTo(section: int, subSection: int) {
|
function goTo(section: int, subSection: int) {
|
||||||
const stackContent = stackLayout.children
|
const stackContent = stackLayout.children
|
||||||
|
|
||||||
|
@ -705,22 +664,6 @@ StatusSectionLayout {
|
||||||
Connections {
|
Connections {
|
||||||
target: rootStore.communityTokensStore
|
target: rootStore.communityTokensStore
|
||||||
|
|
||||||
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
|
||||||
mintPanel.setFeesInfo(ethCurrency, fiatCurrency, errorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
|
||||||
mintPanel.setFeesInfo(ethCurrency, fiatCurrency, errorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBurnFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
|
||||||
mintPanel.setFeesInfo(ethCurrency, fiatCurrency, errorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAirdropFeeUpdated(airdropFees) {
|
|
||||||
airdropPanel.airdropFees = airdropFees
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRemoteDestructStateChanged(communityId, tokenName, status, url) {
|
function onRemoteDestructStateChanged(communityId, tokenName, status, url) {
|
||||||
if (root.community.id !== communityId)
|
if (root.community.id !== communityId)
|
||||||
return
|
return
|
||||||
|
|
|
@ -24,6 +24,10 @@ StatusScrollView {
|
||||||
|
|
||||||
// https://bugreports.qt.io/browse/QTBUG-84269
|
// https://bugreports.qt.io/browse/QTBUG-84269
|
||||||
/* required */ property TokenObject token
|
/* required */ property TokenObject token
|
||||||
|
/* required */ property string feeText
|
||||||
|
/* required */ property string feeErrorText
|
||||||
|
/* required */ property bool isFeeLoading
|
||||||
|
|
||||||
|
|
||||||
readonly property bool isAssetView: token.type === Constants.TokenType.ERC20
|
readonly property bool isAssetView: token.type === Constants.TokenType.ERC20
|
||||||
|
|
||||||
|
@ -58,10 +62,6 @@ StatusScrollView {
|
||||||
|
|
||||||
// Required for preview mode:
|
// Required for preview mode:
|
||||||
property var accounts
|
property var accounts
|
||||||
property string feeText
|
|
||||||
property string feeErrorText
|
|
||||||
property bool isFeeLoading
|
|
||||||
|
|
||||||
signal mintClicked()
|
signal mintClicked()
|
||||||
|
|
||||||
signal airdropRequested(string address)
|
signal airdropRequested(string address)
|
||||||
|
@ -74,8 +74,6 @@ StatusScrollView {
|
||||||
signal kickRequested(string name, string contactId)
|
signal kickRequested(string name, string contactId)
|
||||||
signal banRequested(string name, string contactId)
|
signal banRequested(string name, string contactId)
|
||||||
|
|
||||||
signal deployFeesRequested
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
|
||||||
|
@ -179,8 +177,6 @@ StatusScrollView {
|
||||||
accountsSelector.currentIndex)
|
accountsSelector.currentIndex)
|
||||||
token.accountAddress = item.address
|
token.accountAddress = item.address
|
||||||
token.accountName = item.name
|
token.accountName = item.name
|
||||||
|
|
||||||
root.deployFeesRequested()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,13 +59,18 @@ StatusScrollView {
|
||||||
|
|
||||||
onFeesPerSelectedContractChanged: {
|
onFeesPerSelectedContractChanged: {
|
||||||
feesModel.clear()
|
feesModel.clear()
|
||||||
feesPerSelectedContract.forEach(entry => {
|
|
||||||
|
let feeSource = feesPerSelectedContract
|
||||||
|
if(!feeSource || feeSource.length === 0) // if no fees are available, show the placeholder text based on selection
|
||||||
|
feeSource = ModelUtils.modelToArray(root.selectedHoldingsModel, ["contractUniqueKey"])
|
||||||
|
|
||||||
|
feeSource.forEach(entry => {
|
||||||
feesModel.append({
|
feesModel.append({
|
||||||
contractUniqueKey: entry.contractUniqueKey,
|
contractUniqueKey: entry.contractUniqueKey,
|
||||||
title: qsTr("Airdrop %1 on %2")
|
title: qsTr("Airdrop %1 on %2")
|
||||||
.arg(ModelUtils.getByKey(root.selectedHoldingsModel, "contractUniqueKey", entry.contractUniqueKey, "symbol"))
|
.arg(ModelUtils.getByKey(root.selectedHoldingsModel, "contractUniqueKey", entry.contractUniqueKey, "symbol"))
|
||||||
.arg(ModelUtils.getByKey(root.selectedHoldingsModel, "contractUniqueKey", entry.contractUniqueKey, "networkText")),
|
.arg(ModelUtils.getByKey(root.selectedHoldingsModel, "contractUniqueKey", entry.contractUniqueKey, "networkText")),
|
||||||
feeText: entry.feeText
|
feeText: entry.feeText ?? ""
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,11 @@ StatusScrollView {
|
||||||
property int viewWidth: 560 // by design
|
property int viewWidth: 560 // by design
|
||||||
property bool isAssetView: false
|
property bool isAssetView: false
|
||||||
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
|
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
|
||||||
|
|
||||||
property var tokensModel
|
property var tokensModel
|
||||||
property var tokensModelWallet
|
property var tokensModelWallet
|
||||||
|
property TokenObject token: TokenObject {
|
||||||
property TokenObject collectible: TokenObject {
|
type: root.isAssetView ? Constants.TokenType.ERC20 : Constants.TokenType.ERC721
|
||||||
type: Constants.TokenType.ERC721
|
|
||||||
}
|
|
||||||
|
|
||||||
property TokenObject asset: TokenObject{
|
|
||||||
type: Constants.TokenType.ERC20
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for reference validation when editing a failed deployment
|
// Used for reference validation when editing a failed deployment
|
||||||
|
@ -53,12 +49,11 @@ StatusScrollView {
|
||||||
property bool isFeeLoading
|
property bool isFeeLoading
|
||||||
|
|
||||||
readonly property string feeLabel:
|
readonly property string feeLabel:
|
||||||
isAssetView ? qsTr("Mint asset on %1").arg(asset.chainName)
|
isAssetView ? qsTr("Mint asset on %1").arg(root.token.chainName)
|
||||||
: qsTr("Mint collectible on %1").arg(collectible.chainName)
|
: qsTr("Mint collectible on %1").arg(root.token.chainName)
|
||||||
|
|
||||||
signal chooseArtWork
|
signal chooseArtWork
|
||||||
signal previewClicked
|
signal previewClicked
|
||||||
signal deployFeesRequested
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
@ -69,7 +64,7 @@ StatusScrollView {
|
||||||
&& symbolInput.valid
|
&& symbolInput.valid
|
||||||
&& (unlimitedSupplyChecker.checked || (!unlimitedSupplyChecker.checked && parseInt(supplyInput.text) > 0))
|
&& (unlimitedSupplyChecker.checked || (!unlimitedSupplyChecker.checked && parseInt(supplyInput.text) > 0))
|
||||||
&& (!root.isAssetView || (root.isAssetView && assetDecimalsInput.valid))
|
&& (!root.isAssetView || (root.isAssetView && assetDecimalsInput.valid))
|
||||||
&& !root.isFeeLoading && root.feeErrorText === "" && !requestFeeDelayTimer.running
|
&& deployFeeSubscriber.feeText !== "" && deployFeeSubscriber.feeErrorText === ""
|
||||||
|
|
||||||
readonly property int imageSelectorRectWidth: root.isAssetView ? 128 : 290
|
readonly property int imageSelectorRectWidth: root.isAssetView ? 128 : 290
|
||||||
|
|
||||||
|
@ -83,10 +78,7 @@ StatusScrollView {
|
||||||
contentHeight: mainLayout.height
|
contentHeight: mainLayout.height
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if(root.isAssetView)
|
networkSelector.setChain(root.token.chainId)
|
||||||
networkSelector.setChain(asset.chainId)
|
|
||||||
else
|
|
||||||
networkSelector.setChain(collectible.chainId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
@ -106,8 +98,8 @@ StatusScrollView {
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: d.imageSelectorRectWidth
|
Layout.preferredHeight: d.imageSelectorRectWidth
|
||||||
dataImage: root.isAssetView ? asset.artworkSource : collectible.artworkSource
|
dataImage: root.token.artworkSource
|
||||||
artworkSource: root.isAssetView ? asset.artworkSource : collectible.artworkSource
|
artworkSource: root.token.artworkSource
|
||||||
editorAnchorLeft: false
|
editorAnchorLeft: false
|
||||||
editorRoundedImage: root.isAssetView
|
editorRoundedImage: root.isAssetView
|
||||||
uploadTextLabel.uploadText: root.isAssetView ? qsTr("Upload") : qsTr("Drag and Drop or Upload Artwork")
|
uploadTextLabel.uploadText: root.isAssetView ? qsTr("Upload") : qsTr("Drag and Drop or Upload Artwork")
|
||||||
|
@ -116,25 +108,15 @@ StatusScrollView {
|
||||||
editorTitle: root.isAssetView ? qsTr("Asset icon") : qsTr("Collectible artwork")
|
editorTitle: root.isAssetView ? qsTr("Asset icon") : qsTr("Collectible artwork")
|
||||||
acceptButtonText: root.isAssetView ? qsTr("Upload asset icon") : qsTr("Upload collectible artwork")
|
acceptButtonText: root.isAssetView ? qsTr("Upload asset icon") : qsTr("Upload collectible artwork")
|
||||||
|
|
||||||
onArtworkSourceChanged: {
|
onArtworkSourceChanged: root.token.artworkSource = artworkSource
|
||||||
if(root.isAssetView)
|
onArtworkCropRectChanged: root.token.artworkCropRect = artworkCropRect
|
||||||
asset.artworkSource = artworkSource
|
|
||||||
else
|
|
||||||
collectible.artworkSource = artworkSource
|
|
||||||
}
|
|
||||||
onArtworkCropRectChanged: {
|
|
||||||
if(root.isAssetView)
|
|
||||||
asset.artworkCropRect = artworkCropRect
|
|
||||||
else
|
|
||||||
collectible.artworkCropRect = artworkCropRect
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomStatusInput {
|
CustomStatusInput {
|
||||||
id: nameInput
|
id: nameInput
|
||||||
|
|
||||||
label: qsTr("Name")
|
label: qsTr("Name")
|
||||||
text: root.isAssetView ? asset.name : collectible.name
|
text: root.token.name
|
||||||
charLimit: 15
|
charLimit: 15
|
||||||
placeholderText: qsTr("Name")
|
placeholderText: qsTr("Name")
|
||||||
validationMode: root.validationMode
|
validationMode: root.validationMode
|
||||||
|
@ -144,7 +126,7 @@ StatusScrollView {
|
||||||
qsTr("Your token name contains invalid characters (use A-Z and 0-9, hyphens and underscores only)")
|
qsTr("Your token name contains invalid characters (use A-Z and 0-9, hyphens and underscores only)")
|
||||||
extraValidator.validate: function (value) {
|
extraValidator.validate: function (value) {
|
||||||
// If minting failed, we can retry same deployment, so same name allowed
|
// If minting failed, we can retry same deployment, so same name allowed
|
||||||
const allowRepeatedName = (root.isAssetView ? asset.deployState : collectible.deployState) === Constants.ContractTransactionStatus.Failed
|
const allowRepeatedName = root.token.deployState === Constants.ContractTransactionStatus.Failed
|
||||||
if(allowRepeatedName)
|
if(allowRepeatedName)
|
||||||
if(nameInput.text === root.referenceName)
|
if(nameInput.text === root.referenceName)
|
||||||
return true
|
return true
|
||||||
|
@ -154,19 +136,14 @@ StatusScrollView {
|
||||||
}
|
}
|
||||||
extraValidator.errorMessage: qsTr("You have used this token name before")
|
extraValidator.errorMessage: qsTr("You have used this token name before")
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: root.token.name = text
|
||||||
if(root.isAssetView)
|
|
||||||
asset.name = text
|
|
||||||
else
|
|
||||||
collectible.name = text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomStatusInput {
|
CustomStatusInput {
|
||||||
id: descriptionInput
|
id: descriptionInput
|
||||||
|
|
||||||
label: qsTr("Description")
|
label: qsTr("Description")
|
||||||
text: root.isAssetView ? asset.description : collectible.description
|
text: root.token.description
|
||||||
charLimit: 280
|
charLimit: 280
|
||||||
placeholderText: root.isAssetView ? qsTr("Describe your asset") : qsTr("Describe your collectible")
|
placeholderText: root.isAssetView ? qsTr("Describe your asset") : qsTr("Describe your collectible")
|
||||||
input.multiline: true
|
input.multiline: true
|
||||||
|
@ -179,19 +156,14 @@ StatusScrollView {
|
||||||
regexValidator.regularExpression: Constants.regularExpressions.ascii
|
regexValidator.regularExpression: Constants.regularExpressions.ascii
|
||||||
regexValidator.errorMessage: qsTr("Only A-Z, 0-9 and standard punctuation allowed")
|
regexValidator.errorMessage: qsTr("Only A-Z, 0-9 and standard punctuation allowed")
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: root.token.description
|
||||||
if(root.isAssetView)
|
|
||||||
asset.description = text
|
|
||||||
else
|
|
||||||
collectible.description = text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomStatusInput {
|
CustomStatusInput {
|
||||||
id: symbolInput
|
id: symbolInput
|
||||||
|
|
||||||
label: qsTr("Symbol")
|
label: qsTr("Symbol")
|
||||||
text: root.isAssetView ? asset.symbol : collectible.symbol
|
text: root.token.symbol
|
||||||
charLimit: 6
|
charLimit: 6
|
||||||
placeholderText: root.isAssetView ? qsTr("e.g. ETH"): qsTr("e.g. DOODLE")
|
placeholderText: root.isAssetView ? qsTr("e.g. ETH"): qsTr("e.g. DOODLE")
|
||||||
validationMode: root.validationMode
|
validationMode: root.validationMode
|
||||||
|
@ -201,7 +173,7 @@ StatusScrollView {
|
||||||
regexValidator.regularExpression: Constants.regularExpressions.capitalOnly
|
regexValidator.regularExpression: Constants.regularExpressions.capitalOnly
|
||||||
extraValidator.validate: function (value) {
|
extraValidator.validate: function (value) {
|
||||||
// If minting failed, we can retry same deployment, so same symbol allowed
|
// If minting failed, we can retry same deployment, so same symbol allowed
|
||||||
const allowRepeatedName = (root.isAssetView ? asset.deployState : collectible.deployState) === Constants.ContractTransactionStatus.Failed
|
const allowRepeatedName = root.token.deployState === Constants.ContractTransactionStatus.Failed
|
||||||
if(allowRepeatedName)
|
if(allowRepeatedName)
|
||||||
if(symbolInput.text.toUpperCase() === root.referenceSymbol.toUpperCase())
|
if(symbolInput.text.toUpperCase() === root.referenceSymbol.toUpperCase())
|
||||||
return true
|
return true
|
||||||
|
@ -216,10 +188,7 @@ StatusScrollView {
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
const cursorPos = input.edit.cursorPosition
|
const cursorPos = input.edit.cursorPosition
|
||||||
const upperSymbol = text.toUpperCase()
|
const upperSymbol = text.toUpperCase()
|
||||||
if(root.isAssetView)
|
root.token.symbol = upperSymbol
|
||||||
asset.symbol = upperSymbol
|
|
||||||
else
|
|
||||||
collectible.symbol = upperSymbol
|
|
||||||
text = upperSymbol // breaking the binding on purpose but so does validate() and onTextChanged() internal handler
|
text = upperSymbol // breaking the binding on purpose but so does validate() and onTextChanged() internal handler
|
||||||
input.edit.cursorPosition = cursorPos
|
input.edit.cursorPosition = cursorPos
|
||||||
}
|
}
|
||||||
|
@ -237,15 +206,12 @@ StatusScrollView {
|
||||||
|
|
||||||
label: qsTr("Unlimited supply")
|
label: qsTr("Unlimited supply")
|
||||||
description: qsTr("Enable to allow the minting of additional tokens in the future. Disable to specify a finite supply")
|
description: qsTr("Enable to allow the minting of additional tokens in the future. Disable to specify a finite supply")
|
||||||
checked: root.isAssetView ? asset.infiniteSupply : collectible.infiniteSupply
|
checked: root.token.infiniteSupply
|
||||||
|
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
if(!checked) supplyInput.forceActiveFocus()
|
if(!checked) supplyInput.forceActiveFocus()
|
||||||
|
|
||||||
if(root.isAssetView)
|
root.token.infiniteSupply = checked
|
||||||
asset.infiniteSupply = checked
|
|
||||||
else
|
|
||||||
collectible.infiniteSupply = checked
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,12 +220,8 @@ StatusScrollView {
|
||||||
|
|
||||||
visible: !unlimitedSupplyChecker.checked
|
visible: !unlimitedSupplyChecker.checked
|
||||||
label: qsTr("Total finite supply")
|
label: qsTr("Total finite supply")
|
||||||
text: {
|
text: SQUtils.AmountsArithmetic.toNumber(root.token.supply,
|
||||||
const token = root.isAssetView ? root.asset : root.collectible
|
root.token.multiplierIndex)
|
||||||
|
|
||||||
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")
|
||||||
|
@ -274,9 +236,8 @@ StatusScrollView {
|
||||||
if (Number.isNaN(supplyNumber) || Object.values(errors).length)
|
if (Number.isNaN(supplyNumber) || Object.values(errors).length)
|
||||||
return
|
return
|
||||||
|
|
||||||
const token = root.isAssetView ? root.asset : root.collectible
|
|
||||||
token.supply = SQUtils.AmountsArithmetic.fromNumber(
|
token.supply = SQUtils.AmountsArithmetic.fromNumber(
|
||||||
supplyNumber, token.multiplierIndex).toFixed(0)
|
supplyNumber, root.token.multiplierIndex).toFixed(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,9 +247,9 @@ StatusScrollView {
|
||||||
visible: !root.isAssetView
|
visible: !root.isAssetView
|
||||||
label: checked ? qsTr("Not transferable (Soulbound)") : qsTr("Transferable")
|
label: checked ? qsTr("Not transferable (Soulbound)") : qsTr("Transferable")
|
||||||
description: qsTr("If enabled, the token is locked to the first address it is sent to and can never be transferred to another address. Useful for tokens that represent Admin permissions")
|
description: qsTr("If enabled, the token is locked to the first address it is sent to and can never be transferred to another address. Useful for tokens that represent Admin permissions")
|
||||||
checked: !collectible.transferable
|
checked: !root.token.transferable
|
||||||
|
|
||||||
onCheckedChanged: collectible.transferable = !checked
|
onCheckedChanged: root.token.transferable = !checked
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomSwitchRowComponent {
|
CustomSwitchRowComponent {
|
||||||
|
@ -297,8 +258,8 @@ StatusScrollView {
|
||||||
visible: !root.isAssetView
|
visible: !root.isAssetView
|
||||||
label: qsTr("Remotely destructible")
|
label: qsTr("Remotely destructible")
|
||||||
description: qsTr("Enable to allow you to destroy tokens remotely. Useful for revoking permissions from individuals")
|
description: qsTr("Enable to allow you to destroy tokens remotely. Useful for revoking permissions from individuals")
|
||||||
checked: !!collectible ? collectible.remotelyDestruct : true
|
checked: !!root.token ? root.token.remotelyDestruct : true
|
||||||
onCheckedChanged: collectible.remotelyDestruct = checked
|
onCheckedChanged: root.token.remotelyDestruct = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomStatusInput {
|
CustomStatusInput {
|
||||||
|
@ -309,7 +270,7 @@ StatusScrollView {
|
||||||
charLimit: 2
|
charLimit: 2
|
||||||
charLimitLabel: qsTr("Max 10")
|
charLimitLabel: qsTr("Max 10")
|
||||||
placeholderText: "2"
|
placeholderText: "2"
|
||||||
text: !!asset ? asset.decimals : ""
|
text: root.token.decimals
|
||||||
validationMode: StatusInput.ValidationMode.Always
|
validationMode: StatusInput.ValidationMode.Always
|
||||||
minLengthValidator.errorMessage: qsTr("Please enter how many decimals your token should have")
|
minLengthValidator.errorMessage: qsTr("Please enter how many decimals your token should have")
|
||||||
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your decimal amount is too cool (use 0-9 only)") :
|
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your decimal amount is too cool (use 0-9 only)") :
|
||||||
|
@ -317,7 +278,7 @@ StatusScrollView {
|
||||||
regexValidator.regularExpression: Constants.regularExpressions.numerical
|
regexValidator.regularExpression: Constants.regularExpressions.numerical
|
||||||
extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 10 }
|
extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 10 }
|
||||||
extraValidator.errorMessage: qsTr("Enter a number between 1 and 10")
|
extraValidator.errorMessage: qsTr("Enter a number between 1 and 10")
|
||||||
onTextChanged: asset.decimals = parseInt(text)
|
onTextChanged: root.token.decimals = parseInt(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
FeesBox {
|
FeesBox {
|
||||||
|
@ -338,44 +299,17 @@ StatusScrollView {
|
||||||
readonly property bool error: root.feeErrorText !== ""
|
readonly property bool error: root.feeErrorText !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: requestFeeDelayTimer
|
|
||||||
|
|
||||||
interval: 500
|
|
||||||
onTriggered: root.deployFeesRequested()
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool triggerFeeReevaluation: {
|
|
||||||
dropAreaItem.artworkSource
|
|
||||||
nameInput.text
|
|
||||||
descriptionInput.text
|
|
||||||
symbolInput.text
|
|
||||||
supplyInput.text
|
|
||||||
unlimitedSupplyChecker.checked
|
|
||||||
transferableChecker.checked
|
|
||||||
remotelyDestructChecker.checked
|
|
||||||
feesBox.accountsSelector.currentIndex
|
|
||||||
asset.chainId
|
|
||||||
collectible.chainId
|
|
||||||
|
|
||||||
requestFeeDelayTimer.restart()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
accountsSelector.model: root.accounts
|
accountsSelector.model: root.accounts
|
||||||
|
|
||||||
readonly property TokenObject token: root.isAssetView ? root.asset
|
|
||||||
: root.collectible
|
|
||||||
|
|
||||||
// account can be changed also on preview page and it should be
|
// account can be changed also on preview page and it should be
|
||||||
// reflected in the form after navigating back
|
// reflected in the form after navigating back
|
||||||
Connections {
|
Connections {
|
||||||
target: feesBox.token
|
target: root.token
|
||||||
|
|
||||||
function onAccountAddressChanged() {
|
function onAccountAddressChanged() {
|
||||||
const idx = SQUtils.ModelUtils.indexOf(
|
const idx = SQUtils.ModelUtils.indexOf(
|
||||||
feesBox.accountsSelector.model, "address",
|
feesBox.accountsSelector.model, "address",
|
||||||
feesBox.token.accountAddress)
|
root.token.accountAddress)
|
||||||
|
|
||||||
feesBox.accountsSelector.currentIndex = idx
|
feesBox.accountsSelector.currentIndex = idx
|
||||||
}
|
}
|
||||||
|
@ -387,8 +321,8 @@ StatusScrollView {
|
||||||
|
|
||||||
const item = SQUtils.ModelUtils.get(
|
const item = SQUtils.ModelUtils.get(
|
||||||
accountsSelector.model, accountsSelector.currentIndex)
|
accountsSelector.model, accountsSelector.currentIndex)
|
||||||
token.accountAddress = item.address
|
root.token.accountAddress = item.address
|
||||||
token.accountName = item.name
|
root.token.accountName = item.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,15 +445,9 @@ StatusScrollView {
|
||||||
multiSelection: false
|
multiSelection: false
|
||||||
|
|
||||||
onToggleNetwork: (network) => {
|
onToggleNetwork: (network) => {
|
||||||
if(root.isAssetView) {
|
root.token.chainId = network.chainId
|
||||||
asset.chainId = network.chainId
|
root.token.chainName = network.chainName
|
||||||
asset.chainName = network.chainName
|
root.token.chainIcon = network.iconUrl
|
||||||
asset.chainIcon = network.iconUrl
|
|
||||||
} else {
|
|
||||||
collectible.chainId = network.chainId
|
|
||||||
collectible.chainName = network.chainName
|
|
||||||
collectible.chainIcon = network.iconUrl
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,6 @@ StatusScrollView {
|
||||||
.arg(communityName).arg(ownerToken.chainName)
|
.arg(communityName).arg(ownerToken.chainName)
|
||||||
|
|
||||||
signal mintClicked
|
signal mintClicked
|
||||||
signal deployFeesRequested
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
@ -175,8 +174,6 @@ StatusScrollView {
|
||||||
onAddressChanged: {
|
onAddressChanged: {
|
||||||
ownerToken.accountAddress = address
|
ownerToken.accountAddress = address
|
||||||
tMasterToken.accountAddress = address
|
tMasterToken.accountAddress = address
|
||||||
|
|
||||||
requestFeeDelayTimer.restart()
|
|
||||||
}
|
}
|
||||||
control.onDisplayTextChanged: {
|
control.onDisplayTextChanged: {
|
||||||
ownerToken.accountName = control.displayText
|
ownerToken.accountName = control.displayText
|
||||||
|
@ -324,16 +321,7 @@ StatusScrollView {
|
||||||
tMasterToken.chainId = network.chainId
|
tMasterToken.chainId = network.chainId
|
||||||
tMasterToken.chainName = network.chainName
|
tMasterToken.chainName = network.chainName
|
||||||
tMasterToken.chainIcon = network.iconUrl
|
tMasterToken.chainIcon = network.iconUrl
|
||||||
|
|
||||||
requestFeeDelayTimer.restart()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: requestFeeDelayTimer
|
|
||||||
|
|
||||||
interval: 500
|
|
||||||
onTriggered: root.deployFeesRequested()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ QtObject {
|
||||||
property var enabledNetworks: networksModule.enabled
|
property var enabledNetworks: networksModule.enabled
|
||||||
property var allNetworks: networksModule.all
|
property var allNetworks: networksModule.all
|
||||||
|
|
||||||
signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error)
|
signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error, string responseId)
|
||||||
signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error)
|
signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error, string responseId)
|
||||||
signal airdropFeeUpdated(var airdropFees)
|
signal airdropFeeUpdated(var airdropFees)
|
||||||
signal burnFeeUpdated(var ethCurrency, var fiatCurrency, int error)
|
signal burnFeeUpdated(var ethCurrency, var fiatCurrency, int error, string responseId)
|
||||||
|
|
||||||
signal deploymentStateChanged(string communityId, int status, string url)
|
signal deploymentStateChanged(string communityId, int status, string url)
|
||||||
signal ownerTokenDeploymentStateChanged(string communityId, int status, string url)
|
signal ownerTokenDeploymentStateChanged(string communityId, int status, string url)
|
||||||
|
@ -63,18 +63,22 @@ QtObject {
|
||||||
readonly property Connections connections: Connections {
|
readonly property Connections connections: Connections {
|
||||||
target: communityTokensModuleInst
|
target: communityTokensModuleInst
|
||||||
|
|
||||||
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
|
||||||
root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode)
|
root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
|
||||||
root.selfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode)
|
root.selfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAirdropFeesUpdated(jsonFees) {
|
function onAirdropFeesUpdated(jsonFees) {
|
||||||
root.airdropFeeUpdated(JSON.parse(jsonFees))
|
root.airdropFeeUpdated(JSON.parse(jsonFees))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onBurnFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
|
||||||
|
root.burnFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId)
|
||||||
|
}
|
||||||
|
|
||||||
function onDeploymentStateChanged(communityId, status, url) {
|
function onDeploymentStateChanged(communityId, status, url) {
|
||||||
root.deploymentStateChanged(communityId, status, url)
|
root.deploymentStateChanged(communityId, status, url)
|
||||||
}
|
}
|
||||||
|
@ -98,14 +102,22 @@ QtObject {
|
||||||
function onBurnStateChanged(communityId, tokenName, status, url) {
|
function onBurnStateChanged(communityId, tokenName, status, url) {
|
||||||
root.burnStateChanged(communityId, tokenName, status, url)
|
root.burnStateChanged(communityId, tokenName, status, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBurnFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
|
||||||
root.burnFeeUpdated(ethCurrency, fiatCurrency, errorCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeDeployFee(chainId, accountAddress, tokenType, isOwnerDeployment) {
|
// Burn:
|
||||||
communityTokensModuleInst.computeDeployFee(chainId, accountAddress, tokenType, isOwnerDeployment)
|
function computeBurnFee(tokenKey, amount, accountAddress, requestId) {
|
||||||
|
console.assert(typeof amount === "string")
|
||||||
|
communityTokensModuleInst.computeBurnFee(tokenKey, amount, accountAddress, requestId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeAirdropFee(communityId, contractKeysAndAmounts, addresses, feeAccountAddress, requestId) {
|
||||||
|
communityTokensModuleInst.computeAirdropFee(
|
||||||
|
communityId, JSON.stringify(contractKeysAndAmounts),
|
||||||
|
JSON.stringify(addresses), feeAccountAddress, requestId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeDeployFee(chainId, accountAddress, tokenType, isOwnerDeployment, requestId) {
|
||||||
|
communityTokensModuleInst.computeDeployFee(chainId, accountAddress, tokenType, isOwnerDeployment, requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,20 +129,14 @@ QtObject {
|
||||||
* }
|
* }
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
function computeSelfDestructFee(walletsAndAmounts, tokenKey, accountAddress) {
|
function computeSelfDestructFee(walletsAndAmounts, tokenKey, accountAddress, requestId) {
|
||||||
communityTokensModuleInst.computeSelfDestructFee(JSON.stringify(walletsAndAmounts), tokenKey, accountAddress)
|
communityTokensModuleInst.computeSelfDestructFee(JSON.stringify(walletsAndAmounts), tokenKey, accountAddress, requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteSelfDestructCollectibles(communityId, walletsAndAmounts, tokenKey, accountAddress) {
|
function remoteSelfDestructCollectibles(communityId, walletsAndAmounts, tokenKey, accountAddress) {
|
||||||
communityTokensModuleInst.selfDestructCollectibles(communityId, JSON.stringify(walletsAndAmounts), tokenKey, accountAddress)
|
communityTokensModuleInst.selfDestructCollectibles(communityId, JSON.stringify(walletsAndAmounts), tokenKey, accountAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Burn:
|
|
||||||
function computeBurnFee(tokenKey, amount, accountAddress) {
|
|
||||||
console.assert(typeof amount === "string")
|
|
||||||
communityTokensModuleInst.computeBurnFee(tokenKey, amount, accountAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
function burnToken(communityId, tokenKey, burnAmount, accountAddress) {
|
function burnToken(communityId, tokenKey, burnAmount, accountAddress) {
|
||||||
console.assert(typeof burnAmount === "string")
|
console.assert(typeof burnAmount === "string")
|
||||||
communityTokensModuleInst.burnTokens(communityId, tokenKey, burnAmount, accountAddress)
|
communityTokensModuleInst.burnTokens(communityId, tokenKey, burnAmount, accountAddress)
|
||||||
|
@ -140,10 +146,4 @@ QtObject {
|
||||||
function airdrop(communityId, airdropTokens, addresses, feeAccountAddress) {
|
function airdrop(communityId, airdropTokens, addresses, feeAccountAddress) {
|
||||||
communityTokensModuleInst.airdropTokens(communityId, JSON.stringify(airdropTokens), JSON.stringify(addresses), feeAccountAddress)
|
communityTokensModuleInst.airdropTokens(communityId, JSON.stringify(airdropTokens), JSON.stringify(addresses), feeAccountAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeAirdropFee(communityId, contractKeysAndAmounts, addresses, feeAccountAddress) {
|
|
||||||
communityTokensModuleInst.computeAirdropFee(
|
|
||||||
communityId, JSON.stringify(contractKeysAndAmounts),
|
|
||||||
JSON.stringify(addresses), feeAccountAddress)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue