fix(Update fees periodically): Update airdrop flows to use fees subscriber

This commit is contained in:
Alex Jbanca 2023-09-01 07:28:50 +03:00 committed by Alex Jbanca
parent f2b3ba1ae7
commit f9e7265447
5 changed files with 182 additions and 187 deletions

View File

@ -104,8 +104,6 @@ SplitView {
assetsModel: AssetsModel {}
collectiblesModel: ListModel {}
accountsModel: ListModel {}
CollectiblesModel {
id: collectiblesModel
}

View File

@ -99,32 +99,35 @@ SplitView {
interval: 2000
property var response
property var feesPerContract: []
function createAmount(amount, symbol, decimals) {
return {
amount, symbol,
displayDecimals: decimals, stripTrailingZeroes: false
}
}
function requestMockedFees(contractKeysAndAmounts) {
const fees = []
if (!loader.item)
return
const view = loader.item
view.feesAvailable = false
view.totalFeeText = ""
view.feeErrorText = ""
view.feesPerSelectedContract = []
function createAmount(amount, symbol, decimals) {
return {
amount, symbol,
displayDecimals: decimals, stripTrailingZeroes: false
}
}
const fees = []
contractKeysAndAmounts.forEach(entry => {
fees.push({
ethFee: createAmount(0.0002120115, "ETH", 4),
fiatFee: createAmount(123.15, "USD", 2),
errorCode: 0,
contractUniqueKey: entry.contractUniqueKey
contractUniqueKey: entry.contractUniqueKey,
feeText: "0.0002120115 ETH (123.15 USD)"
})
})
response = {
fees, errorCode: feesErrorsButtonGroup.checkedButton.code,
totalEthFee: createAmount(0.0002120115 * fees.length, "ETH", 4),
totalFiatFee: createAmount(123.15 * fees.length, "USD", 2)
}
feesPerContract = fees
restart()
}
@ -134,7 +137,10 @@ SplitView {
return
const view = loader.item
view.airdropFees = response
view.totalFeeText = createAmount(0.0002120115 * feesPerContract.length, "ETH", 4) + "(" ,createAmount(123.15 * feesPerContract.length, "USD", 2),"USD)"
view.feeErrorText = feesErrorsButtonGroup.checkedButton.code ? feesErrorsButtonGroup.checkedButton.text : ""
view.feesAvailable = true
view.feesPerSelectedContract = feesCalculationTimer.feesPerContract
}
}
@ -252,6 +258,14 @@ SplitView {
assetsModel: AssetsModel {}
collectiblesModel: CollectiblesModel {}
membersModel: members
totalFeeText: ""
feeErrorText: ""
feesPerSelectedContract: []
feesAvailable: false
onShowingFeesChanged: {
feesCalculationTimer.requestMockedFees(loader.item.selectedContractKeysAndAmounts)
}
accountsModel: ListModel {
ListElement {
@ -283,15 +297,6 @@ SplitView {
"membersPubKeys", "feeAccountAddress"],
arguments)
}
onAirdropFeesRequested: {
logs.logEvent("EditAirdropView::airdropFeesRequested",
["contractKeysAndAmounts", "addresses",
"feeAccountAddress"],
arguments)
feesCalculationTimer.requestMockedFees(contractKeysAndAmounts)
}
}
}
}
@ -317,6 +322,12 @@ SplitView {
id: feesErrorsButtonGroup
buttons: feesErrorsRow.children
onCheckedButtonChanged: {
if(!loader.item)
return
feesCalculationTimer.requestMockedFees(loader.item.selectedContractKeysAndAmounts)
}
}
RowLayout {

View File

@ -0,0 +1,70 @@
import QtQuick 2.15
import StatusQ.Core 0.1
import utils 1.0
/*!
\qmltype AirdropFeesSubscriber
\inherits QtObject
\brief Helper object that holds the request data and the fee response when available
*/
QtObject {
id: root
required property string communityId
required property var contractKeysAndAmounts
required property var addressesToAirdrop
required property string feeAccountAddress
required property bool enabled
// JS object specifing fees for the airdrop operation, should be set to
// provide response to airdropFeesRequested signal.
//
// The expected structure is as follows:
// {
// fees: [{
// ethFee: {CurrencyAmount JSON},
// fiatFee: {CurrencyAmount JSON},
// contractUniqueKey: string,
// errorCode: ComputeFeeErrorCode (int)
// }],
// totalEthFee: {CurrencyAmount JSON},
// totalFiatFee: {CurrencyAmount JSON},
// errorCode: ComputeFeeErrorCode (int)
// }
property var airdropFeesResponse: null
readonly property string feesError: {
if (!airdropFeesResponse) return ""
if (airdropFeesResponse.errorCode === Constants.ComputeFeeErrorCode.Success) return ""
if (airdropFeesResponse.errorCode === Constants.ComputeFeeErrorCode.Balance)
return qsTr("Your account does not have enough ETH to pay the gas fee for this airdrop. Try adding some ETH to your account.")
if (airdropFeesResponse.errorCode === Constants.ComputeFeeErrorCode.Infura)
return qsTr("Infura error")
return qsTr("Unknown error")
}
readonly property string totalFee: {
if (!airdropFeesResponse || !Object.values(airdropFeesResponse.totalEthFee).length || !Object.values(airdropFeesResponse.totalFiatFee).length) return ""
if (airdropFeesResponse.errorCode !== Constants.ComputeFeeErrorCode.Success && airdropFeesResponse.errorCode !== Constants.ComputeFeeErrorCode.Balance)
return ""
return `${LocaleUtils.currencyAmountToLocaleString(airdropFeesResponse.totalEthFee)} (${LocaleUtils.currencyAmountToLocaleString(airdropFeesResponse.totalFiatFee)})`
}
readonly property var feesPerContract: {
if (!airdropFeesResponse || !Object.values(airdropFeesResponse.fees).length || totalFee == "") return []
return airdropFeesResponse.fees.map(fee => {
return {
contractUniqueKey: fee.contractUniqueKey,
feeText: `${LocaleUtils.currencyAmountToLocaleString(fee.ethFee)} (${LocaleUtils.currencyAmountToLocaleString(fee.fiatFee)})`
}
})
}
}

View File

@ -5,6 +5,7 @@ import StatusQ.Controls 0.1
import AppLayouts.Communities.layouts 1.0
import AppLayouts.Communities.views 1.0
import AppLayouts.Communities.helpers 1.0
import utils 1.0
@ -19,6 +20,7 @@ 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
@ -32,11 +34,6 @@ StackView {
required property var membersModel
required property var accountsModel
// JS object specifing fees for the airdrop operation, should be set to
// provide response to airdropFeesRequested signal.
// Refer EditAirdropView::airdropFees for details.
property var airdropFees: null
property int viewWidth: 560 // by design
property string previousPageName: depth > 1 ? qsTr("Airdrops") : ""
@ -64,7 +61,7 @@ StackView {
id: d
readonly property bool isAdminOnly: root.isAdmin && !root.isPrivilegedTokenOwnerProfile
property AirdropFeesSubscriber aidropFeeSubscriber: null
signal selectToken(string key, string amount, int type)
signal addAddresses(var addresses)
}
@ -80,7 +77,6 @@ StackView {
text: qsTr("New Airdrop")
enabled: !d.isAdminOnly && root.arePrivilegedTokensDeployed
onClicked: root.push(newAirdropView, StackView.Immediate)
}
]
@ -121,10 +117,10 @@ StackView {
collectiblesModel: root.collectiblesModel
membersModel: root.membersModel
accountsModel: root.accountsModel
Binding on airdropFees {
value: root.airdropFees
}
totalFeeText: feesSubscriber.totalFee
feeErrorText: feesSubscriber.feesError
feesPerSelectedContract: feesSubscriber.feesPerContract
feesAvailable: !!feesSubscriber.airdropFeesResponse
onAirdropClicked: {
root.airdropClicked(airdropTokens, addresses, feeAccountAddress)
@ -136,7 +132,16 @@ StackView {
Component.onCompleted: {
d.selectToken.connect(view.selectToken)
d.addAddresses.connect(view.addAddresses)
airdropFeesRequested.connect(root.airdropFeesRequested)
d.aidropFeeSubscriber = feesSubscriber
}
AirdropFeesSubscriber {
id: feesSubscriber
enabled: view.visible && view.showingFees
communityId: view.communityDetails.id
contractKeysAndAmounts: view.selectedContractKeysAndAmounts
addressesToAirdrop: view.selectedAddressesToAirdrop
feeAccountAddress: view.selectedFeeAccount
}
}
}

View File

@ -34,27 +34,41 @@ StatusScrollView {
// A model containing accounts from which the fee can be paid:
required property var accountsModel
// JS object specifing fees for the airdrop operation, should be set to
// provide response to airdropFeesRequested signal.
//
// The expected structure is as follows:
// {
// fees: [{
// ethFee: {CurrencyAmount JSON},
// fiatFee: {CurrencyAmount JSON},
// contractUniqueKey: string,
// errorCode: ComputeFeeErrorCode (int)
// }],
// totalEthFee: {CurrencyAmount JSON},
// totalFiatFee: {CurrencyAmount JSON},
// errorCode: ComputeFeeErrorCode (int)
// }
property var airdropFees: null
// Text to display as total fee
required property string totalFeeText
// Text to display in case of error
required property string feeErrorText
// Array containing the fees for each token
// [{contractUniqueKey: string, feeText: string}]
required property var feesPerSelectedContract
// Bool property indicating whether the fees are available
required property bool feesAvailable
property int viewWidth: 560 // by design
readonly property var selectedHoldingsModel: ListModel {}
// Array containing the contract keys and amounts of the tokens to be airdropped
readonly property alias selectedContractKeysAndAmounts: d.selectedContractKeysAndAmounts
// Array containing the addresses to which the tokens will be airdropped
readonly property alias selectedAddressesToAirdrop: d.selectedAddressesToAirdrop
// The address of the account from which the fee will be paid
readonly property alias selectedFeeAccount: d.selectedFeeAccount
// Bool property indicating whether the fees are shown
readonly property bool showingFees: d.showFees
onFeesPerSelectedContractChanged: {
feesModel.clear()
feesPerSelectedContract.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
})
})
}
ModelChangeTracker {
id: holdingsModelTracker
@ -80,20 +94,12 @@ StatusScrollView {
signal airdropClicked(var airdropTokens, var addresses, string feeAccountAddress)
signal airdropFeesRequested(var contractKeysAndAmounts, var addresses, string feeAccountAddress)
signal navigateToMintTokenSettings(bool isAssetType)
function selectToken(key, amount, type) {
if(selectedHoldingsModel)
selectedHoldingsModel.clear()
const tokenModel = type === Constants.TokenType.ERC20 ?
root.assetsModel : root.collectiblesModel
const modelItem = PermissionsHelpers.getTokenByKey(
tokenModel, key)
const entry = d.prepareEntry(key, amount, type)
entry.valid = true
selectedHoldingsModel.append(entry)
@ -103,51 +109,6 @@ StatusScrollView {
addresses.addAddresses(_addresses)
}
onAirdropFeesChanged: {
if (root.airdropFees === null)
return
const fees = root.airdropFees.fees
const errorCode = root.airdropFees.errorCode
function buildFeeString(ethFee, fiatFee) {
return `${LocaleUtils.currencyAmountToLocaleString(ethFee)} (${LocaleUtils.currencyAmountToLocaleString(fiatFee)})`
}
d.feesError = ""
d.totalFee = ""
if (errorCode === Constants.ComputeFeeErrorCode.Infura) {
d.feesError = qsTr("Infura error")
return
}
if (errorCode === Constants.ComputeFeeErrorCode.Success ||
errorCode === Constants.ComputeFeeErrorCode.Balance) {
fees.forEach(fee => {
const idx = ModelUtils.indexOf(
feesModel, "contractUniqueKey",
fee.contractUniqueKey)
feesModel.set(idx, {
feeText: buildFeeString(fee.ethFee, fee.fiatFee)
})
})
d.totalFee = buildFeeString(
root.airdropFees.totalEthFee,
root.airdropFees.totalFiatFee)
if (errorCode === Constants.ComputeFeeErrorCode.Balance) {
d.feesError = qsTr("Your account does not have enough ETH to pay the gas fee for this airdrop. Try adding some ETH to your account.")
}
return
}
d.feesError = qsTr("Unknown error")
}
QtObject {
id: d
@ -155,25 +116,32 @@ StatusScrollView {
readonly property int dropdownHorizontalOffset: 4
readonly property int dropdownVerticalOffset: 1
property string feesError
property string totalFee
readonly property bool isFeeLoading:
root.airdropFees === null ||
(root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Success &&
root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Balance)
readonly property bool showFees: root.selectedHoldingsModel.count > 0
&& airdropRecipientsSelector.valid
&& airdropRecipientsSelector.count > 0
readonly property int totalRevision: holdingsModelTracker.revision
+ addressesModelTracker.revision
+ membersModelTracker.revision
+ feesBox.accountsSelector.currentIndex
+ (d.showFees ? 1 : 0)
readonly property var selectedContractKeysAndAmounts: {
//Depedencies:
root.selectedHoldingsModel
holdingsModelTracker.revision
onTotalRevisionChanged: Qt.callLater(() => d.resetFees())
return ModelUtils.modelToArray(
root.selectedHoldingsModel,
["contractUniqueKey", "amount"])
}
readonly property var selectedAddressesToAirdrop: {
//Dependecies:
addresses
addressesModelTracker.revision
return ModelUtils.modelToArray(
addresses, ["address"]).map(e => e.address)
.concat([...selectedKeysFilter.keys])
}
readonly property string selectedFeeAccount: ModelUtils.get(root.accountsModel,
feesBox.accountIndex).address
function prepareEntry(key, amount, type) {
const tokenModel = type === Constants.TokenType.ERC20
@ -199,61 +167,6 @@ StatusScrollView {
symbol: modelItem.symbol
}
}
function rebuildFeesModel() {
feesModel.clear()
const airdropTokens = ModelUtils.modelToArray(
root.selectedHoldingsModel,
["contractUniqueKey", "accountName",
"symbol", "amount", "tokenText",
"networkText"])
airdropTokens.forEach(entry => {
feesModel.append({
contractUniqueKey: entry.contractUniqueKey,
title: qsTr("Airdrop %1 on %2")
.arg(entry.symbol)
.arg(entry.networkText),
feeText: ""
})
})
}
function requestFees() {
const airdropTokens = ModelUtils.modelToArray(
root.selectedHoldingsModel,
["contractUniqueKey", "amount"])
const contractKeysAndAmounts = airdropTokens.map(item => ({
amount: item.amount,
contractUniqueKey: item.contractUniqueKey
}))
const addressesArray = ModelUtils.modelToArray(
addresses, ["address"]).map(e => e.address)
const airdropAddresses = [...selectedKeysFilter.keys]
const accountItem = ModelUtils.get(root.accountsModel,
feesBox.accountIndex)
airdropFeesRequested(contractKeysAndAmounts, addressesArray.concat(airdropAddresses),
accountItem.address)
}
function resetFees() {
root.airdropFees = null
d.feesError = ""
d.totalFee = ""
if (!d.showFees) {
feesModel.clear()
return
}
d.rebuildFeesModel()
d.requestFees()
}
}
ListModel {
@ -622,10 +535,10 @@ StatusScrollView {
model: feesModel
accountsSelector.model: root.accountsModel
totalFeeText: d.totalFee
totalFeeText: root.totalFeeText
placeholderText: qsTr("Add valid “What” and “To” values to see fees")
accountErrorText: d.feesError
accountErrorText: root.feeErrorText
}
WarningPanel {
@ -646,7 +559,7 @@ StatusScrollView {
Layout.fillWidth: true
Layout.topMargin: Style.current.bigPadding
text: qsTr("Create airdrop")
enabled: root.isFullyFilled && !d.isFeeLoading && d.feesError === ""
enabled: root.isFullyFilled && root.feesAvailable && root.feeErrorText === ""
onClicked: {
const accountItem = ModelUtils.get(root.accountsModel,
@ -666,8 +579,8 @@ StatusScrollView {
model: feesModel
totalFeeText: d.totalFee
errorText: d.feesError
totalFeeText: root.totalFeeText
errorText: root.feeErrorText
onOpened: {
const title1 = qsTr("Sign transaction - Airdrop %n token(s)", "",
@ -676,8 +589,6 @@ StatusScrollView {
addresses.count + airdropRecipientsSelector.membersModel.count)
title = `${title1} ${title2}`
d.resetFees()
}
onSignTransactionClicked: {