diff --git a/src/app_service/service/community_tokens/async_tasks.nim b/src/app_service/service/community_tokens/async_tasks.nim
index 4ec24f7b27..1e2752c457 100644
--- a/src/app_service/service/community_tokens/async_tasks.nim
+++ b/src/app_service/service/community_tokens/async_tasks.nim
@@ -100,8 +100,9 @@ const asyncGetRemoteBurnFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimc
"gasTable": tableToJsonArray(gasTable),
"chainId": arg.chainId,
"addressFrom": arg.addressFrom,
+ "error": "",
"requestId": arg.requestId,
- "error": "" })
+ })
except Exception as e:
arg.finish(%* {
"error": e.msg,
@@ -130,8 +131,9 @@ const asyncGetBurnFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.}
"gasTable": tableToJsonArray(gasTable),
"chainId": arg.chainId,
"addressFrom": arg.addressFrom,
- "requestId": arg.requestId,
- "error": "" })
+ "error": "",
+ "requestId": arg.requestId
+ })
except Exception as e:
arg.finish(%* {
"error": e.msg,
@@ -163,15 +165,16 @@ const asyncGetMintFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.}
arg.walletAddresses, collectibleAndAmount.amount).result.getInt
gasTable[(chainId, collectibleAndAmount.communityToken.address)] = gas
arg.finish(%* {
- "requestId": arg.requestId,
"feeTable": tableToJsonArray(feeTable),
"gasTable": tableToJsonArray(gasTable),
"addressFrom": arg.addressFrom,
- "error": "" })
+ "error": "",
+ "requestId": arg.requestId
+ })
except Exception as e:
let output = %* {
- "requestId": arg.requestId,
- "error": e.msg
+ "error": e.msg,
+ "requestId": arg.requestId
}
arg.finish(output)
diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim
index 771fd5d8ac..3734e236dd 100644
--- a/src/app_service/service/community_tokens/service.nim
+++ b/src/app_service/service/community_tokens/service.nim
@@ -656,8 +656,10 @@ QtObject:
)
self.threadpool.start(arg)
except Exception as e:
- #TODO: handle error - emit error signal
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 =
if (cryptoSymbol == ""):
diff --git a/storybook/pages/BurnTokensPopupPage.qml b/storybook/pages/BurnTokensPopupPage.qml
index 60c3c88cdb..1ce51b878f 100644
--- a/storybook/pages/BurnTokensPopupPage.qml
+++ b/storybook/pages/BurnTokensPopupPage.qml
@@ -68,24 +68,27 @@ SplitView {
onBurnClicked: logs.logEvent("BurnTokensPopup::onBurnClicked --> Burn amount: " + burnAmount)
onCancelClicked: logs.logEvent("BurnTokensPopup::onCancelClicked")
+ feeText: "0.0015 ETH ($75.43)"
+ feeErrorText: ""
+ isFeeLoading: false
- onBurnFeesRequested: {
- feeText = ""
- feeErrorText = ""
- isFeeLoading = true
-
- feeCalculationTimer.restart()
+ onSelectedAccountAddressChanged: {
+ burnTokensPopup.isFeeLoading = true
+ timer.delay(2000, () => burnTokensPopup.isFeeLoading = false)
+ }
+ onAmountToBurnChanged: {
+ burnTokensPopup.isFeeLoading = true
+ timer.delay(2000, () => burnTokensPopup.isFeeLoading = false)
}
}
Timer {
- id: feeCalculationTimer
-
- interval: 1000
-
- onTriggered: {
- burnTokensPopup.feeText = "0.0015 ETH ($75.43)"
- burnTokensPopup.isFeeLoading = false
+ id: timer
+ function delay(ms, callback) {
+ timer.interval = ms
+ timer.repeat = false
+ timer.triggered.connect(callback)
+ timer.start()
}
}
}
diff --git a/storybook/pages/CommunityTokenViewPage.qml b/storybook/pages/CommunityTokenViewPage.qml
index dae6e6f16a..b8da235a1a 100644
--- a/storybook/pages/CommunityTokenViewPage.qml
+++ b/storybook/pages/CommunityTokenViewPage.qml
@@ -62,6 +62,9 @@ SplitView {
token: tokenObject
tokenOwnersModel: TokenHoldersModel {}
+ feeText: "0.01"
+ feeErrorText: ""
+ isFeeLoading: false
accounts: WalletAccountsModel {}
diff --git a/storybook/pages/EditOwnerTokenViewPage.qml b/storybook/pages/EditOwnerTokenViewPage.qml
index 8775283f9f..7adf16759e 100644
--- a/storybook/pages/EditOwnerTokenViewPage.qml
+++ b/storybook/pages/EditOwnerTokenViewPage.qml
@@ -53,7 +53,7 @@ SplitView {
onMintClicked: logs.logEvent("EditOwnerTokenView::onMintClicked")
- onDeployFeesRequested: {
+ Component.onCompleted: {
feeText = ""
feeErrorText = ""
isFeeLoading = true
diff --git a/storybook/pages/MintTokensSettingsPanelPage.qml b/storybook/pages/MintTokensSettingsPanelPage.qml
index 7162d1fc31..1a08bd8f01 100644
--- a/storybook/pages/MintTokensSettingsPanelPage.qml
+++ b/storybook/pages/MintTokensSettingsPanelPage.qml
@@ -30,13 +30,12 @@ SplitView {
}
Timer {
- id: feesTimer
-
- interval: 1000
-
- onTriggered: {
- panel.isFeeLoading = false
- panel.feeText = "0,0002 ETH (123,15 USD)"
+ id: timer
+ function delay(delayTime, cb) {
+ timer.interval = delayTime;
+ timer.repeat = false;
+ timer.triggered.connect(cb);
+ timer.start();
}
}
@@ -48,6 +47,24 @@ SplitView {
MintTokensSettingsPanel {
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 {
id: mintedTokensModel
}
@@ -105,14 +122,9 @@ SplitView {
onMintCollectible: logs.logEvent("CommunityMintTokensSettingsPanel::mintCollectible")
onMintAsset: logs.logEvent("CommunityMintTokensSettingsPanel::mintAssets")
onDeleteToken: logs.logEvent("CommunityMintTokensSettingsPanel::deleteToken: " + tokenKey)
-
- onDeployFeesRequested: {
- feeText = ""
- feeErrorText = ""
- isFeeLoading = true
-
- feesTimer.restart()
- }
+ onRegisterDeployFeesSubscriber: timer.delay(2000, () => feeSubscriber.feesResponse = panel.singleTransactionFee)
+ onRegisterSelfDestructFeesSubscriber: timer.delay(2000, () => feeSubscriber.feesResponse = panel.singleTransactionFee)
+ onRegisterBurnTokenFeesSubscriber: timer.delay(2000, () => feeSubscriber.feesResponse = panel.singleTransactionFee)
}
}
diff --git a/storybook/pages/RemotelyDestructPopupPage.qml b/storybook/pages/RemotelyDestructPopupPage.qml
index 34de2010ad..38910ec3d1 100644
--- a/storybook/pages/RemotelyDestructPopupPage.qml
+++ b/storybook/pages/RemotelyDestructPopupPage.qml
@@ -74,12 +74,6 @@ SplitView {
])
close()
}
- onRemotelyDestructFeesRequested: {
- logs.logEvent("RemoteSelfDestructPopup::onRemotelyDestructFeesRequested",
- ["walletsAndAmounts", "accountAddress"], [
- JSON.stringify(walletsAndAmounts), accountAddress
- ])
- }
Component.onCompleted: {
open()
diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/Subscription.qml b/ui/StatusQ/src/StatusQ/Core/Utils/Subscription.qml
new file mode 100644
index 0000000000..0144ef7c90
--- /dev/null
+++ b/ui/StatusQ/src/StatusQ/Core/Utils/Subscription.qml
@@ -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: {}
+}
diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBroker.qml b/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBroker.qml
new file mode 100644
index 0000000000..fb7c246560
--- /dev/null
+++ b/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBroker.qml
@@ -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
+ }
+ }
+}
diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/qmldir b/ui/StatusQ/src/StatusQ/Core/Utils/qmldir
index a20bf6089a..ede0e58c2a 100644
--- a/ui/StatusQ/src/StatusQ/Core/Utils/qmldir
+++ b/ui/StatusQ/src/StatusQ/Core/Utils/qmldir
@@ -8,6 +8,8 @@ ModelChangeTracker 0.1 ModelChangeTracker.qml
ModelsComparator 0.1 ModelsComparator.qml
StackViewStates 0.1 StackViewStates.qml
StatesStack 0.1 StatesStack.qml
+Subscription 0.1 Subscription.qml
+SubscriptionBroker 0.1 SubscriptionBroker.qml
XSS 1.0 xss.js
singleton AmountsArithmetic 0.1 AmountsArithmetic.qml
singleton Emoji 0.1 Emoji.qml
diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc
index d8f066aa30..079e87408e 100644
--- a/ui/StatusQ/src/statusq.qrc
+++ b/ui/StatusQ/src/statusq.qrc
@@ -226,5 +226,7 @@
StatusQ/Controls/StatusBlockProgressBar.qml
StatusQ/Components/StatusInfoBoxPanel.qml
StatusQ/Controls/StatusWarningBox.qml
+ StatusQ/Core/Utils/Subscription.qml
+ StatusQ/Core/Utils/SubscriptionBroker.qml
diff --git a/ui/StatusQ/tests/TestCore/TestUtils/tst_SubscriptionBroker.qml b/ui/StatusQ/tests/TestCore/TestUtils/tst_SubscriptionBroker.qml
new file mode 100644
index 0000000000..deca79c528
--- /dev/null
+++ b/ui/StatusQ/tests/TestCore/TestUtils/tst_SubscriptionBroker.qml
@@ -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")
+ }
+}
+
diff --git a/ui/app/AppLayouts/Communities/helpers/AirdropFeesSubscriber.qml b/ui/app/AppLayouts/Communities/helpers/AirdropFeesSubscriber.qml
index fc801a5f73..9426e22b46 100644
--- a/ui/app/AppLayouts/Communities/helpers/AirdropFeesSubscriber.qml
+++ b/ui/app/AppLayouts/Communities/helpers/AirdropFeesSubscriber.qml
@@ -67,4 +67,4 @@ QtObject {
}
})
}
-}
\ No newline at end of file
+}
diff --git a/ui/app/AppLayouts/Communities/helpers/BurnTokenFeesSubscriber.qml b/ui/app/AppLayouts/Communities/helpers/BurnTokenFeesSubscriber.qml
new file mode 100644
index 0000000000..30aff018df
--- /dev/null
+++ b/ui/app/AppLayouts/Communities/helpers/BurnTokenFeesSubscriber.qml
@@ -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
+}
diff --git a/ui/app/AppLayouts/Communities/helpers/DeployFeesSubscriber.qml b/ui/app/AppLayouts/Communities/helpers/DeployFeesSubscriber.qml
new file mode 100644
index 0000000000..4c01998818
--- /dev/null
+++ b/ui/app/AppLayouts/Communities/helpers/DeployFeesSubscriber.qml
@@ -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
+}
diff --git a/ui/app/AppLayouts/Communities/helpers/SelfDestructFeesSubscriber.qml b/ui/app/AppLayouts/Communities/helpers/SelfDestructFeesSubscriber.qml
new file mode 100644
index 0000000000..00f9ec4230
--- /dev/null
+++ b/ui/app/AppLayouts/Communities/helpers/SelfDestructFeesSubscriber.qml
@@ -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
+}
diff --git a/ui/app/AppLayouts/Communities/helpers/SingleFeeSubscriber.qml b/ui/app/AppLayouts/Communities/helpers/SingleFeeSubscriber.qml
new file mode 100644
index 0000000000..1544534784
--- /dev/null
+++ b/ui/app/AppLayouts/Communities/helpers/SingleFeeSubscriber.qml
@@ -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")
+ }
+}
diff --git a/ui/app/AppLayouts/Communities/helpers/TransactionFeesBroker.qml b/ui/app/AppLayouts/Communities/helpers/TransactionFeesBroker.qml
new file mode 100644
index 0000000000..ef8bab7e03
--- /dev/null
+++ b/ui/app/AppLayouts/Communities/helpers/TransactionFeesBroker.qml
@@ -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)
+ }
+}
diff --git a/ui/app/AppLayouts/Communities/helpers/qmldir b/ui/app/AppLayouts/Communities/helpers/qmldir
index 676d10b094..0ecc5bdebd 100644
--- a/ui/app/AppLayouts/Communities/helpers/qmldir
+++ b/ui/app/AppLayouts/Communities/helpers/qmldir
@@ -1,2 +1,8 @@
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
+TransactionFeesBroker 1.0 TransactionFeesBroker.qml
diff --git a/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml
index 7bd83868c8..0713b59595 100644
--- a/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml
+++ b/ui/app/AppLayouts/Communities/panels/AirdropsSettingsPanel.qml
@@ -20,7 +20,6 @@ StackView {
required property bool isTokenMasterOwner
required property bool isAdmin
readonly property bool isPrivilegedTokenOwnerProfile: root.isOwner || root.isTokenMasterOwner
- readonly property alias airdropFeesSubscriber: d.aidropFeeSubscriber
// Owner and TMaster token related properties:
readonly property bool arePrivilegedTokensDeployed: root.isOwnerTokenDeployed && root.isTMasterTokenDeployed
@@ -38,8 +37,8 @@ StackView {
property string previousPageName: depth > 1 ? qsTr("Airdrops") : ""
signal airdropClicked(var airdropTokens, var addresses, string feeAccountAddress)
- signal airdropFeesRequested(var contractKeysAndAmounts, var addresses, string feeAccountAddress)
signal navigateToMintTokenSettings(bool isAssetType)
+ signal registerAirdropFeeSubscriber(var feeSubscriber)
function navigateBack() {
pop(StackView.Immediate)
@@ -132,7 +131,6 @@ StackView {
Component.onCompleted: {
d.selectToken.connect(view.selectToken)
d.addAddresses.connect(view.addAddresses)
- d.aidropFeeSubscriber = feesSubscriber
}
AirdropFeesSubscriber {
@@ -142,6 +140,7 @@ StackView {
contractKeysAndAmounts: view.selectedContractKeysAndAmounts
addressesToAirdrop: view.selectedAddressesToAirdrop
feeAccountAddress: view.selectedFeeAccount
+ Component.onCompleted: root.registerAirdropFeeSubscriber(feesSubscriber)
}
}
}
diff --git a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml
index 8c7a597637..5d9b82a0d5 100644
--- a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml
+++ b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml
@@ -50,11 +50,6 @@ StackView {
property var tokensModelWallet
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:
property var layer1Networks
property var layer2Networks
@@ -67,25 +62,15 @@ StackView {
signal kickUserRequested(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)] }
string tokenKey,
string accountAddress)
- signal signBurnTransactionOpened(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 deleteToken(string tokenKey)
-
- function setFeeLoading() {
- root.isFeeLoading = true
- root.feeText = ""
- root.feeErrorText = ""
- }
+ signal registerDeployFeesSubscriber(var feeSubscriber)
+ signal registerSelfDestructFeesSubscriber(var feeSubscriber)
+ signal registerBurnTokenFeesSubscriber(var feeSubscriber)
function navigateBack() {
pop(StackView.Immediate)
@@ -138,7 +123,6 @@ StackView {
root.push(ownerTokenEditViewComponent, properties,
StackView.Immediate)
}
-
}
initialItem: SettingsPage {
@@ -243,31 +227,36 @@ StackView {
allNetworks: root.allNetworks
accounts: root.accounts
+ feeText: feeSubscriber.feeText
+ feeErrorText: feeSubscriber.feeErrorText
+ isFeeLoading: !feeSubscriber.feesResponse
+
onMintClicked: signMintPopup.open()
-
- onDeployFeesRequested: root.deployFeesRequested(
- ownerToken.chainId,
- ownerToken.accountAddress,
- Constants.TokenType.ERC721)
-
- feeText: root.feeText
- feeErrorText: root.feeErrorText
- isFeeLoading: root.isFeeLoading
+
+ DeployFeesSubscriber {
+ id: feeSubscriber
+ chainId: editOwnerTokenView.ownerToken.chainId
+ tokenType: editOwnerTokenView.ownerToken.type
+ isOwnerDeployment: editOwnerTokenView.ownerToken.isPrivilegedToken
+ accountAddress: editOwnerTokenView.ownerToken.accountAddress
+ enabled: editOwnerTokenView.visible || signMintPopup.visible
+ Component.onCompleted: root.registerDeployFeesSubscriber(feeSubscriber)
+ }
SignMultiTokenTransactionsPopup {
id: signMintPopup
title: qsTr("Sign transaction - Mint %1 tokens").arg(
editOwnerTokenView.communityName)
- totalFeeText: root.isFeeLoading ?
- "" : root.feeText
- errorText: root.feeErrorText
+ totalFeeText: editOwnerTokenView.isFeeLoading ?
+ "" : editOwnerTokenView.feeText
+ errorText: editOwnerTokenView.feeErrorText
accountName: editOwnerTokenView.ownerToken.accountName
model: QtObject {
readonly property string title: editOwnerTokenView.feeLabel
readonly property string feeText: signMintPopup.totalFeeText
- readonly property bool error: root.feeErrorText !== ""
+ readonly property bool error: editOwnerTokenView.feeErrorText !== ""
}
onSignTransactionClicked: editOwnerTokenView.signMintTransaction()
@@ -335,7 +324,7 @@ StackView {
validationMode: !newTokenPage.isAssetView
? newTokenPage.validationMode
: StatusInput.ValidationMode.OnlyWhenDirty
- collectible: newTokenPage.collectible
+ token: newTokenPage.collectible
}
CustomEditCommunityTokenView {
@@ -345,10 +334,12 @@ StackView {
validationMode: newTokenPage.isAssetView
? newTokenPage.validationMode
: StatusInput.ValidationMode.OnlyWhenDirty
- asset: newTokenPage.asset
+ token: newTokenPage.asset
}
component CustomEditCommunityTokenView: EditCommunityTokenView {
+ id: editView
+
viewWidth: root.viewWidth
layer1Networks: root.layer1Networks
layer2Networks: root.layer2Networks
@@ -361,28 +352,27 @@ StackView {
referenceName: newTokenPage.referenceName
referenceSymbol: newTokenPage.referenceSymbol
- feeText: root.feeText
- feeErrorText: root.feeErrorText
- isFeeLoading: root.isFeeLoading
+ feeText: deployFeeSubscriber.feeText
+ feeErrorText: deployFeeSubscriber.feeErrorText
+ isFeeLoading: !deployFeeSubscriber.feesResponse
onPreviewClicked: {
const properties = {
- token: isAssetView ? asset : collectible
+ token: token
}
root.push(previewTokenViewComponent, properties,
StackView.Immediate)
}
- onDeployFeesRequested: {
- if (isAssetView)
- root.deployFeesRequested(asset.chainId,
- asset.accountAddress,
- Constants.TokenType.ERC20)
- else
- root.deployFeesRequested(collectible.chainId,
- collectible.accountAddress,
- Constants.TokenType.ERC721)
+ DeployFeesSubscriber {
+ id: deployFeeSubscriber
+ chainId: editView.token.chainId
+ tokenType: editView.token.type
+ isOwnerDeployment: editView.token.isPrivilegedToken
+ accountAddress: editView.token.accountAddress
+ enabled: editView.visible
+ Component.onCompleted: root.registerDeployFeesSubscriber(deployFeeSubscriber)
}
}
}
@@ -407,20 +397,14 @@ StackView {
viewWidth: root.viewWidth
preview: true
- feeText: root.feeText
- feeErrorText: root.feeErrorText
- isFeeLoading: root.isFeeLoading
accounts: root.accounts
-
- onDeployFeesRequested: root.deployFeesRequested(
- token.chainId, token.accountAddress,
- token.type)
+ feeText: feeSubscriber.feeText
+ feeErrorText: feeSubscriber.feeErrorText
+ isFeeLoading: !feeSubscriber.feesResponse
onMintClicked: signMintPopup.open()
function signMintTransaction() {
- root.setFeeLoading()
-
if(preview.isAssetView)
root.mintAsset(token)
else
@@ -429,18 +413,28 @@ StackView {
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 {
id: signMintPopup
title: qsTr("Sign transaction - Mint %1 token").arg(
preview.token.name)
- totalFeeText: root.isFeeLoading ? "" : root.feeText
+ totalFeeText: preview.isFeeLoading ? "" : preview.feeText
accountName: preview.token.accountName
model: QtObject {
readonly property string title: preview.feeLabel
readonly property string feeText: signMintPopup.totalFeeText
- readonly property bool error: root.feeErrorText !== ""
+ readonly property bool error: preview.feeErrorText !== ""
}
onSignTransactionClicked: preview.signMintTransaction()
@@ -452,12 +446,11 @@ StackView {
component TokenViewPage: SettingsPage {
id: tokenViewPage
- readonly property alias token: view.token
+ property TokenObject token: TokenObject {}
readonly property bool deploymentFailed: view.deployState === Constants.ContractTransactionStatus.Failed
- property alias tokenOwnersModel: view.tokenOwnersModel
- property alias airdropKey: view.airdropKey
-
+ property var tokenOwnersModel
+ property string airdropKey
// Owner and TMaster related props
readonly property bool isPrivilegedTokenItem: isOwnerTokenItem || isTMasterTokenItem
readonly property bool isOwnerTokenItem: token.privilegesLevel === Constants.TokenPrivilegesLevel.Owner
@@ -517,11 +510,12 @@ StackView {
contentItem: CommunityTokenView {
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
- token: TokenObject {}
+ token: tokenViewPage.token
+ tokenOwnersModel: tokenViewPage.tokenOwnersModel
onGeneralAirdropRequested: {
root.airdropToken(view.airdropKey,
@@ -538,7 +532,7 @@ StackView {
onRemoteDestructRequested: {
if (token.isPrivilegedToken) {
tokenMasterActionPopup.openPopup(
- TokenMasterActionPopup.ActionType.RemotelyDestruct, name)
+ TokenMasterActionPopup.ActionType.RemotelyDestruct, name, address)
} else {
remotelyDestructPopup.open()
// TODO: set the address selected in the popup's list
@@ -572,16 +566,34 @@ StackView {
TokenMasterActionPopup {
id: tokenMasterActionPopup
+ property string address: ""
+
communityName: root.communityName
networkName: view.token.chainName
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.userName = userName
+ tokenMasterActionPopup.address = address
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 {
@@ -661,27 +673,32 @@ StackView {
RemotelyDestructPopup {
id: remotelyDestructPopup
+ property alias feeSubscriber: remotelyDestructFeeSubscriber
+
collectibleName: view.token.name
model: view.tokenOwnersModel || null
accounts: root.accounts
- chainName: view.token.chainName
-
- feeText: root.feeText
- isFeeLoading: root.isFeeLoading
- feeErrorText: root.feeErrorText
-
- onRemotelyDestructFeesRequested: {
- root.remotelyDestructFeesRequest(walletsAndAmounts,
- view.token.key,
- accountAddress)
- }
+ chainName: view.token.chainName
+ feeText: remotelyDestructFeeSubscriber.feeText
+ feeErrorText: remotelyDestructFeeSubscriber.feeErrorText
+ isFeeLoading: !remotelyDestructFeeSubscriber.feesResponse
+
onRemotelyDestructClicked: {
remotelyDestructPopup.close()
footer.accountAddress = accountAddress
footer.walletsAndAmounts = walletsAndAmounts
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 {
@@ -702,11 +719,13 @@ StackView {
id: signTransactionPopup
property bool isRemotelyDestructTransaction
+ property var feeSubscriber: isRemotelyDestructTransaction
+ ? remotelyDestructPopup.feeSubscriber
+ : burnTokensPopup.feeSubscriber
+
readonly property string tokenKey: tokenViewPage.token.key
function signTransaction() {
- root.setFeeLoading()
-
if(signTransactionPopup.isRemotelyDestructTransaction)
root.remotelyDestructCollectibles(footer.walletsAndAmounts,
tokenKey, footer.accountAddress)
@@ -723,37 +742,31 @@ StackView {
tokenName: footer.token.name
accountName: footer.token.accountName
networkName: footer.token.chainName
- feeText: root.feeText
- isFeeLoading: root.isFeeLoading
- errorText: root.feeErrorText
+ feeText: feeSubscriber.feeText
+ isFeeLoading: feeSubscriber.feeText === "" && feeSubscriber.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()
}
BurnTokensPopup {
id: burnTokensPopup
-
+
+ property alias feeSubscriber: burnTokensFeeSubscriber
communityName: root.communityName
tokenName: footer.token.name
remainingTokens: footer.token.remainingTokens
multiplierIndex: footer.token.multiplierIndex
tokenSource: footer.token.artworkSource
chainName: footer.token.chainName
+
+ onAmountToBurnChanged: burnTokensFeeSubscriber.updateAmount()
- feeText: root.feeText
- feeErrorText: root.feeErrorText
- isFeeLoading: root.isFeeLoading
+ feeText: burnTokensFeeSubscriber.feeText
+ feeErrorText: burnTokensFeeSubscriber.feeErrorText
+ isFeeLoading: burnTokensFeeSubscriber.feeText === "" && burnTokensFeeSubscriber.feeErrorText === ""
accounts: root.accounts
- onBurnFeesRequested: root.burnFeesRequested(footer.token.key, burnAmount, accountAddress)
-
onBurnClicked: {
burnTokensPopup.close()
footer.burnAmount = burnAmount
@@ -761,13 +774,25 @@ StackView {
signTransactionPopup.isRemotelyDestructTransaction = false
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 {
id: deleteTokenAlertPopup
- readonly property alias tokenName: view.token.name
+ readonly property string tokenName: view.token.name
width: 521
title: qsTr("Delete %1").arg(tokenName)
@@ -799,6 +824,7 @@ StackView {
}
delegate: TokenViewPage {
+ required property var model
implicitWidth: 0
anchors.fill: parent
diff --git a/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml b/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml
index 1e27c73a51..b888a3f478 100644
--- a/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml
+++ b/ui/app/AppLayouts/Communities/popups/BurnTokensPopup.qml
@@ -27,6 +27,9 @@ StatusDialog {
property url tokenSource
property string chainName
+ readonly property alias amountToBurn: d.amountToBurn
+ readonly property alias selectedAccountAddress: d.accountAddress
+
// Fees related properties:
property string feeText
property string feeErrorText: ""
@@ -38,7 +41,6 @@ StatusDialog {
signal burnClicked(string burnAmount, string accountAddress)
signal cancelClicked
- signal burnFeesRequested(string burnAmount, string accountAddress)
QtObject {
id: d
@@ -51,6 +53,8 @@ StatusDialog {
LocaleUtils.numberToLocaleString(remainingTokensFloat)
property string accountAddress
+ property string amountToBurn: !isFormValid ? "" :
+ specificAmountButton.checked ? amountInput.amount : root.remainingTokens
readonly property bool isFeeError: root.feeErrorText !== ""
@@ -119,7 +123,7 @@ StatusDialog {
font.pixelSize: Style.current.primaryTextFontSize
ButtonGroup.group: radioGroup
- onToggled: if(checked) amountToBurnInput.forceActiveFocus()
+ onToggled: if(checked) amountInput.forceActiveFocus()
}
AmountInput {
@@ -164,18 +168,6 @@ StatusDialog {
FeesBox {
id: feesBox
-
- readonly property bool triggerFeeReevaluation: {
- specificAmountButton.checked
- amountInput.amount
- feesBox.accountsSelector.currentIndex
-
- if (root.opened)
- requestFeeDelayTimer.restart()
-
- return true
- }
-
Layout.fillWidth: true
placeholderText: qsTr("Choose number of tokens to burn to see gas fees")
@@ -195,24 +187,6 @@ StatusDialog {
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 {
id: singleFeeModel
diff --git a/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml b/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml
index aa6ee8cd38..1000a54e26 100644
--- a/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml
+++ b/ui/app/AppLayouts/Communities/popups/RemotelyDestructPopup.qml
@@ -27,11 +27,17 @@ StatusDialog {
property string feeLabel: qsTr("Remotely destruct %1 token on %2").arg(root.collectibleName).arg(root.chainName)
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
property var accounts
+
signal remotelyDestructClicked(var walletsAndAmounts, string accountAddress)
- signal remotelyDestructFeesRequested(var walletsAndAmounts, string accountAddress)
QtObject {
id: d
@@ -74,17 +80,6 @@ StatusDialog {
d.walletsAndAmountsList, "amount")
const sum = amounts.reduce((a, b) => a + b, 0)
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,
accountsSelector.currentIndex)
d.accountAddress = item.address
-
- // Whenever a change in the form happens, new fee calculation:
- if (d.tokenCount > 0)
- d.sendFeeRequest()
}
QtObject {
diff --git a/ui/app/AppLayouts/Communities/popups/TokenMasterActionPopup.qml b/ui/app/AppLayouts/Communities/popups/TokenMasterActionPopup.qml
index ddf66b912a..ca299800ba 100644
--- a/ui/app/AppLayouts/Communities/popups/TokenMasterActionPopup.qml
+++ b/ui/app/AppLayouts/Communities/popups/TokenMasterActionPopup.qml
@@ -7,6 +7,7 @@ import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups.Dialog 0.1
import StatusQ.Core.Theme 0.1
+import StatusQ.Core.Utils 0.1
import AppLayouts.Communities.panels 1.0
@@ -36,11 +37,14 @@ StatusDialog {
property string communityName
property string userName
property string networkName
-
+
property string feeText
property string feeErrorText
property bool isFeeLoading
+
+
property var accountsModel
+ readonly property alias selectedAccount: d.accountAddress
readonly property string feeLabel: qsTr("Remotely destruct 1 TokenMaster token on %1").arg(
root.networkName)
@@ -140,9 +144,18 @@ StatusDialog {
readonly property string title: root.feeLabel
readonly property string feeText: root.isFeeLoading ?
- "" : root.feeText
+ "" : root.feeText
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 {
enabled: !root.isFeeLoading && root.feeErrorText === ""
- && root.feeText !== ""
text: {
if (root.actionType === TokenMasterActionPopup.ActionType.Ban)
return qsTr("Ban %1 and remotely destruct 1 token").arg(root.userName)
@@ -179,4 +191,10 @@ StatusDialog {
}
}
}
+
+ QtObject {
+ id: d
+
+ property string accountAddress: ""
+ }
}
diff --git a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml
index 7f3026a0e4..f5b08dc322 100644
--- a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml
+++ b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml
@@ -309,29 +309,6 @@ StatusSectionLayout {
readonly property CommunityTokensStore 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
communityName: root.community.name
communityLogo: root.community.image
@@ -356,22 +333,11 @@ StatusSectionLayout {
allNetworks: communityTokensStore.allNetworks
accounts: root.walletAccountsModel
- onDeployFeesRequested: {
- feeText = ""
- feeErrorText = ""
- isFeeLoading = true
+ onRegisterDeployFeesSubscriber: d.feesBroker.registerDeployFeesSubscriber(feeSubscriber)
- communityTokensStore.computeDeployFee(
- chainId, accountAddress, tokenType, !(mintPanel.isOwnerTokenDeployed && mintPanel.isTMasterTokenDeployed))
- }
+ onRegisterSelfDestructFeesSubscriber: d.feesBroker.registerSelfDestructFeesSubscriber(feeSubscriber)
- onBurnFeesRequested: {
- feeText = ""
- feeErrorText = ""
- isFeeLoading = true
-
- communityTokensStore.computeBurnFee(tokenKey, amount, accountAddress)
- }
+ onRegisterBurnTokenFeesSubscriber: d.feesBroker.registerBurnFeesSubscriber(feeSubscriber)
onMintCollectible:
communityTokensStore.deployCollectible(
@@ -384,17 +350,10 @@ StatusSectionLayout {
communityTokensStore.deployOwnerToken(
root.community.id, ownerToken, tMasterToken)
- onRemotelyDestructFeesRequest:
- communityTokensStore.computeSelfDestructFee(
- walletsAndAmounts, tokenKey, accountAddress)
-
onRemotelyDestructCollectibles:
communityTokensStore.remoteSelfDestructCollectibles(
root.community.id, walletsAndAmounts, tokenKey, accountAddress)
- onSignBurnTransactionOpened:
- communityTokensStore.computeBurnFee(tokenKey, amount, accountAddress)
-
onBurnToken:
communityTokensStore.burnToken(root.community.id, tokenKey, amount, accountAddress)
@@ -524,20 +483,16 @@ StatusSectionLayout {
membersModel: community.members
accountsModel: root.walletAccountsModel
-
onAirdropClicked: communityTokensStore.airdrop(
- root.community.id, airdropTokens, addresses,
- feeAccountAddress)
-
+ root.community.id, airdropTokens, addresses,
+ feeAccountAddress)
+
onNavigateToMintTokenSettings: {
root.goTo(Constants.CommunitySettingsSections.MintTokens)
mintPanel.openNewTokenForm(isAssetType)
}
- onAirdropFeesRequested:
- communityTokensStore.computeAirdropFee(
- root.community.id, contractKeysAndAmounts, addresses,
- feeAccountAddress)
+ onRegisterAirdropFeeSubscriber: d.feesBroker.registerAirdropFeesSubscriber(feeSubscriber)
}
}
@@ -556,6 +511,10 @@ StatusSectionLayout {
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) {
const stackContent = stackLayout.children
@@ -705,22 +664,6 @@ StatusSectionLayout {
Connections {
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) {
if (root.community.id !== communityId)
return
diff --git a/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml b/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml
index 56c3fe89f4..d635c40c70 100644
--- a/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml
+++ b/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml
@@ -24,6 +24,10 @@ StatusScrollView {
// https://bugreports.qt.io/browse/QTBUG-84269
/* 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
@@ -52,16 +56,12 @@ StatusScrollView {
readonly property string feeLabel:
isAssetView ? qsTr("Mint asset on %1").arg(token.chainName)
: qsTr("Mint collectible on %1").arg(token.chainName)
-
+
// Models:
property var tokenOwnersModel
// Required for preview mode:
property var accounts
- property string feeText
- property string feeErrorText
- property bool isFeeLoading
-
signal mintClicked()
signal airdropRequested(string address)
@@ -74,8 +74,6 @@ StatusScrollView {
signal kickRequested(string name, string contactId)
signal banRequested(string name, string contactId)
- signal deployFeesRequested
-
QtObject {
id: d
@@ -179,8 +177,6 @@ StatusScrollView {
accountsSelector.currentIndex)
token.accountAddress = item.address
token.accountName = item.name
-
- root.deployFeesRequested()
})
}
}
diff --git a/ui/app/AppLayouts/Communities/views/EditAirdropView.qml b/ui/app/AppLayouts/Communities/views/EditAirdropView.qml
index 4492f9d3c1..d1b973a688 100644
--- a/ui/app/AppLayouts/Communities/views/EditAirdropView.qml
+++ b/ui/app/AppLayouts/Communities/views/EditAirdropView.qml
@@ -59,13 +59,18 @@ StatusScrollView {
onFeesPerSelectedContractChanged: {
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({
contractUniqueKey: entry.contractUniqueKey,
title: qsTr("Airdrop %1 on %2")
.arg(ModelUtils.getByKey(root.selectedHoldingsModel, "contractUniqueKey", entry.contractUniqueKey, "symbol"))
.arg(ModelUtils.getByKey(root.selectedHoldingsModel, "contractUniqueKey", entry.contractUniqueKey, "networkText")),
- feeText: entry.feeText
+ feeText: entry.feeText ?? ""
})
})
}
diff --git a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml
index 05b065aaa4..5152e4fd77 100644
--- a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml
+++ b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml
@@ -24,17 +24,13 @@ StatusScrollView {
property int viewWidth: 560 // by design
property bool isAssetView: false
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
+
property var tokensModel
property var tokensModelWallet
-
- property TokenObject collectible: TokenObject {
- type: Constants.TokenType.ERC721
+ property TokenObject token: TokenObject {
+ type: root.isAssetView ? Constants.TokenType.ERC20 : Constants.TokenType.ERC721
}
-
- property TokenObject asset: TokenObject{
- type: Constants.TokenType.ERC20
- }
-
+
// Used for reference validation when editing a failed deployment
property string referenceName: ""
property string referenceSymbol: ""
@@ -53,12 +49,11 @@ StatusScrollView {
property bool isFeeLoading
readonly property string feeLabel:
- isAssetView ? qsTr("Mint asset on %1").arg(asset.chainName)
- : qsTr("Mint collectible on %1").arg(collectible.chainName)
+ isAssetView ? qsTr("Mint asset on %1").arg(root.token.chainName)
+ : qsTr("Mint collectible on %1").arg(root.token.chainName)
signal chooseArtWork
signal previewClicked
- signal deployFeesRequested
QtObject {
id: d
@@ -69,7 +64,7 @@ StatusScrollView {
&& symbolInput.valid
&& (unlimitedSupplyChecker.checked || (!unlimitedSupplyChecker.checked && parseInt(supplyInput.text) > 0))
&& (!root.isAssetView || (root.isAssetView && assetDecimalsInput.valid))
- && !root.isFeeLoading && root.feeErrorText === "" && !requestFeeDelayTimer.running
+ && deployFeeSubscriber.feeText !== "" && deployFeeSubscriber.feeErrorText === ""
readonly property int imageSelectorRectWidth: root.isAssetView ? 128 : 290
@@ -83,10 +78,7 @@ StatusScrollView {
contentHeight: mainLayout.height
Component.onCompleted: {
- if(root.isAssetView)
- networkSelector.setChain(asset.chainId)
- else
- networkSelector.setChain(collectible.chainId)
+ networkSelector.setChain(root.token.chainId)
}
ColumnLayout {
@@ -106,8 +98,8 @@ StatusScrollView {
Layout.fillWidth: true
Layout.preferredHeight: d.imageSelectorRectWidth
- dataImage: root.isAssetView ? asset.artworkSource : collectible.artworkSource
- artworkSource: root.isAssetView ? asset.artworkSource : collectible.artworkSource
+ dataImage: root.token.artworkSource
+ artworkSource: root.token.artworkSource
editorAnchorLeft: false
editorRoundedImage: root.isAssetView
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")
acceptButtonText: root.isAssetView ? qsTr("Upload asset icon") : qsTr("Upload collectible artwork")
- onArtworkSourceChanged: {
- if(root.isAssetView)
- asset.artworkSource = artworkSource
- else
- collectible.artworkSource = artworkSource
- }
- onArtworkCropRectChanged: {
- if(root.isAssetView)
- asset.artworkCropRect = artworkCropRect
- else
- collectible.artworkCropRect = artworkCropRect
- }
+ onArtworkSourceChanged: root.token.artworkSource = artworkSource
+ onArtworkCropRectChanged: root.token.artworkCropRect = artworkCropRect
}
CustomStatusInput {
id: nameInput
label: qsTr("Name")
- text: root.isAssetView ? asset.name : collectible.name
+ text: root.token.name
charLimit: 15
placeholderText: qsTr("Name")
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)")
extraValidator.validate: function (value) {
// 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(nameInput.text === root.referenceName)
return true
@@ -154,19 +136,14 @@ StatusScrollView {
}
extraValidator.errorMessage: qsTr("You have used this token name before")
- onTextChanged: {
- if(root.isAssetView)
- asset.name = text
- else
- collectible.name = text
- }
+ onTextChanged: root.token.name = text
}
CustomStatusInput {
id: descriptionInput
label: qsTr("Description")
- text: root.isAssetView ? asset.description : collectible.description
+ text: root.token.description
charLimit: 280
placeholderText: root.isAssetView ? qsTr("Describe your asset") : qsTr("Describe your collectible")
input.multiline: true
@@ -179,19 +156,14 @@ StatusScrollView {
regexValidator.regularExpression: Constants.regularExpressions.ascii
regexValidator.errorMessage: qsTr("Only A-Z, 0-9 and standard punctuation allowed")
- onTextChanged: {
- if(root.isAssetView)
- asset.description = text
- else
- collectible.description = text
- }
+ onTextChanged: root.token.description
}
CustomStatusInput {
id: symbolInput
label: qsTr("Symbol")
- text: root.isAssetView ? asset.symbol : collectible.symbol
+ text: root.token.symbol
charLimit: 6
placeholderText: root.isAssetView ? qsTr("e.g. ETH"): qsTr("e.g. DOODLE")
validationMode: root.validationMode
@@ -201,7 +173,7 @@ StatusScrollView {
regexValidator.regularExpression: Constants.regularExpressions.capitalOnly
extraValidator.validate: function (value) {
// 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(symbolInput.text.toUpperCase() === root.referenceSymbol.toUpperCase())
return true
@@ -216,10 +188,7 @@ StatusScrollView {
onTextChanged: {
const cursorPos = input.edit.cursorPosition
const upperSymbol = text.toUpperCase()
- if(root.isAssetView)
- asset.symbol = upperSymbol
- else
- collectible.symbol = upperSymbol
+ root.token.symbol = upperSymbol
text = upperSymbol // breaking the binding on purpose but so does validate() and onTextChanged() internal handler
input.edit.cursorPosition = cursorPos
}
@@ -237,15 +206,12 @@ StatusScrollView {
label: qsTr("Unlimited 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: {
if(!checked) supplyInput.forceActiveFocus()
- if(root.isAssetView)
- asset.infiniteSupply = checked
- else
- collectible.infiniteSupply = checked
+ root.token.infiniteSupply = checked
}
}
@@ -254,12 +220,8 @@ StatusScrollView {
visible: !unlimitedSupplyChecker.checked
label: qsTr("Total finite supply")
- text: {
- const token = root.isAssetView ? root.asset : root.collectible
-
- return SQUtils.AmountsArithmetic.toNumber(token.supply,
- token.multiplierIndex)
- }
+ text: SQUtils.AmountsArithmetic.toNumber(root.token.supply,
+ root.token.multiplierIndex)
placeholderText: qsTr("e.g. 300")
minLengthValidator.errorMessage: qsTr("Please enter a total finite supply")
@@ -274,9 +236,8 @@ StatusScrollView {
if (Number.isNaN(supplyNumber) || Object.values(errors).length)
return
- const token = root.isAssetView ? root.asset : root.collectible
token.supply = SQUtils.AmountsArithmetic.fromNumber(
- supplyNumber, token.multiplierIndex).toFixed(0)
+ supplyNumber, root.token.multiplierIndex).toFixed(0)
}
}
@@ -286,9 +247,9 @@ StatusScrollView {
visible: !root.isAssetView
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")
- checked: !collectible.transferable
+ checked: !root.token.transferable
- onCheckedChanged: collectible.transferable = !checked
+ onCheckedChanged: root.token.transferable = !checked
}
CustomSwitchRowComponent {
@@ -297,8 +258,8 @@ StatusScrollView {
visible: !root.isAssetView
label: qsTr("Remotely destructible")
description: qsTr("Enable to allow you to destroy tokens remotely. Useful for revoking permissions from individuals")
- checked: !!collectible ? collectible.remotelyDestruct : true
- onCheckedChanged: collectible.remotelyDestruct = checked
+ checked: !!root.token ? root.token.remotelyDestruct : true
+ onCheckedChanged: root.token.remotelyDestruct = checked
}
CustomStatusInput {
@@ -309,7 +270,7 @@ StatusScrollView {
charLimit: 2
charLimitLabel: qsTr("Max 10")
placeholderText: "2"
- text: !!asset ? asset.decimals : ""
+ text: root.token.decimals
validationMode: StatusInput.ValidationMode.Always
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)") :
@@ -317,7 +278,7 @@ StatusScrollView {
regexValidator.regularExpression: Constants.regularExpressions.numerical
extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 10 }
extraValidator.errorMessage: qsTr("Enter a number between 1 and 10")
- onTextChanged: asset.decimals = parseInt(text)
+ onTextChanged: root.token.decimals = parseInt(text)
}
FeesBox {
@@ -338,44 +299,17 @@ StatusScrollView {
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
- readonly property TokenObject token: root.isAssetView ? root.asset
- : root.collectible
-
// account can be changed also on preview page and it should be
// reflected in the form after navigating back
Connections {
- target: feesBox.token
+ target: root.token
function onAccountAddressChanged() {
const idx = SQUtils.ModelUtils.indexOf(
feesBox.accountsSelector.model, "address",
- feesBox.token.accountAddress)
+ root.token.accountAddress)
feesBox.accountsSelector.currentIndex = idx
}
@@ -387,8 +321,8 @@ StatusScrollView {
const item = SQUtils.ModelUtils.get(
accountsSelector.model, accountsSelector.currentIndex)
- token.accountAddress = item.address
- token.accountName = item.name
+ root.token.accountAddress = item.address
+ root.token.accountName = item.name
}
}
@@ -511,15 +445,9 @@ StatusScrollView {
multiSelection: false
onToggleNetwork: (network) => {
- if(root.isAssetView) {
- asset.chainId = network.chainId
- asset.chainName = network.chainName
- asset.chainIcon = network.iconUrl
- } else {
- collectible.chainId = network.chainId
- collectible.chainName = network.chainName
- collectible.chainIcon = network.iconUrl
- }
+ root.token.chainId = network.chainId
+ root.token.chainName = network.chainName
+ root.token.chainIcon = network.iconUrl
}
}
}
diff --git a/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml b/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml
index 3cd754a628..be20767f84 100644
--- a/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml
+++ b/ui/app/AppLayouts/Communities/views/EditOwnerTokenView.qml
@@ -69,7 +69,6 @@ StatusScrollView {
.arg(communityName).arg(ownerToken.chainName)
signal mintClicked
- signal deployFeesRequested
QtObject {
id: d
@@ -175,8 +174,6 @@ StatusScrollView {
onAddressChanged: {
ownerToken.accountAddress = address
tMasterToken.accountAddress = address
-
- requestFeeDelayTimer.restart()
}
control.onDisplayTextChanged: {
ownerToken.accountName = control.displayText
@@ -324,16 +321,7 @@ StatusScrollView {
tMasterToken.chainId = network.chainId
tMasterToken.chainName = network.chainName
tMasterToken.chainIcon = network.iconUrl
-
- requestFeeDelayTimer.restart()
}
}
}
-
- Timer {
- id: requestFeeDelayTimer
-
- interval: 500
- onTriggered: root.deployFeesRequested()
- }
}
diff --git a/ui/imports/shared/stores/CommunityTokensStore.qml b/ui/imports/shared/stores/CommunityTokensStore.qml
index 9b45a0d43f..fa3cefabf4 100644
--- a/ui/imports/shared/stores/CommunityTokensStore.qml
+++ b/ui/imports/shared/stores/CommunityTokensStore.qml
@@ -12,10 +12,10 @@ QtObject {
property var enabledNetworks: networksModule.enabled
property var allNetworks: networksModule.all
- signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error)
- signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error)
+ signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error, string responseId)
+ signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error, string responseId)
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 ownerTokenDeploymentStateChanged(string communityId, int status, string url)
@@ -63,18 +63,22 @@ QtObject {
readonly property Connections connections: Connections {
target: communityTokensModuleInst
- function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
- root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode)
+ function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
+ root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId)
}
- function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
- root.selfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode)
+ function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
+ root.selfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId)
}
function onAirdropFeesUpdated(jsonFees) {
root.airdropFeeUpdated(JSON.parse(jsonFees))
}
+ function onBurnFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId) {
+ root.burnFeeUpdated(ethCurrency, fiatCurrency, errorCode, responseId)
+ }
+
function onDeploymentStateChanged(communityId, status, url) {
root.deploymentStateChanged(communityId, status, url)
}
@@ -98,14 +102,22 @@ QtObject {
function onBurnStateChanged(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) {
- communityTokensModuleInst.computeDeployFee(chainId, accountAddress, tokenType, isOwnerDeployment)
+ // Burn:
+ 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) {
- communityTokensModuleInst.computeSelfDestructFee(JSON.stringify(walletsAndAmounts), tokenKey, accountAddress)
+ function computeSelfDestructFee(walletsAndAmounts, tokenKey, accountAddress, requestId) {
+ communityTokensModuleInst.computeSelfDestructFee(JSON.stringify(walletsAndAmounts), tokenKey, accountAddress, requestId)
}
function remoteSelfDestructCollectibles(communityId, 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) {
console.assert(typeof burnAmount === "string")
communityTokensModuleInst.burnTokens(communityId, tokenKey, burnAmount, accountAddress)
@@ -140,10 +146,4 @@ QtObject {
function airdrop(communityId, airdropTokens, 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)
- }
}