fix(Update fees periodically): Implementing periodic fee update for airdrops, minting, self destruct and burning transactions

This commit is contained in:
Alex Jbanca 2023-09-01 12:27:44 +03:00 committed by Alex Jbanca
parent f9e7265447
commit 624b758c85
30 changed files with 1119 additions and 422 deletions

View File

@ -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)

View File

@ -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 == ""):

View File

@ -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
} }
} }
} }

View File

@ -62,6 +62,9 @@ SplitView {
token: tokenObject token: tokenObject
tokenOwnersModel: TokenHoldersModel {} tokenOwnersModel: TokenHoldersModel {}
feeText: "0.01"
feeErrorText: ""
isFeeLoading: false
accounts: WalletAccountsModel {} accounts: WalletAccountsModel {}

View File

@ -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

View File

@ -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()
}
} }
} }

View File

@ -74,12 +74,6 @@ SplitView {
]) ])
close() close()
} }
onRemotelyDestructFeesRequested: {
logs.logEvent("RemoteSelfDestructPopup::onRemotelyDestructFeesRequested",
["walletsAndAmounts", "accountAddress"], [
JSON.stringify(walletsAndAmounts), accountAddress
])
}
Component.onCompleted: { Component.onCompleted: {
open() open()

View File

@ -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: {}
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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>

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)
} }
} }
} }

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)
@ -140,9 +144,18 @@ StatusDialog {
readonly property string title: root.feeLabel readonly property string title: root.feeLabel
readonly property string feeText: root.isFeeLoading ? readonly property string feeText: root.isFeeLoading ?
"" : 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: ""
}
} }

View File

@ -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,20 +483,16 @@ 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)
onNavigateToMintTokenSettings: { onNavigateToMintTokenSettings: {
root.goTo(Constants.CommunitySettingsSections.MintTokens) root.goTo(Constants.CommunitySettingsSections.MintTokens)
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

View File

@ -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()
}) })
} }
} }

View File

@ -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 ?? ""
}) })
}) })
} }

View File

@ -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
}
} }
} }
} }

View File

@ -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()
}
} }

View File

@ -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)
}
} }