mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-28 07:15:21 +00:00
106988d534
This PR is refactoring the dapps service to avoid code duplication between SDKs and also to avoid overlapping requests/responses. It brings Browser Connect inline with Wallet Connect in terms of session management and sign transactions. New architecture: WalletConnectService becomes DAppsService. Its responsibility is to provide dapp access to the app. This is the component currently used by the UI What does it do: 1. Provide dapp APIs line connect, disconnect, session requests etc 2. Spawn app notifications on dapp events 3. Timeout requests if the dapp does not respons DAppsRequestHandler becomes DAppsModule. This component is consumed by the DAppService. Its responsibility is to aggregate all the building blocks for the dapps, but does not control any of the dapp features or consume the SDKs requests. What does it do: 1. Aggregate all the building blocks for dapps (currently known as plugins) DAppConnectionsPlugin - This component provides the session management features line connect, disconnect and provide a model with the connected dapps. SignRequestPlugin - This component provides the sign request management. It receives the sign request from the dapp, translates it to what Status understands and manages the lifecycle of the request.
671 lines
26 KiB
QML
671 lines
26 KiB
QML
import QtQuick 2.15
|
|
import QtQml 2.15
|
|
|
|
import AppLayouts.Wallet.services.dapps 1.0
|
|
import AppLayouts.Wallet.services.dapps.types 1.0
|
|
|
|
import StatusQ 0.1
|
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
|
|
|
import shared.stores 1.0
|
|
import utils 1.0
|
|
|
|
/// Plugin that listens for session requests and manages the lifecycle of the request.
|
|
SQUtils.QObject {
|
|
id: root
|
|
|
|
required property WalletConnectSDKBase sdk
|
|
required property DAppsStore store
|
|
/// Expected to have the following roles:
|
|
/// - topic
|
|
/// - name
|
|
/// - url
|
|
/// - iconUrl
|
|
/// - rawSessions
|
|
required property var dappsModel
|
|
/// Expected to have the following roles:
|
|
/// - tokensKey
|
|
/// - balances
|
|
required property var groupedAccountAssetsModel
|
|
/// Expected to have the following roles:
|
|
/// - layer
|
|
/// - chainId
|
|
required property var networksModel
|
|
/// Expected to have the following roles:
|
|
/// - address
|
|
required property var accountsModel
|
|
/// App currency
|
|
required property string currentCurrency
|
|
// SessionRequestsModel where the requests are stored
|
|
// This component will append and remove requests from this model
|
|
required property SessionRequestsModel requests
|
|
// Function to transform the eth value to fiat
|
|
property var getFiatValue: (maxFeesEthStr, token /*Constants.ethToken*/) => console.error("getFiatValue not implemented")
|
|
|
|
// Signals
|
|
/// Signal emitted when a session request is accepted
|
|
signal accepted(string topic, string id, string data)
|
|
/// Signal emitted when a session request is rejected
|
|
signal rejected(string topic, string id, bool hasError)
|
|
/// Signal emitted when a session request is completed
|
|
/// Completed mean that we have the ACK from the SDK
|
|
signal signCompleted(string topic, string id, bool userAccepted, string error)
|
|
|
|
function requestReceived(event, dappName, dappUrl, dappIcon, connectorId) {
|
|
d.onSessionRequestEvent(event, dappName, dappUrl, dappIcon, connectorId)
|
|
}
|
|
|
|
function requestResolved(topic, id) {
|
|
root.requests.removeRequest(topic, id)
|
|
}
|
|
|
|
function requestExpired(sessionId) {
|
|
d.onSessionRequestExpired(sessionId)
|
|
}
|
|
|
|
onRejected: (topic, id, hasError) => {
|
|
root.sdk.rejectSessionRequest(topic, id, hasError)
|
|
}
|
|
|
|
onAccepted: (topic, id, data) => {
|
|
root.sdk.acceptSessionRequest(topic, id, data)
|
|
}
|
|
|
|
Component {
|
|
id: sessionRequestComponent
|
|
|
|
SessionRequestWithAuth {
|
|
id: request
|
|
store: root.store
|
|
|
|
function signedHandler(topic, id, data) {
|
|
if (topic != request.topic || id != request.requestId) {
|
|
return
|
|
}
|
|
root.store.signingResult.disconnect(request.signedHandler)
|
|
|
|
let hasErrors = (data == "")
|
|
if (!hasErrors) {
|
|
root.accepted(topic, id, data)
|
|
} else {
|
|
request.reject(true)
|
|
}
|
|
}
|
|
|
|
onActiveChanged: {
|
|
if (active === false) {
|
|
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
}
|
|
if (active === true) {
|
|
d.subscribeForFeeUpdates(request.topic, request.requestId)
|
|
}
|
|
}
|
|
|
|
onRejected: (hasError) => {
|
|
root.rejected(request.topic, request.requestId, hasError)
|
|
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
}
|
|
|
|
onAuthFailed: () => {
|
|
root.rejected(request.topic, request.requestId, true /*hasError*/)
|
|
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
}
|
|
|
|
onExecute: (password, pin) => {
|
|
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
root.store.signingResult.connect(request.signedHandler)
|
|
let executed = false
|
|
try {
|
|
executed = d.executeSessionRequest(request, password, pin, request.feesInfo)
|
|
} catch (e) {
|
|
console.error("Error executing session request", e)
|
|
}
|
|
|
|
if (!executed) {
|
|
root.rejected(request.topic, request.requestId, true /*hasError*/)
|
|
root.store.signingResult.disconnect(request.signedHandler)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.sdk
|
|
|
|
function onSessionRequestEvent(sessionRequest) {
|
|
const { id, topic } = sessionRequest
|
|
const dapp = SQUtils.ModelUtils.getFirstModelEntryIf(root.dappsModel, (dapp) => {
|
|
if (dapp.topic === topic) {
|
|
return true
|
|
}
|
|
return !!SQUtils.ModelUtils.getFirstModelEntryIf(dapp.rawSessions, (session) => {
|
|
if (session.topic === topic) {
|
|
return true
|
|
}
|
|
})
|
|
})
|
|
|
|
if (!dapp) {
|
|
console.warn("Error finding dapp for topic", topic, "id", id)
|
|
root.sdk.rejectSessionRequest(topic, id, true)
|
|
return
|
|
}
|
|
|
|
root.requestReceived(sessionRequest, dapp.name, dapp.url, dapp.iconUrl, dapp.connectorId)
|
|
}
|
|
|
|
function onSessionRequestExpired(sessionId) {
|
|
root.requestExpired(sessionId)
|
|
}
|
|
|
|
function onSessionRequestUserAnswerResult(topic, id, accept, error) {
|
|
let request = root.requests.findRequest(topic, id)
|
|
|
|
if (request === null) {
|
|
console.error("Error finding event for topic", topic, "id", id)
|
|
return
|
|
}
|
|
Qt.callLater(() => root.requestResolved(topic, id))
|
|
|
|
if (error) {
|
|
root.signCompleted(topic, id, accept, error)
|
|
console.error(`Error accepting session request for topic: ${topic}, id: ${id}, accept: ${accept}, error: ${error}`)
|
|
return
|
|
}
|
|
|
|
root.signCompleted(topic, id, accept, "")
|
|
}
|
|
}
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
function onSessionRequestEvent(event, dappName, dappUrl, dappIcon, connectorId) {
|
|
try {
|
|
const res = d.resolve(event, dappName, dappUrl, dappIcon, connectorId)
|
|
if (res.conde === SessionRequest.Ignored) {
|
|
return
|
|
}
|
|
if (res.code !== SessionRequest.NoError) {
|
|
root.rejected(event.topic, event.id, true)
|
|
return
|
|
}
|
|
root.requests.enqueue(res.obj)
|
|
} catch (e) {
|
|
console.error("Error processing session request event", e)
|
|
root.rejected(event.topic, event.id, true)
|
|
}
|
|
}
|
|
|
|
function onSessionRequestExpired(sessionId) {
|
|
// Expired event coming from WC
|
|
// Handling as a failsafe in case the event is not processed by the SDK
|
|
let request = root.requests.findById(sessionId)
|
|
if (request === null) {
|
|
console.error("Error finding event for session id", sessionId)
|
|
return
|
|
}
|
|
|
|
if (request.isExpired()) {
|
|
return //nothing to do. The request is already expired
|
|
}
|
|
|
|
request.setExpired()
|
|
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
}
|
|
// returns {
|
|
// obj: obj or nil
|
|
// code: SessionRequest.ErrorCode
|
|
// }
|
|
function resolve(event, dappName, dappUrl, dappIcon, connectorId) {
|
|
const {request, error} = SessionRequestResolver.resolveEvent(event, root.accountsModel, root.networksModel, root.store.hexToDec)
|
|
if (error !== SessionRequest.NoError) {
|
|
return { obj: null, code: error }
|
|
}
|
|
if (!request) {
|
|
return { obj: null, code: SessionRequest.RuntimeError }
|
|
}
|
|
const mainNet = lookupMainnetNetwork()
|
|
if (!mainNet) {
|
|
console.error("Mainnet network not found")
|
|
return { obj: null, code: SessionRequest.RuntimeError }
|
|
}
|
|
|
|
updateFeesOnPreparedData(request)
|
|
|
|
let obj = sessionRequestComponent.createObject(null, {
|
|
event: request.event,
|
|
topic: request.topic,
|
|
requestId: request.requestId,
|
|
method: request.method,
|
|
accountAddress: request.account,
|
|
chainId: request.chainId,
|
|
data: request.data,
|
|
preparedData: JSON.stringify(request.preparedData),
|
|
expirationTimestamp: request.expiryTimestamp,
|
|
dappName,
|
|
dappUrl,
|
|
dappIcon,
|
|
sourceId: connectorId,
|
|
value: request.value
|
|
})
|
|
if (obj === null) {
|
|
console.error("Error creating SessionRequestResolved for event")
|
|
return { obj: null, code: SessionRequest.RuntimeError }
|
|
}
|
|
|
|
if (!request.transaction) {
|
|
obj.haveEnoughFunds = true
|
|
return { obj: obj, code: SessionRequest.NoError }
|
|
}
|
|
|
|
updateFeesParamsToPassedObj(obj)
|
|
|
|
return {
|
|
obj: obj,
|
|
code: SessionRequest.NoError
|
|
}
|
|
}
|
|
|
|
|
|
// Updates the fees to a SessionRequestResolved
|
|
function updateFeesParamsToPassedObj(requestItem) {
|
|
if (!(requestItem instanceof SessionRequestResolved)) {
|
|
return
|
|
}
|
|
if (!SessionRequest.isTransactionMethod(requestItem.method)) {
|
|
return
|
|
}
|
|
|
|
const mainNet = lookupMainnetNetwork()
|
|
if (!mainNet) {
|
|
console.error("Mainnet network not found")
|
|
return { obj: null, code: SessionRequest.RuntimeError }
|
|
}
|
|
|
|
const tx = SessionRequest.getTxObject(requestItem.method, requestItem.data)
|
|
requestItem.estimatedTimeCategory = root.store.getEstimatedTime(requestItem.chainId, tx.maxFeePerGas || tx.gasPrice || "")
|
|
|
|
let st = getEstimatedFeesStatus(tx, requestItem.method, requestItem.chainId, mainNet.chainId)
|
|
let fundsStatus = checkFundsStatus(st.feesInfo.maxFees, st.feesInfo.l1GasFee, requestItem.accountAddress, requestItem.chainId, mainNet.chainId, requestItem.value)
|
|
requestItem.fiatMaxFees = st.fiatMaxFees
|
|
requestItem.ethMaxFees = st.maxFeesEth
|
|
requestItem.haveEnoughFunds = fundsStatus.haveEnoughFunds
|
|
requestItem.haveEnoughFees = fundsStatus.haveEnoughForFees
|
|
requestItem.feesInfo = st.feesInfo
|
|
}
|
|
|
|
// Updates the fee in the transaction preview on a JS Object built by SessionRequest
|
|
function updateFeesOnPreparedData(request) {
|
|
if (!request.transaction && !request.preparedData instanceof Object) {
|
|
return
|
|
}
|
|
|
|
let fees = root.store.getSuggestedFees(request.chainId)
|
|
if (!request.preparedData.maxFeePerGas
|
|
&& request.preparedData.hasOwnProperty("maxFeePerGas")
|
|
&& fees.eip1559Enabled) {
|
|
request.preparedData.maxFeePerGas = d.getFeesForFeesMode(fees)
|
|
}
|
|
|
|
if (!request.preparedData.maxPriorityFeePerGas
|
|
&& request.preparedData.hasOwnProperty("maxPriorityFeePerGas")
|
|
&& fees.eip1559Enabled) {
|
|
request.preparedData.maxPriorityFeePerGas = fees.maxPriorityFeePerGas
|
|
}
|
|
|
|
if (!request.preparedData.gasPrice
|
|
&& request.preparedData.hasOwnProperty("gasPrice")
|
|
&& !fees.eip1559Enabled) {
|
|
request.preparedData.gasPrice = fees.gasPrice
|
|
}
|
|
}
|
|
|
|
/// Returns null if the network is not found
|
|
function lookupMainnetNetwork() {
|
|
return SQUtils.ModelUtils.getByKey(root.networksModel, "layer", 1)
|
|
}
|
|
|
|
function executeSessionRequest(request, password, pin, payload) {
|
|
if (!SessionRequest.getSupportedMethods().includes(request.method)) {
|
|
console.error("Unsupported method to execute: ", request.method)
|
|
return false
|
|
}
|
|
|
|
if (password === "") {
|
|
console.error("No password provided to sign message")
|
|
return false
|
|
}
|
|
|
|
if (request.method === SessionRequest.methods.sign.name) {
|
|
root.store.signMessageUnsafe(request.topic,
|
|
request.requestId,
|
|
request.accountAddress,
|
|
SessionRequest.methods.personalSign.getMessageFromData(request.data),
|
|
password,
|
|
pin)
|
|
} else if (request.method === SessionRequest.methods.personalSign.name) {
|
|
root.store.signMessage(request.topic,
|
|
request.requestId,
|
|
request.accountAddress,
|
|
SessionRequest.methods.personalSign.getMessageFromData(request.data),
|
|
password,
|
|
pin)
|
|
} else if (request.method === SessionRequest.methods.signTypedData_v4.name ||
|
|
request.method === SessionRequest.methods.signTypedData.name)
|
|
{
|
|
let legacy = request.method === SessionRequest.methods.signTypedData.name
|
|
root.store.safeSignTypedData(request.topic,
|
|
request.requestId,
|
|
request.accountAddress,
|
|
SessionRequest.methods.signTypedData.getMessageFromData(request.data),
|
|
request.chainId,
|
|
legacy,
|
|
password,
|
|
pin)
|
|
} else if (SessionRequest.isTransactionMethod(request.method)) {
|
|
let txObj = SessionRequest.getTxObject(request.method, request.data)
|
|
if (!!payload) {
|
|
let hexFeesJson = root.store.convertFeesInfoToHex(JSON.stringify(payload))
|
|
if (!!hexFeesJson) {
|
|
let feesInfo = JSON.parse(hexFeesJson)
|
|
if (feesInfo.maxFeePerGas) {
|
|
txObj.maxFeePerGas = feesInfo.maxFeePerGas
|
|
}
|
|
if (feesInfo.maxPriorityFeePerGas) {
|
|
txObj.maxPriorityFeePerGas = feesInfo.maxPriorityFeePerGas
|
|
}
|
|
}
|
|
delete txObj.gasLimit
|
|
delete txObj.gasPrice
|
|
}
|
|
// Remove nonce from txObj to be auto-filled by the wallet
|
|
delete txObj.nonce
|
|
|
|
if (request.method === SessionRequest.methods.signTransaction.name) {
|
|
root.store.signTransaction(request.topic,
|
|
request.requestId,
|
|
request.accountAddress,
|
|
request.chainId,
|
|
txObj,
|
|
password,
|
|
pin)
|
|
} else if (request.method === SessionRequest.methods.sendTransaction.name) {
|
|
root.store.sendTransaction(
|
|
request.topic,
|
|
request.requestId,
|
|
request.accountAddress,
|
|
request.chainId,
|
|
txObj,
|
|
password,
|
|
pin)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Returns {
|
|
// maxFees -> Big number in Gwei
|
|
// maxFeePerGas
|
|
// maxPriorityFeePerGas
|
|
// gasPrice
|
|
// }
|
|
function getEstimatedMaxFees(tx, method, chainId, mainNetChainId) {
|
|
const BigOps = SQUtils.AmountsArithmetic
|
|
const gasLimit = BigOps.fromString("21000")
|
|
const parsedTransaction = SessionRequest.parseTransaction(tx, root.store.hexToDec)
|
|
let gasPrice = BigOps.fromString(parsedTransaction.maxFeePerGas)
|
|
let maxFeePerGas = BigOps.fromString(parsedTransaction.maxFeePerGas)
|
|
let maxPriorityFeePerGas = BigOps.fromString(parsedTransaction.maxPriorityFeePerGas)
|
|
let l1GasFee = BigOps.fromNumber(0)
|
|
|
|
if (!maxFeePerGas || !maxPriorityFeePerGas || !gasPrice) {
|
|
const suggesteFees = getSuggestedFees(chainId)
|
|
maxFeePerGas = suggesteFees.maxFeePerGas
|
|
maxPriorityFeePerGas = suggesteFees.maxPriorityFeePerGas
|
|
gasPrice = suggesteFees.gasPrice
|
|
l1GasFee = suggesteFees.l1GasFee
|
|
}
|
|
|
|
let maxFees = BigOps.times(gasLimit, gasPrice)
|
|
return {maxFees, maxFeePerGas, maxPriorityFeePerGas, gasPrice, l1GasFee}
|
|
}
|
|
|
|
function getSuggestedFees(chainId) {
|
|
const BigOps = SQUtils.AmountsArithmetic
|
|
const fees = root.store.getSuggestedFees(chainId)
|
|
const maxPriorityFeePerGas = fees.maxPriorityFeePerGas
|
|
let maxFeePerGas
|
|
let gasPrice
|
|
if (fees.eip1559Enabled) {
|
|
if (!!fees.maxFeePerGasM) {
|
|
gasPrice = BigOps.fromNumber(fees.maxFeePerGasM)
|
|
maxFeePerGas = fees.maxFeePerGasM
|
|
} else if(!!tx.maxFeePerGas) {
|
|
let maxFeePerGasDec = root.store.hexToDec(tx.maxFeePerGas)
|
|
gasPrice = BigOps.fromString(maxFeePerGasDec)
|
|
maxFeePerGas = maxFeePerGasDec
|
|
} else {
|
|
console.error("Error fetching maxFeePerGas from fees or tx objects")
|
|
return
|
|
}
|
|
} else {
|
|
if (!!fees.gasPrice) {
|
|
gasPrice = BigOps.fromNumber(fees.gasPrice)
|
|
} else {
|
|
console.error("Error fetching suggested fees")
|
|
return
|
|
}
|
|
}
|
|
const l1GasFee = BigOps.fromNumber(fees.l1GasFee)
|
|
return {maxFeePerGas, maxPriorityFeePerGas, gasPrice, l1GasFee}
|
|
}
|
|
|
|
// Returned values are Big numbers
|
|
function getEstimatedFeesStatus(tx, method, chainId, mainNetChainId) {
|
|
const BigOps = SQUtils.AmountsArithmetic
|
|
|
|
const feesInfo = getEstimatedMaxFees(tx, method, chainId, mainNetChainId)
|
|
|
|
const totalMaxFees = BigOps.sum(feesInfo.maxFees, feesInfo.l1GasFee)
|
|
const maxFeesEth = BigOps.div(totalMaxFees, BigOps.fromNumber(1, 9))
|
|
|
|
const maxFeesEthStr = maxFeesEth.toString()
|
|
const fiatMaxFeesStr = root.getFiatValue(maxFeesEthStr, Constants.ethToken)
|
|
const fiatMaxFees = BigOps.fromString(fiatMaxFeesStr)
|
|
const symbol = root.currentCurrency
|
|
|
|
return {fiatMaxFees, maxFeesEth, symbol, feesInfo}
|
|
}
|
|
|
|
function getBalanceInEth(balances, address, chainId) {
|
|
const BigOps = SQUtils.AmountsArithmetic
|
|
const accEth = SQUtils.ModelUtils.getFirstModelEntryIf(balances, (balance) => {
|
|
return balance.account.toLowerCase() === address.toLowerCase() && balance.chainId == chainId
|
|
})
|
|
if (!accEth) {
|
|
console.error("Error balance lookup for account ", address, " on chain ", chainId)
|
|
return null
|
|
}
|
|
const accountFundsWei = BigOps.fromString(accEth.balance)
|
|
return BigOps.div(accountFundsWei, BigOps.fromNumber(1, 18))
|
|
}
|
|
|
|
// Returns {haveEnoughForFees, haveEnoughFunds} and true in case of error not to block request
|
|
function checkFundsStatus(maxFees, l1GasFee, address, chainId, mainNetChainId, value) {
|
|
const BigOps = SQUtils.AmountsArithmetic
|
|
let valueEth = BigOps.fromString(value)
|
|
let haveEnoughForFees = true
|
|
let haveEnoughFunds = true
|
|
|
|
let token = SQUtils.ModelUtils.getByKey(root.groupedAccountAssetsModel, "tokensKey", Constants.ethToken)
|
|
if (!token || !token.balances) {
|
|
console.error("Error token balances lookup for ETH", SQUtils.ModelUtils.modelToArray(root.groupedAccountAssetsModel))
|
|
console.error("Looking for tokensKey: ", Constants.ethToken)
|
|
return {haveEnoughForFees, haveEnoughFunds}
|
|
}
|
|
|
|
let chainBalance = getBalanceInEth(token.balances, address, chainId)
|
|
if (!chainBalance) {
|
|
console.error("Error fetching chain balance")
|
|
return {haveEnoughForFees, haveEnoughFunds}
|
|
}
|
|
haveEnoughFunds = BigOps.cmp(chainBalance, valueEth) >= 0
|
|
if (haveEnoughFunds) {
|
|
chainBalance = BigOps.sub(chainBalance, valueEth)
|
|
|
|
if (chainId == mainNetChainId) {
|
|
const finalFees = BigOps.sum(maxFees, l1GasFee)
|
|
let feesEth = BigOps.div(finalFees, BigOps.fromNumber(1, 9))
|
|
haveEnoughForFees = BigOps.cmp(chainBalance, feesEth) >= 0
|
|
} else {
|
|
const feesChain = BigOps.div(maxFees, BigOps.fromNumber(1, 9))
|
|
const haveEnoughOnChain = BigOps.cmp(chainBalance, feesChain) >= 0
|
|
|
|
const mainBalance = getBalanceInEth(token.balances, address, mainNetChainId)
|
|
if (!mainBalance) {
|
|
console.error("Error fetching mainnet balance")
|
|
return {haveEnoughForFees, haveEnoughFunds}
|
|
}
|
|
const feesMain = BigOps.div(l1GasFee, BigOps.fromNumber(1, 9))
|
|
const haveEnoughOnMain = BigOps.cmp(mainBalance, feesMain) >= 0
|
|
|
|
haveEnoughForFees = haveEnoughOnChain && haveEnoughOnMain
|
|
}
|
|
} else {
|
|
haveEnoughForFees = false
|
|
}
|
|
|
|
return {haveEnoughForFees, haveEnoughFunds}
|
|
}
|
|
|
|
property int selectedFeesMode: Constants.FeesMode.Medium
|
|
|
|
function getFeesForFeesMode(feesObj) {
|
|
if (!(feesObj.hasOwnProperty("maxFeePerGasL") &&
|
|
feesObj.hasOwnProperty("maxFeePerGasM") &&
|
|
feesObj.hasOwnProperty("maxFeePerGasH"))) {
|
|
throw new Error("inappropriate fees object provided")
|
|
}
|
|
|
|
switch (d.selectedFeesMode) {
|
|
case Constants.FeesMode.Low:
|
|
return feesObj.maxFeePerGasL
|
|
case Constants.FeesMode.Medium:
|
|
return feesObj.maxFeePerGasM
|
|
case Constants.FeesMode.High:
|
|
return feesObj.maxFeePerGasH
|
|
default:
|
|
throw new Error("unknown selected mode")
|
|
}
|
|
}
|
|
|
|
property var feesSubscriptions: []
|
|
|
|
function findSubscriptionIndex(topic, id) {
|
|
for (let i = 0; i < d.feesSubscriptions.length; i++) {
|
|
const subscription = d.feesSubscriptions[i]
|
|
if (subscription.topic == topic && subscription.id == id) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
function findChainIndex(chainId) {
|
|
for (let i = 0; i < feesSubscription.chainIds.length; i++) {
|
|
if (feesSubscription.chainIds[i] == chainId) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
function subscribeForFeeUpdates(topic, id) {
|
|
const request = requests.findRequest(topic, id)
|
|
if (request === null) {
|
|
console.error("Error finding event for subscribing for fees for topic", topic, "id", id)
|
|
return
|
|
}
|
|
|
|
const index = d.findSubscriptionIndex(topic, id)
|
|
if (index >= 0) {
|
|
return
|
|
}
|
|
|
|
d.feesSubscriptions.push({
|
|
topic: topic,
|
|
id: id,
|
|
chainId: request.chainId
|
|
})
|
|
|
|
for (let i = 0; i < feesSubscription.chainIds.length; i++) {
|
|
if (feesSubscription.chainIds == request.chainId) {
|
|
return
|
|
}
|
|
}
|
|
|
|
feesSubscription.chainIds.push(request.chainId)
|
|
feesSubscription.restart()
|
|
}
|
|
|
|
function unsubscribeForFeeUpdates(topic, id) {
|
|
const index = d.findSubscriptionIndex(topic, id)
|
|
if (index == -1) {
|
|
return
|
|
}
|
|
|
|
const chainId = d.feesSubscriptions[index].chainId
|
|
d.feesSubscriptions.splice(index, 1)
|
|
|
|
const chainIndex = d.findChainIndex(chainId)
|
|
if (index == -1) {
|
|
return
|
|
}
|
|
|
|
let found = false
|
|
for (let i = 0; i < d.feesSubscriptions.length; i++) {
|
|
if (d.feesSubscriptions[i].chainId == chainId) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
return
|
|
}
|
|
|
|
feesSubscription.chainIds.splice(chainIndex, 1)
|
|
if (feesSubscription.chainIds.length == 0) {
|
|
feesSubscription.stop()
|
|
}
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: feesSubscription
|
|
|
|
property var chainIds: []
|
|
|
|
interval: 5000
|
|
repeat: true
|
|
running: Qt.application.state === Qt.ApplicationActive
|
|
|
|
onTriggered: {
|
|
for (let i = 0; i < chainIds.length; i++) {
|
|
for (let j = 0; j < d.feesSubscriptions.length; j++) {
|
|
let subscription = d.feesSubscriptions[j]
|
|
if (subscription.chainId == chainIds[i]) {
|
|
let request = requests.findRequest(subscription.topic, subscription.id)
|
|
if (request === null) {
|
|
console.error("Error updating fees for topic", subscription.topic, "id", subscription.id)
|
|
continue
|
|
}
|
|
d.updateFeesParamsToPassedObj(request)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |