WalletConnect no internet edge-cases (#20826)

* feat: only initialize wc if internet online

* feat: no internet toast for session establishment

* feat: no internet banner on session requests

* feat: reloading walletconnect on connection change

* fix: re-initialize only when previously failed to

* fix: removed legacy net-info ns

* ref: renamed :network-status to :network/status

* ref: moved network subs to own "category"

* fix: device network fx args

* fix: tests & showing persisted dapps when offline

* fix: addressed review comments

* fix: rebase issues

* fix: linting

* fix: usage of web3-wallet (#20864)

* fix: moved networks to contextx and renaming

* ref: moved building supported namespaces into fx
This commit is contained in:
Lungu Cristian 2024-07-25 11:21:31 +03:00 committed by GitHub
parent 60ad7c8a29
commit cee21241d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 221 additions and 175 deletions

View File

@ -18,7 +18,6 @@
legacy.status-im.multiaccounts.logout.core legacy.status-im.multiaccounts.logout.core
[legacy.status-im.multiaccounts.model :as multiaccounts.model] [legacy.status-im.multiaccounts.model :as multiaccounts.model]
legacy.status-im.multiaccounts.update.core legacy.status-im.multiaccounts.update.core
legacy.status-im.network.net-info
legacy.status-im.pairing.core legacy.status-im.pairing.core
legacy.status-im.profile.core legacy.status-im.profile.core
legacy.status-im.search.core legacy.status-im.search.core

View File

@ -1,56 +0,0 @@
(ns legacy.status-im.network.net-info
(:require
["@react-native-community/netinfo" :default net-info]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(rf/defn change-network-status
[{:keys [db] :as cofx} is-connected?]
(rf/merge cofx
{:db (assoc db :network-status (if is-connected? :online :offline))}))
(rf/defn change-network-type
[{:keys [db] :as cofx} network-type expensive?]
(rf/merge cofx
{:db (assoc db :network/type network-type)
:network/notify-status-go [network-type expensive?]
:dispatch [:mobile-network/on-network-status-change]}))
(rf/defn handle-network-info-change
{:events [::network-info-changed]}
[{:keys [db] :as cofx} {:keys [isConnected type details] :as state}]
(let [old-network-status (:network-status db)
old-network-type (:network/type db)
connectivity-status (if isConnected :online :offline)
status-changed? (= connectivity-status old-network-status)
type-changed? (= type old-network-type)]
(log/debug "[net-info]"
"old-network-status" old-network-status
"old-network-type" old-network-type
"connectivity-status" connectivity-status
"type" type
"details" details)
(rf/merge cofx
(when-not status-changed?
(change-network-status isConnected))
(when-not type-changed?
(change-network-type type (:is-connection-expensive details))))))
(defn add-net-info-listener
[]
(when net-info
(.addEventListener ^js net-info
#(re-frame/dispatch [::network-info-changed
(js->clj % :keywordize-keys true)]))))
(re-frame/reg-fx
:network/listen-to-network-info
(fn []
(add-net-info-listener)))
(re-frame/reg-fx
:network/notify-status-go
(fn [[network-type expensive?]]
(native-module/connection-change network-type expensive?)))

View File

@ -15,8 +15,6 @@
(reg-root-key-sub :visibility-status-updates :visibility-status-updates) (reg-root-key-sub :visibility-status-updates :visibility-status-updates)
(reg-root-key-sub :fleets/custom-fleets :custom-fleets) (reg-root-key-sub :fleets/custom-fleets :custom-fleets)
(reg-root-key-sub :ui/search :ui/search) (reg-root-key-sub :ui/search :ui/search)
(reg-root-key-sub :network/type :network/type)
(reg-root-key-sub :network-status :network-status)
(reg-root-key-sub :peer-stats/count :peer-stats/count) (reg-root-key-sub :peer-stats/count :peer-stats/count)
(reg-root-key-sub :peers-summary :peers-summary) (reg-root-key-sub :peers-summary :peers-summary)
(reg-root-key-sub :web3-node-version :web3-node-version) (reg-root-key-sub :web3-node-version :web3-node-version)

View File

@ -127,7 +127,7 @@
[:app-state [:app-state
:current-chat-id :current-chat-id
:network :network
:network-status :network/status
:peers-summary :peers-summary
:sync-state :sync-state
:view-id :view-id

View File

@ -171,7 +171,7 @@
#js #js
{:getEnforcing {}}) {:getEnforcing {}})
(def net-info #js {}) (def net-info #js {:addEventListener identity})
(def react-native-biometrics #js {:default {}}) (def react-native-biometrics #js {:default {}})
(def react-native-static-safe-area-insets #js {:default {}}) (def react-native-static-safe-area-insets #js {:default {}})

View File

@ -23,7 +23,7 @@
(oops/ocall wc-utils (oops/ocall wc-utils
"buildApprovedNamespaces" "buildApprovedNamespaces"
(bean/->js {:proposal proposal (bean/->js {:proposal proposal
:supportedNamespaces supported-namespaces}))) :supportedNamespaces (clj->js supported-namespaces)})))
;; Get an error from this list: ;; Get an error from this list:
;; https://github.com/WalletConnect/walletconnect-monorepo/blob/c6e9529418a0c81d4efcc6ac4e61f242a50b56c5/packages/utils/src/errors.ts ;; https://github.com/WalletConnect/walletconnect-monorepo/blob/c6e9529418a0c81d4efcc6ac4e61f242a50b56c5/packages/utils/src/errors.ts

View File

@ -0,0 +1,55 @@
(ns status-im.contexts.networks.events
(:require
["@react-native-community/netinfo" :default net-info]
[native-module.core :as native-module]
[status-im.feature-flags :as ff]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(rf/reg-fx
:effects.network/listen-to-network-info
(fn []
(when net-info
(.addEventListener ^js net-info
#(rf/dispatch [:network/on-state-change
(js->clj % :keywordize-keys true)])))))
(rf/reg-event-fx
:network/on-state-change
(fn [{:keys [db]} [{:keys [isConnected type details]}]]
(let [old-network-status (:network/status db)
old-network-type (:network/type db)
connectivity-status (if isConnected :online :offline)
status-changed? (not= connectivity-status old-network-status)
type-changed? (not= type old-network-type)
is-connection-expensive? (:is-connection-expensive details)]
(log/debug "[net-info]"
"old-network-status" old-network-status
"old-network-type" old-network-type
"connectivity-status" connectivity-status
"type" type
"details" details)
{:fx [(when status-changed?
[:dispatch [:network/on-network-status-change isConnected]])
(when type-changed?
[:dispatch [:network/on-network-type-change type is-connection-expensive?]])]})))
(rf/reg-event-fx
:network/on-network-type-change
(fn [{:keys [db]} [network-type expensive?]]
{:db (assoc db :network/type network-type)
:fx [[:effects.network/notify-status-go network-type expensive?]
[:dispatch [:mobile-network/on-network-status-change]]]}))
(rf/reg-event-fx
:network/on-network-status-change
(fn [{:keys [db]} [is-connected?]]
(let [network-status (if is-connected? :online :offline)]
{:db (assoc db :network/status network-status)
:fx [(when (ff/enabled? ::ff/wallet.wallet-connect)
[:dispatch [:wallet-connect/reload-on-network-change is-connected?]])]})))
(rf/reg-fx
:effects.network/notify-status-go
(fn [network-type expensive?]
(native-module/connection-change network-type expensive?)))

View File

@ -50,11 +50,15 @@
(rf/reg-fx (rf/reg-fx
:effects.wallet-connect/approve-session :effects.wallet-connect/approve-session
(fn [{:keys [web3-wallet proposal supported-namespaces on-success on-fail]}] (fn [{:keys [web3-wallet proposal networks accounts on-success on-fail]}]
(let [{:keys [params id]} proposal (let [{:keys [params id]} proposal
approved-namespaces (wallet-connect/build-approved-namespaces approved-namespaces (->> {:eip155
params {:chains networks
supported-namespaces)] :accounts accounts
:methods constants/wallet-connect-supported-methods
:events constants/wallet-connect-supported-events}}
(wallet-connect/build-approved-namespaces
params))]
(-> (wallet-connect/approve-session (-> (wallet-connect/approve-session
{:web3-wallet web3-wallet {:web3-wallet web3-wallet
:id id :id id

View File

@ -13,10 +13,14 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/init :wallet-connect/init
(fn [] (fn [{:keys [db]}]
(let [network-status (:network/status db)]
(if (= network-status :online)
{:fx [[:effects.wallet-connect/init {:fx [[:effects.wallet-connect/init
{:on-success #(rf/dispatch [:wallet-connect/on-init-success %]) {:on-success #(rf/dispatch [:wallet-connect/on-init-success %])
:on-fail #(rf/dispatch [:wallet-connect/on-init-fail %])}]]})) :on-fail #(rf/dispatch [:wallet-connect/on-init-fail %])}]]}
;; NOTE: when offline, fetching persistent sessions only
{:fx [[:dispatch [:wallet-connect/fetch-persisted-sessions]]]}))))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/on-init-success :wallet-connect/on-init-success
@ -25,6 +29,14 @@
:fx [[:dispatch [:wallet-connect/register-event-listeners]] :fx [[:dispatch [:wallet-connect/register-event-listeners]]
[:dispatch [:wallet-connect/fetch-persisted-sessions]]]})) [:dispatch [:wallet-connect/fetch-persisted-sessions]]]}))
(rf/reg-event-fx
:wallet-connect/reload-on-network-change
(fn [{:keys [db]} [is-connected?]]
(let [logged-in? (-> db :profile/profile boolean)
web3-wallet-missing? (-> db :wallet-connect/web3-wallet boolean not)]
(when (and is-connected? logged-in? web3-wallet-missing?)
{:fx [[:dispatch [:wallet-connect/init]]]}))))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/register-event-listeners :wallet-connect/register-event-listeners
(fn [{:keys [db]}] (fn [{:keys [db]}]
@ -77,12 +89,12 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/session-networks-unsupported :wallet-connect/session-networks-unsupported
(fn [_ [proposal]] (fn [{:keys [db]} [proposal]]
(let [{:keys [name]} (wallet-connect-core/get-session-dapp-metadata proposal)] (let [{:keys [name]} (wallet-connect-core/get-session-dapp-metadata proposal)]
{:fx [[:dispatch {:fx [[:dispatch
[:toasts/upsert [:toasts/upsert
{:type :negative {:type :negative
:theme :dark :theme (:theme db)
:text (i18n/label :t/wallet-connect-networks-not-supported {:dapp name})}]]]}))) :text (i18n/label :t/wallet-connect-networks-not-supported {:dapp name})}]]]})))
(rf/reg-event-fx (rf/reg-event-fx
@ -116,7 +128,9 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/disconnect-dapp :wallet-connect/disconnect-dapp
(fn [{:keys [db]} [{:keys [topic on-success on-fail]}]] (fn [{:keys [db]} [{:keys [topic on-success on-fail]}]]
(let [web3-wallet (get db :wallet-connect/web3-wallet)] (let [web3-wallet (get db :wallet-connect/web3-wallet)
network-status (:network/status db)]
(if (= network-status :online)
{:fx [[:effects.wallet-connect/disconnect {:fx [[:effects.wallet-connect/disconnect
{:web3-wallet web3-wallet {:web3-wallet web3-wallet
:topic topic :topic topic
@ -126,7 +140,8 @@
:on-success (fn [] :on-success (fn []
(rf/dispatch [:wallet-connect/disconnect-session topic]) (rf/dispatch [:wallet-connect/disconnect-session topic])
(when on-success (when on-success
(on-success)))}]]}))) (on-success)))}]]}
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/pair :wallet-connect/pair
@ -149,15 +164,13 @@
current-address (get-in db [:wallet-connect/current-proposal :address]) current-address (get-in db [:wallet-connect/current-proposal :address])
accounts (-> (partial wallet-connect-core/format-eip155-address current-address) accounts (-> (partial wallet-connect-core/format-eip155-address current-address)
(map session-networks)) (map session-networks))
supported-namespaces (clj->js {:eip155 network-status (:network/status db)]
{:chains session-networks (if (= network-status :online)
:methods constants/wallet-connect-supported-methods
:events constants/wallet-connect-supported-events
:accounts accounts}})]
{:fx [[:effects.wallet-connect/approve-session {:fx [[:effects.wallet-connect/approve-session
{:web3-wallet web3-wallet {:web3-wallet web3-wallet
:proposal current-proposal :proposal current-proposal
:supported-namespaces supported-namespaces :networks session-networks
:accounts accounts
:on-success (fn [approved-session] :on-success (fn [approved-session]
(log/info "Wallet Connect session approved") (log/info "Wallet Connect session approved")
(rf/dispatch [:wallet-connect/reset-current-session-proposal]) (rf/dispatch [:wallet-connect/reset-current-session-proposal])
@ -168,24 +181,32 @@
:event :wallet-connect/approve-session}) :event :wallet-connect/approve-session})
(rf/dispatch (rf/dispatch
[:wallet-connect/reset-current-session-proposal]))}] [:wallet-connect/reset-current-session-proposal]))}]
[:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]}))) [:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]}
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/on-scan-connection :wallet-connect/on-scan-connection
(fn [_ [scanned-text]] (fn [{:keys [db]} [scanned-text]]
(let [parsed-uri (wallet-connect/parse-uri scanned-text) (let [network-status (:network/status db)
parsed-uri (wallet-connect/parse-uri scanned-text)
version (:version parsed-uri) version (:version parsed-uri)
valid-wc-uri? (wc-utils/valid-wc-uri? parsed-uri) valid-wc-uri? (wc-utils/valid-wc-uri? parsed-uri)
expired? (-> parsed-uri expired? (-> parsed-uri
:expiryTimestamp :expiryTimestamp
wc-utils/timestamp-expired?) wc-utils/timestamp-expired?)
version-supported? (wc-utils/version-supported? version)] version-supported? (wc-utils/version-supported? version)]
(if (or (not valid-wc-uri?) expired? (not version-supported?)) (if (or (not valid-wc-uri?)
(not version-supported?)
(= network-status :offline)
expired?)
{:fx [[:dispatch {:fx [[:dispatch
[:toasts/upsert [:toasts/upsert
{:type :negative {:type :negative
:theme :dark :theme :dark
:text (cond (not valid-wc-uri?) :text (cond (= network-status :offline)
(i18n/label :t/wallet-connect-no-internet-warning)
(not valid-wc-uri?)
(i18n/label :t/wallet-connect-wrong-qr) (i18n/label :t/wallet-connect-wrong-qr)
expired? expired?
@ -236,7 +257,8 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/fetch-persisted-sessions-success :wallet-connect/fetch-persisted-sessions-success
(fn [{:keys [db]} [sessions]] (fn [{:keys [db]} [sessions]]
(let [sessions' (mapv (fn [{:keys [sessionJson] :as session}] (let [network-status (:network/status db)
sessions' (mapv (fn [{:keys [sessionJson] :as session}]
(assoc session (assoc session
:accounts :accounts
(-> sessionJson (-> sessionJson
@ -245,7 +267,8 @@
:eip155 :eip155
:accounts))) :accounts)))
sessions)] sessions)]
{:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]] {:fx [(when (= network-status :online)
[:dispatch [:wallet-connect/fetch-active-sessions]])]
:db (assoc db :wallet-connect/sessions sessions')}))) :db (assoc db :wallet-connect/sessions sessions')})))
(rf/reg-event-fx (rf/reg-event-fx
@ -256,14 +279,14 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/fetch-persisted-sessions :wallet-connect/fetch-persisted-sessions
(fn [_ _] (fn [{:keys [now]} _]
(let [current-timestamp (quot now 1000)]
{:fx [[:json-rpc/call {:fx [[:json-rpc/call
[{:method "wallet_getWalletConnectActiveSessions" [{:method "wallet_getWalletConnectActiveSessions"
;; This is the activeSince timestamp to avoid expired sessions ;; NOTE: This is the activeSince timestamp to avoid expired sessions
;; 0 means, return everything :params [current-timestamp]
:params [0]
:on-success [:wallet-connect/fetch-persisted-sessions-success] :on-success [:wallet-connect/fetch-persisted-sessions-success]
:on-error [:wallet-connect/fetch-persisted-sessions-fail]}]]]})) :on-error [:wallet-connect/fetch-persisted-sessions-fail]}]]]})))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/persist-session :wallet-connect/persist-session
@ -290,3 +313,12 @@
:params [topic] :params [topic]
:on-success #(log/info "Wallet Connect session disconnected") :on-success #(log/info "Wallet Connect session disconnected")
:on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]}))
(rf/reg-event-fx
:wallet-connect/no-internet-toast
(fn [{:keys [db]}]
{:fx [[:dispatch
[:toasts/upsert
{:type :negative
:theme (:theme db)
:text (i18n/label :t/wallet-connect-no-internet-warning)}]]]}))

View File

@ -14,9 +14,17 @@
(rf/dispatch [:wallet-connect/respond-current-session password])) (rf/dispatch [:wallet-connect/respond-current-session password]))
(defn view (defn view
[{:keys [warning-label slide-button-text disabled?]} & children] [{:keys [warning-label slide-button-text error-text]} & children]
(let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details]) (let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details])
offline? (rf/sub [:network/offline?])
theme (quo.theme/use-theme)] theme (quo.theme/use-theme)]
[:<>
(when (or offline? error-text)
[quo/alert-banner
{:action? false
:text (if offline?
(i18n/label :t/wallet-connect-no-internet-warning)
error-text)}])
[rn/view {:style style/content-container} [rn/view {:style style/content-container}
(into [rn/view (into [rn/view
{:style style/data-items-container}] {:style style/data-items-container}]
@ -25,7 +33,7 @@
[standard-authentication/slide-button [standard-authentication/slide-button
{:size :size-48 {:size :size-48
:track-text slide-button-text :track-text slide-button-text
:disabled? disabled? :disabled? (or offline? (seq error-text))
:customization-color customization-color :customization-color customization-color
:on-auth-success on-auth-success :on-auth-success on-auth-success
:auth-button-label (i18n/label :t/confirm)}]] :auth-button-label (i18n/label :t/confirm)}]]
@ -36,4 +44,4 @@
colors/white-opa-70 colors/white-opa-70
colors/neutral-80-opa-70)} colors/neutral-80-opa-70)}
:weight :medium} :weight :medium}
warning-label]]])) warning-label]]]]))

View File

@ -33,19 +33,16 @@
:dapp dapp :dapp dapp
:account account}] :account account}]
[data-block/view]] [data-block/view]]
(when error-state [footer/view
[quo/alert-banner {:warning-label (i18n/label :t/wallet-connect-sign-warning)
{:action? false :slide-button-text (i18n/label :t/slide-to-send)
:text (i18n/label (condp = error-state :error-text (when error-state
(i18n/label (condp = error-state
:not-enough-assets-to-pay-gas-fees :not-enough-assets-to-pay-gas-fees
:t/not-enough-assets-to-pay-gas-fees :t/not-enough-assets-to-pay-gas-fees
:not-enough-assets :not-enough-assets
:t/not-enough-assets))}]) :t/not-enough-assets)))}
[footer/view
{:warning-label (i18n/label :t/wallet-connect-sign-warning)
:slide-button-text (i18n/label :t/slide-to-send)
:disabled? error-state}
[quo/data-item [quo/data-item
{:status :default {:status :default
:card? false :card? false

View File

@ -32,19 +32,16 @@
:dapp dapp :dapp dapp
:account account}] :account account}]
[data-block/view]] [data-block/view]]
(when error-state [footer/view
[quo/alert-banner {:warning-label (i18n/label :t/wallet-connect-sign-warning)
{:action? false :slide-button-text (i18n/label :t/slide-to-sign)
:text (i18n/label (condp = error-state :error-text (when error-state
(i18n/label (condp = error-state
:not-enough-assets-to-pay-gas-fees :not-enough-assets-to-pay-gas-fees
:t/not-enough-assets-to-pay-gas-fees :t/not-enough-assets-to-pay-gas-fees
:not-enough-assets :not-enough-assets
:t/not-enough-assets))}]) :t/not-enough-assets)))}
[footer/view
{:warning-label (i18n/label :t/wallet-connect-sign-warning)
:slide-button-text (i18n/label :t/slide-to-sign)
:disabled? error-state}
[quo/data-item [quo/data-item
{:status :default {:status :default
:card? false :card? false

View File

@ -27,6 +27,7 @@
status-im.contexts.contact.blocking.events status-im.contexts.contact.blocking.events
status-im.contexts.keycard.effects status-im.contexts.keycard.effects
status-im.contexts.keycard.events status-im.contexts.keycard.events
status-im.contexts.networks.events
status-im.contexts.onboarding.common.overlay.events status-im.contexts.onboarding.common.overlay.events
status-im.contexts.onboarding.events status-im.contexts.onboarding.events
status-im.contexts.profile.events status-im.contexts.profile.events
@ -57,7 +58,7 @@
cofx cofx
{:db db/app-db {:db db/app-db
:theme/init-theme nil :theme/init-theme nil
:network/listen-to-network-info nil :effects.network/listen-to-network-info nil
:effects.biometric/get-supported-type nil :effects.biometric/get-supported-type nil
:effects.keycard/register-card-events nil :effects.keycard/register-card-events nil
:effects.keycard/check-nfc-enabled nil :effects.keycard/check-nfc-enabled nil

View File

@ -151,3 +151,9 @@
:<- [:toasts] :<- [:toasts]
(fn [toasts [_ toast-id & cursor]] (fn [toasts [_ toast-id & cursor]]
(get-in toasts (into [:toasts toast-id] cursor)))) (get-in toasts (into [:toasts toast-id] cursor))))
(re-frame/reg-sub
:network/offline?
:<- [:network/status]
(fn [status]
(= status :offline)))

View File

@ -44,6 +44,10 @@
;;push notifications ;;push notifications
(reg-root-key-sub :push-notifications/preferences :push-notifications/preferences) (reg-root-key-sub :push-notifications/preferences :push-notifications/preferences)
;;device
(reg-root-key-sub :network/status :network/status)
(reg-root-key-sub :network/type :network/type)
;;general ;;general
(reg-root-key-sub :messenger/started? :messenger/started?) (reg-root-key-sub :messenger/started? :messenger/started?)
(reg-root-key-sub :animations :animations) (reg-root-key-sub :animations :animations)

View File

@ -2632,6 +2632,7 @@
"wallet-connect-go-back": "Go back to your browser or dapp", "wallet-connect-go-back": "Go back to your browser or dapp",
"wallet-connect-label": "WalletConnect", "wallet-connect-label": "WalletConnect",
"wallet-connect-networks-not-supported": "{{dapp}} requires an unsupported network.", "wallet-connect-networks-not-supported": "{{dapp}} requires an unsupported network.",
"wallet-connect-no-internet-warning": "Oops, you have no internet. Try again later!",
"wallet-connect-proposal-description": "By connecting you allow {{name}} to retrieve your account address and enable Web3", "wallet-connect-proposal-description": "By connecting you allow {{name}} to retrieve your account address and enable Web3",
"wallet-connect-proposal-title": "Would like to connect with your wallet", "wallet-connect-proposal-title": "Would like to connect with your wallet",
"wallet-connect-qr-expired": "WalletConnect QR has expired", "wallet-connect-qr-expired": "WalletConnect QR has expired",