feat(WC): Handle unavailable chains or internet connection

This commits implements the `connect` `disconnect` session events for WC and also disables primary buttons for WC whenever there is no connection to internet or chains.

+ update tests
This commit is contained in:
Alex Jbanca 2024-10-23 15:58:45 +03:00
parent 94672b0e36
commit adf760e81c
No known key found for this signature in database
GPG Key ID: 6004079575C21C5D
13 changed files with 289 additions and 12 deletions

View File

@ -72,6 +72,7 @@ Item {
accountsModel: wcService.validAccounts
networksModel: wcService.flatNetworks
sessionRequestsModel: wcService.sessionRequestsModel
enabled: wcService.isServiceOnline
//formatBigNumber: (number, symbol, noSymbolOption) => wcService.walletRootStore.currencyStore.formatBigNumber(number, symbol, noSymbolOption)
@ -200,6 +201,13 @@ Item {
}
}
StatusBaseText { text: "Networks Down" }
NetworkFilter {
id: networkFilter
flatNetworks: walletConnectService.walletRootStore.filteredFlatModel
}
// spacer
ColumnLayout {}
@ -337,6 +345,8 @@ Item {
projectId: projectIdText.projectId
}
blockchainNetworksDown: networkFilter.selection
store: SharedStores.DAppsStore {
signal dappsListReceived(string dappsJson)
signal userAuthenticated(string topic, string id, string password, string pin)

View File

@ -121,6 +121,7 @@ Item {
onPairingValidated: function(validationState) {
onPairingValidatedTriggers.push({validationState})
}
blockchainNetworksDown: []
}
}
@ -221,6 +222,33 @@ Item {
}
}
readonly property ListModel filteredFlatModelWithOnlineStat: ListModel {
ListElement {
chainId: 1
layer: 1
isOnline: true
}
ListElement {
chainId: 2
chainName: "Test Chain"
iconUrl: "network/Network=Ethereum"
layer: 2
isOnline: true
}
// Used by tst_balanceCheck
ListElement {
chainId: 11155111
layer: 1
isOnline: true
}
// Used by tst_balanceCheck
ListElement {
chainId: 421613
layer: 2
isOnline: true
}
}
readonly property ListModel nonWatchAccounts: ListModel {
ListElement {
address: "0x1"
@ -299,9 +327,10 @@ Item {
sdk: sdk,
store: store,
accountsModel: walletStore.nonWatchAccounts,
networksModel: walletStore.filteredFlatModel
networksModel: walletStore.filteredFlatModelWithOnlineStat
})
verify(!!handler)
sdk.getActiveSessionsCallbacks = []
}
function cleanup() {
@ -791,6 +820,7 @@ Item {
verify(!!walletStore)
provider = createTemporaryObject(dappsListProviderComponent, root, {sdk: sdk, store: store, supportedAccountsModel: walletStore.nonWatchAccounts})
verify(!!provider)
sdk.getActiveSessionsCallbacks = []
}
function cleanup() {

View File

@ -0,0 +1,56 @@
import QtQuick 2.15
import StatusQ 0.1
import StatusQ.Core.Utils 0.1
// This component receives the networksModel
// and breaks down the online status of each chain
QObject {
id: root
// Required roleNames: chainId, isOnline
required property var networksModel
readonly property bool allOffline: d.allOffline
readonly property bool allOnline: d.allOnline
property bool active: true
signal chainOnlineChanged(int chainId, bool isOnline)
// Network online observer
// `chainOnlineChanged` signal when the online status of a chain changes
Instantiator {
id: networkOnlineObserver
model: root.networksModel
active: root.active
delegate: QtObject {
required property var model
property var /*var intended*/ isOnline: model.isOnline
onIsOnlineChanged: {
root.chainOnlineChanged(model.chainId, isOnline)
}
}
}
// Aggregator to count the number of online chains
SumAggregator{
id: aggregator
model: root.active ? root.networksModel : null
roleName: "isOnline"
}
// Network checker to check if the device is online
NetworkChecker {
id: networkChecker
active: root.active
}
QtObject {
id: d
readonly property bool allOffline: !networkChecker.isOnline || aggregator.value === 0
readonly property bool allOnline: networkChecker.isOnline &&
aggregator.value > 0 &&
aggregator.value === networksModel.ModelCount.count
}
}

View File

@ -2,3 +2,4 @@ ChartDataBase 1.0 ChartDataBase.qml
TokenBalanceHistoryData 1.0 TokenBalanceHistoryData.qml
TokenMarketValuesData 1.0 TokenMarketValuesData.qml
singleton NetworkModelHelpers 1.0 NetworkModelHelpers.qml
ChainsAvailabilityWatchdog 1.0 ChainsAvailabilityWatchdog.qml

View File

@ -146,7 +146,7 @@ Item {
visible: !root.walletStore.showSavedAddresses
&& root.dappsEnabled
&& wcService.serviceAvailableToCurrentAddress
enabled: !!Global.walletConnectService
enabled: !!wcService && wcService.isServiceOnline
loginType: root.loginType

View File

@ -16,11 +16,13 @@ SQUtils.QObject {
required property WalletConnectSDKBase sdk
required property DAppsStore store
required property var accountsModel
// Required roles: chainId, layer, isOnline
required property var networksModel
required property CurrenciesStore currenciesStore
required property WalletStore.WalletAssetsStore assetsStore
property alias requestsModel: requests
readonly property bool isServiceOnline: chainsSupervisorPlugin.anyChainAvailable && sdk.sdkReady
function subscribeForFeeUpdates(topic, id) {
d.subscribeForFeeUpdates(topic, id)
@ -1175,4 +1177,11 @@ SQUtils.QObject {
}
}
}
ChainsSupervisorPlugin {
id: chainsSupervisorPlugin
sdk: root.sdk
networksModel: root.networksModel
}
}

View File

@ -364,14 +364,6 @@ WalletConnectSDKBase {
`
)
}
function connected() {
console.debug(`WC WalletConnectSDK.wcCall.connected;`)
}
function disconnected() {
console.debug(`WC WalletConnectSDK.wcCall.disconnected;`)
}
}
QtObject {

View File

@ -32,6 +32,8 @@ QObject {
required property WalletConnectSDKBase wcSDK
required property DAppsStore store
required property var walletRootStore
// // Array[chainId] of the networks that are down
required property var blockchainNetworksDown
//output properties
/// Model contaning all dApps available for the currently selected account
@ -49,6 +51,8 @@ QObject {
/// TODO: refactor
readonly property alias connectorDAppsProvider: connectorDAppsProvider
readonly property bool isServiceOnline: requestHandler.isServiceOnline
// methods
/// Triggers the signing process for the given session request
/// @param topic The topic of the session
@ -250,7 +254,16 @@ QObject {
sdk: root.wcSDK
store: root.store
accountsModel: root.validAccounts
networksModel: root.flatNetworks
networksModel: SortFilterProxyModel {
sourceModel: root.flatNetworks
proxyRoles: [
FastExpressionRole {
name: "isOnline"
expression: !root.blockchainNetworksDown.map(Number).includes(model.chainId)
expectedRoles: "chainId"
}
]
}
currenciesStore: root.walletRootStore.currencyStore
assetsStore: root.walletRootStore.walletAssetsStore

View File

@ -0,0 +1,150 @@
import QtQuick 2.15
import AppLayouts.Wallet.helpers 1.0
import AppLayouts.Wallet.services.dapps 1.0
import AppLayouts.Wallet.services.dapps.types 1.0 as DAppsTypes
import StatusQ 0.1
import StatusQ.Core.Utils 0.1
// This plugin handles the chain ability for the dapps
// It monitors the chain availability and updates the dapps accordingly
// When all chains are offline, it will inform each session that the chains are offline
// When a chain comes back online, it will check if there are any active sessions and inform them
// See [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)
QObject {
id: root
required property WalletConnectSDKBase sdk
// Required roles: chainId, isOnline
required property var networksModel
// Happens when any chain is available
readonly property bool anyChainAvailable: !chainsAvailabilityWatchdog.allOffline
ChainsAvailabilityWatchdog {
id: chainsAvailabilityWatchdog
// property used for spam protection
property var lastNotifiedCStatuses: new Map()
networksModel: root.networksModel
onChainOnlineChanged: (chainId, isOnline) => {
if (!isOnline || !sdk.sdkReady) {
return
}
// Spam protection. Spamming the SDK could result to blocks
if (lastNotifiedCStatuses.has(chainId) && lastNotifiedCStatuses.get(chainId) === isOnline) {
return
}
lastNotifiedCStatuses.set(chainId, isOnline)
d.notifyChainConnected(chainId)
}
onAllOfflineChanged: {
if(!sdk.sdkReady) {
return
}
if (allOffline) {
lastNotifiedCStatuses.clear()
d.notifyAllChainsDisconnected()
}
}
}
QtObject {
id: d
function sessionHasEventSupport(session, event) {
try {
if (!event) {
return false
}
if (session.namespaces.eip155.events.includes(event)) {
return true
}
return false
} catch (e) {
console.error("Error checking sessionHasEventSupport: ", e)
return false
}
}
function sessionHasChainSupport(session, chainId) {
try {
if (!chainId) {
return false
}
let chainIds = session.namespaces.eip155.chains.map(DAppsHelpers.chainIdFromEip155);
if (!chainIds.includes(chainId)) {
return false
}
let accounts = session.namespaces.eip155.accounts
let chainAccounts = accounts.map((account) => account.startsWith(`eip155:${chainId}`))
if (!chainAccounts.includes(true)) {
return false
}
return true
} catch (e) {
console.error("Error checking sessionHasChainSupport: ", e)
return false
}
}
function notifyAllChainsDisconnected() {
sdk.getActiveSessions((allSessions) => {
Object.values(allSessions).forEach((session) => {
if (!d.sessionHasEventSupport(session, "disconnect")) {
return
}
try {
session.namespaces.eip155.chains.forEach((chainId) => {
const chainInt = DAppsHelpers.chainIdFromEip155(chainId)
if (!d.sessionHasChainSupport(session, chainInt)) {
return
}
sdk.emitSessionEvent(session.topic, {
name: "disconnect",
data: {
code: DAppsTypes.ErrorCodes.rpcErrors.disconnected,
}
}, chainId)
})
} catch (e) {
console.error("Error emitting session event: ", e)
}
})
})
}
function notifyChainConnected(chainId) {
sdk.getActiveSessions((sessions) => {
Object.values(sessions).forEach((session) => {
if (!d.sessionHasEventSupport(session, "connect")) {
return
}
if (!d.sessionHasChainSupport(session, chainId)) {
return
}
const hexChain = `0x${chainId.toString(16)}`
sdk.emitSessionEvent(session.topic, {
name: "connect",
data: {
chainId: hexChain,
}
}, `eip155:${chainId}`)
})
})
}
}
}

View File

@ -1,2 +1,3 @@
ChainsSupervisorPlugin 1.0 ChainsSupervisorPlugin.qml
SiweRequestPlugin 1.0 SiweRequestPlugin.qml
SiweLifeCycle 1.0 SiweLifeCycle.qml

View File

@ -0,0 +1,13 @@
pragma Singleton
import QtQuick 2.15
QtObject {
// Maps to https://eips.ethereum.org/EIPS/eip-1193#rpc-errors
readonly property QtObject rpcErrors: QtObject {
readonly property int userRejectedRequest: 4001
readonly property int unauthorized: 4100
readonly property int unsupportedMethod: 4200
readonly property int disconnected: 4900
readonly property int chainDisconnected: 4901
}
}

View File

@ -2,4 +2,5 @@ SessionRequestResolved 1.0 SessionRequestResolved.qml
SessionRequestWithAuth 1.0 SessionRequestWithAuth.qml
SessionRequestsModel 1.0 SessionRequestsModel.qml
singleton SessionRequest 1.0 SessionRequest.qml
singleton Pairing 1.0 Pairing.qml
singleton Pairing 1.0 Pairing.qml
singleton ErrorCodes 1.0 ErrorCodes.qml

View File

@ -2208,6 +2208,7 @@ Item {
controller: WalletStores.RootStore.walletConnectController
}
walletRootStore: WalletStores.RootStore
blockchainNetworksDown: appMain.networkConnectionStore.blockchainNetworksDown
Component.onCompleted: {
Global.walletConnectService = walletConnectService