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