From d748ccdef95252b6823548e584a87230422e9069 Mon Sep 17 00:00:00 2001 From: Lungu Cristian Date: Wed, 2 Oct 2024 18:16:20 +0300 Subject: [PATCH] Fix WalletConnect sessions disappearing (#21350) * feat: moved disconnect logic to function * ref: moved rpc calls to rpc ns * ref: moved session approval logic to function * fix: small fixes for sessions * test: wallet-connect/on-session-delete event test * test: added event tests for wc sessions * fix: require sessions events ns * fix: the wallet was loaded after wc sometimes --- .../contexts/profile/login/events.cljs | 2 - src/status_im/contexts/wallet/events.cljs | 4 +- .../wallet/wallet_connect/events/core.cljs | 6 +- .../wallet/wallet_connect/events/effects.cljs | 40 ++--- .../events/session_proposals.cljs | 57 +++++--- .../events/session_responses.cljs | 13 -- .../wallet_connect/events/sessions.cljs | 138 ++++++------------ .../wallet_connect/events/sessions_test.cljs | 122 ++++++++++++++++ .../wallet/wallet_connect/utils/rpc.cljs | 17 +++ .../wallet/wallet_connect/utils/sessions.cljs | 105 +++++++++++++ translations/en.json | 1 + 11 files changed, 347 insertions(+), 158 deletions(-) create mode 100644 src/status_im/contexts/wallet/wallet_connect/events/sessions_test.cljs diff --git a/src/status_im/contexts/profile/login/events.cljs b/src/status_im/contexts/profile/login/events.cljs index 52e4979cde..eac82330a2 100644 --- a/src/status_im/contexts/profile/login/events.cljs +++ b/src/status_im/contexts/profile/login/events.cljs @@ -102,8 +102,6 @@ [:dispatch-later [{:ms 1500 :dispatch [:profile.login/non-critical-initialization]}]] [:dispatch [:network/check-expensive-connection]] [:profile.settings/get-profile-picture key-uid] - (when (ff/enabled? ::ff/wallet.wallet-connect) - [:dispatch [:wallet-connect/init]]) (when notifications-enabled? [:effects/push-notifications-enable])]}))) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 38ff712ce4..70963aba47 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -478,7 +478,9 @@ [:dispatch [:wallet/get-ethereum-chains]] [:dispatch [:wallet/get-accounts]] [:dispatch [:wallet/get-keypairs]] - [:dispatch [:wallet/get-saved-addresses]]]})) + [:dispatch [:wallet/get-saved-addresses]] + (when (ff/enabled? ::ff/wallet.wallet-connect) + [:dispatch-later [{:ms 500 :dispatch [:wallet-connect/init]}]])]})) (rf/reg-event-fx :wallet/share-account (fn [_ [{:keys [content title]}]] diff --git a/src/status_im/contexts/wallet/wallet_connect/events/core.cljs b/src/status_im/contexts/wallet/wallet_connect/events/core.cljs index 5a3a951250..de501c8ff1 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events/core.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/core.cljs @@ -21,8 +21,8 @@ {:fx [[:effects.wallet-connect/init {:on-success #(rf/dispatch [:wallet-connect/on-init-success %]) :on-fail #(rf/dispatch [:wallet-connect/on-init-fail %])}]]}) - ;; NOTE: when offline, fetching persistent sessions only - {:fx [[:dispatch [:wallet-connect/fetch-persisted-sessions]]]})))) + ;; NOTE: when offline, fetching persistent sessions without initializing WC + {:fx [[:dispatch [:wallet-connect/get-sessions]]]})))) (rf/reg-event-fx :wallet-connect/on-init-success @@ -30,7 +30,7 @@ (log/info "WalletConnect SDK initialisation successful") {:db (assoc db :wallet-connect/web3-wallet web3-wallet) :fx [[:dispatch [:wallet-connect/register-event-listeners]] - [:dispatch [:wallet-connect/fetch-persisted-sessions]]]})) + [:dispatch [:wallet-connect/get-sessions]]]})) (rf/reg-event-fx :wallet-connect/reload-on-network-change diff --git a/src/status_im/contexts/wallet/wallet_connect/events/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/events/effects.cljs index a4ae4639aa..1e907d6ee8 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/effects.cljs @@ -5,6 +5,7 @@ [react-native.wallet-connect :as wallet-connect] [status-im.config :as config] [status-im.constants :as constants] + [status-im.contexts.wallet.wallet-connect.utils.sessions :as sessions] [status-im.contexts.wallet.wallet-connect.utils.signing :as signing] [status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions] [status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data] @@ -42,35 +43,19 @@ (rf/reg-fx :effects.wallet-connect/disconnect - (fn [{:keys [web3-wallet topic reason on-success on-fail]}] - (-> (wallet-connect/disconnect-session {:web3-wallet web3-wallet - :topic topic - :reason reason}) + (fn [{:keys [web3-wallet topic on-success on-fail]}] + (-> (sessions/disconnect web3-wallet topic) (promesa/then on-success) (promesa/catch on-fail)))) (rf/reg-fx :effects.wallet-connect/approve-session - (fn [{:keys [web3-wallet proposal networks accounts on-success on-fail]}] - (let [{:keys [params id]} proposal - approved-namespaces (->> {:eip155 - {:chains networks - :accounts accounts - :methods constants/wallet-connect-supported-methods - :events constants/wallet-connect-supported-events}} - (wallet-connect/build-approved-namespaces - params))] - (-> (wallet-connect/approve-session - {:web3-wallet web3-wallet - :id id - :approved-namespaces approved-namespaces}) - (promesa/then on-success) - (promesa/catch on-fail))))) - -(rf/reg-fx - :effects.wallet-connect/fetch-active-sessions - (fn [{:keys [web3-wallet on-success on-fail]}] - (-> (wallet-connect/get-active-sessions web3-wallet) + (fn [{:keys [web3-wallet proposal-request session-networks address on-success on-fail]}] + (-> (sessions/approve + {:web3-wallet web3-wallet + :proposal-request proposal-request + :address address + :session-networks session-networks}) (promesa/then on-success) (promesa/catch on-fail)))) @@ -156,3 +141,10 @@ :reason reason}) (promesa/then on-success) (promesa/catch on-error))))) + +(rf/reg-fx + :effects.wallet-connect/get-sessions + (fn [{:keys [web3-wallet addresses online? on-success on-error]}] + (-> (sessions/get-sessions web3-wallet addresses online?) + (promesa/then on-success) + (promesa/catch on-error)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events/session_proposals.cljs b/src/status_im/contexts/wallet/wallet_connect/events/session_proposals.cljs index f883d4aba1..628d093c79 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events/session_proposals.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_proposals.cljs @@ -109,7 +109,6 @@ (fn [{:keys [db]} [address]] {:db (assoc-in db [:wallet-connect/current-proposal :address] address)})) - (rf/reg-event-fx :wallet-connect/approve-session (fn [{:keys [db]}] @@ -119,33 +118,51 @@ (map networks/chain-id->eip155) vec) current-address (get-in db [:wallet-connect/current-proposal :address]) - accounts (-> (partial networks/format-eip155-address current-address) - (map session-networks)) network-status (:network/status db) - expiry (get-in current-proposal [:params :expiryTimestamp])] + expired? (-> current-proposal + (get-in [:params :expiryTimestamp]) + uri/timestamp-expired?)] (if (= network-status :online) {:db (assoc-in db [:wallet-connect/current-proposal :response-sent?] true) - :fx [(if (uri/timestamp-expired? expiry) + :fx [(if expired? [:dispatch [:toasts/upsert {:id :wallet-connect-proposal-expired :type :negative :text (i18n/label :t/wallet-connect-proposal-expired)}]] [:effects.wallet-connect/approve-session - {:web3-wallet web3-wallet - :proposal current-proposal - :networks session-networks - :accounts accounts - :on-success (fn [approved-session] - (log/info "Wallet Connect session approved") - (rf/dispatch [:wallet-connect/reset-current-session-proposal]) - (rf/dispatch [:wallet-connect/persist-session - approved-session])) - :on-fail (fn [error] - (log/error "Wallet Connect session approval failed" - {:error error - :event :wallet-connect/approve-session}) - (rf/dispatch - [:wallet-connect/reset-current-session-proposal]))}]) + {:web3-wallet web3-wallet + :proposal-request current-proposal + :session-networks session-networks + :address current-address + :on-success #(rf/dispatch [:wallet-connect/approve-session-success %]) + :on-fail #(rf/dispatch [:wallet-connect/approve-session-error %])}]) [:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]} {:fx [[:dispatch [:wallet-connect/no-internet-toast]]]})))) + +(rf/reg-event-fx :wallet-connect/approve-session-success + (fn [_ [session]] + (log/info "Wallet Connect session approved") + {:fx [[:dispatch [:wallet-connect/on-new-session session]] + [:dispatch [:wallet-connect/reset-current-session-proposal]] + [:dispatch [:wallet-connect/redirect-to-dapp (data-store/get-dapp-redirect-url session)]]]})) + +(rf/reg-event-fx :wallet-connect/approve-session-error + (fn [_ [error]] + (log/error "Wallet Connect session approval failed" + {:error error + :event :wallet-connect/approve-session}) + {:fx [[:dispatch [:wallet-connect/reset-current-session-proposal]]]})) + +(rf/reg-event-fx + :wallet-connect/reject-session-proposal + (fn [{:keys [db]} [proposal]] + (let [web3-wallet (get db :wallet-connect/web3-wallet) + {:keys [request response-sent?]} (:wallet-connect/current-proposal db)] + {:fx [(when-not response-sent? + [:effects.wallet-connect/reject-session-proposal + {:web3-wallet web3-wallet + :proposal (or proposal request) + :on-success #(log/info "Wallet Connect session proposal rejected") + :on-error #(log/error "Wallet Connect unable to reject session proposal")}]) + [:dispatch [:wallet-connect/reset-current-session-proposal]]]}))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs b/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs index 31ad394a4b..0179a5221c 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs @@ -165,19 +165,6 @@ {:fx [[:dispatch [:wallet-connect/send-response {:result result}]] [:dispatch [:wallet-connect/dismiss-request-modal]]]})) -(rf/reg-event-fx - :wallet-connect/reject-session-proposal - (fn [{:keys [db]} [proposal]] - (let [web3-wallet (get db :wallet-connect/web3-wallet) - {:keys [request response-sent?]} (:wallet-connect/current-proposal db)] - {:fx [(when-not response-sent? - [:effects.wallet-connect/reject-session-proposal - {:web3-wallet web3-wallet - :proposal (or proposal request) - :on-success #(log/info "Wallet Connect session proposal rejected") - :on-error #(log/error "Wallet Connect unable to reject session proposal")}]) - [:dispatch [:wallet-connect/reset-current-session-proposal]]]}))) - ;; NOTE: Currently we only reject a session if the user dismissed a modal ;; without accepting the session first. ;; But this needs to be solidified to ensure other cases: diff --git a/src/status_im/contexts/wallet/wallet_connect/events/sessions.cljs b/src/status_im/contexts/wallet/wallet_connect/events/sessions.cljs index 515271e40f..5aedbb120b 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events/sessions.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/sessions.cljs @@ -1,20 +1,20 @@ (ns status-im.contexts.wallet.wallet-connect.events.sessions (:require [re-frame.core :as rf] - [react-native.wallet-connect :as wallet-connect] - [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.utils.data-store :as - data-store] [status-im.contexts.wallet.wallet-connect.utils.networks :as networks] [status-im.contexts.wallet.wallet-connect.utils.sessions :as sessions] [taoensso.timbre :as log] - [utils.transforms :as types])) + [utils.i18n :as i18n])) (rf/reg-event-fx :wallet-connect/on-session-delete (fn [{:keys [db]} [{:keys [topic] :as event}]] (when (networks/event-should-be-handled? db event) (log/info "Received Wallet Connect session delete from the SDK: " event) - {:fx [[:dispatch [:wallet-connect/disconnect-persisted-session topic]]]}))) + {:fx [[:json-rpc/call + [{:method "wallet_disconnectWalletConnectSession" + :params [topic] + :on-success [:wallet-connect/delete-session topic] + :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]}))) (rf/reg-event-fx :wallet-connect/disconnect-dapp @@ -26,116 +26,64 @@ {:fx [[:effects.wallet-connect/disconnect {:web3-wallet web3-wallet :topic topic - :reason (wallet-connect/get-sdk-error - constants/wallet-connect-user-disconnected-reason-key) :on-fail on-fail :on-success (fn [] - (rf/dispatch [:wallet-connect/disconnect-persisted-session topic]) + (log/info "Successfully disconnected dApp session" topic) + (rf/dispatch [:wallet-connect/delete-session topic]) (when on-success (on-success)))}]]} {:fx [[:dispatch [:wallet-connect/no-internet-toast]]]})))) -;; We first load sessions from database, then we initiate a call to Wallet Connect SDK and -;; then replace the list we have stored in the database with the one that came from the SDK. -;; In addition to that, we also update the backend state by marking sessions that are not -;; active anymore by calling `:wallet-connect/disconnect-session`. (rf/reg-event-fx - :wallet-connect/fetch-active-sessions-success - (fn [{:keys [db now]} [sessions]] - (let [persisted-sessions (:wallet-connect/sessions db) - account-addresses (->> (get-in db [:wallet :accounts]) - vals - sessions/filter-operable-accounts - (map :address)) - sessions (->> (js->clj sessions :keywordize-keys true) - vals - (map sessions/sdk-session->db-session) - (sessions/filter-sessions-for-account-addresses - account-addresses)) - session-topics (set (map :topic sessions)) - expired-sessions (filter - (fn [{:keys [expiry topic]}] - (or (< expiry (/ now 1000)) - (not (contains? session-topics topic)))) - persisted-sessions)] - (when (seq expired-sessions) - (log/info "Updating WalletConnect persisted sessions due to expired/inactive sessions" - {:expired expired-sessions})) - {:fx (mapv (fn [{:keys [topic]}] - [:dispatch [:wallet-connect/disconnect-persisted-session topic]]) - expired-sessions) - :db (assoc db :wallet-connect/sessions sessions)}))) - -(rf/reg-event-fx - :wallet-connect/fetch-active-sessions + :wallet-connect/get-sessions (fn [{:keys [db]}] - (let [web3-wallet (get db :wallet-connect/web3-wallet)] - {:fx [[:effects.wallet-connect/fetch-active-sessions - {:web3-wallet web3-wallet - :on-fail #(log/error "Failed to get active sessions" {:error %}) - :on-success #(rf/dispatch [:wallet-connect/fetch-active-sessions-success %])}]]}))) + (let [addresses (->> (get-in db [:wallet :accounts]) + vals + sessions/filter-operable-accounts + (map :address))] + (if (not (seq addresses)) + ;; NOTE: Re-trying to get active sessions if accounts weren't loaded yet during + ;; initialization + ((log/info "Re-trying to fetch active WalletConnect sessions") + {:fx [[:dispatch-later [{:ms 500 :dispatch [:wallet-connect/get-sessions]}]]]}) + {:fx [[:effects.wallet-connect/get-sessions + {:online? (-> db :network/status (= :online)) + :web3-wallet (get db :wallet-connect/web3-wallet) + :addresses addresses + :on-success #(rf/dispatch [:wallet-connect/get-sessions-success %]) + :on-error #(rf/dispatch [:wallet-connect/get-sessions-error %])}]]})))) (rf/reg-event-fx - :wallet-connect/fetch-persisted-sessions-success + :wallet-connect/get-sessions-success (fn [{:keys [db]} [sessions]] - (let [network-status (:network/status db) - sessions' (mapv (fn [{:keys [sessionJson] :as session}] - (assoc session - :accounts - (-> sessionJson - types/json->clj - :namespaces - :eip155 - :accounts))) - sessions)] - {:fx [(when (= network-status :online) - [:dispatch [:wallet-connect/fetch-active-sessions]])] - :db (assoc db :wallet-connect/sessions sessions')}))) + (log/info "WalletConnect sessions loaded successfully") + {:db (assoc db :wallet-connect/sessions sessions)})) (rf/reg-event-fx - :wallet-connect/fetch-persisted-sessions-fail + :wallet-connect/get-sessions-error (fn [_ [error]] - (log/info "Wallet Connect fetch persisted sessions failed" error) - {:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]]})) + (log/error "WalletConnect sessions failed to load" error) + {:fx [[:dispatch + [:toasts/upsert + {:type :negative + :text (i18n/label :t/wallet-connect-connections-error)}]]]})) (rf/reg-event-fx - :wallet-connect/fetch-persisted-sessions - (fn [{:keys [now]} _] - (let [current-timestamp (quot now 1000)] - {:fx [[:json-rpc/call - [{:method "wallet_getWalletConnectActiveSessions" - ;; NOTE: This is the activeSince timestamp to avoid expired sessions - :params [current-timestamp] - :on-success [:wallet-connect/fetch-persisted-sessions-success] - :on-error [:wallet-connect/fetch-persisted-sessions-fail]}]]]}))) + :wallet-connect/on-new-session + (fn [{:keys [db]} [new-session]] + {:db (update db + :wallet-connect/sessions + (fn [sessions] + (->> new-session + sessions/sdk-session->db-session + (conj sessions))))})) (rf/reg-event-fx - :wallet-connect/persist-session - (fn [_ [session-info]] - (let [redirect-url (-> session-info - (js->clj :keywordize-keys true) - (data-store/get-dapp-redirect-url))] - {:fx [[:json-rpc/call - [{:method "wallet_addWalletConnectSession" - :params [(js/JSON.stringify session-info)] - :on-success (fn [] - (log/info "Wallet Connect session persisted") - (rf/dispatch [:wallet-connect/fetch-persisted-sessions]) - (rf/dispatch [:wallet-connect/redirect-to-dapp redirect-url])) - :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]}))) - -(rf/reg-event-fx - :wallet-connect/disconnect-persisted-session + :wallet-connect/delete-session (fn [{:keys [db]} [topic]] - (log/info "Removing session from persistance and state" topic) {:db (update db :wallet-connect/sessions (fn [sessions] (->> sessions (remove #(= (:topic %) topic)) - (into [])))) - :fx [[:json-rpc/call - [{:method "wallet_disconnectWalletConnectSession" - :params [topic] - :on-success #(log/info "Wallet Connect session disconnected") - :on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) + (into []))))})) diff --git a/src/status_im/contexts/wallet/wallet_connect/events/sessions_test.cljs b/src/status_im/contexts/wallet/wallet_connect/events/sessions_test.cljs new file mode 100644 index 0000000000..05f1c964c0 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/events/sessions_test.cljs @@ -0,0 +1,122 @@ +(ns status-im.contexts.wallet.wallet-connect.events.sessions-test + (:require + [cljs.test :refer-macros [is are testing]] + matcher-combinators.test + [re-frame.db :as rf-db] + status-im.contexts.wallet.wallet-connect.events.session-responses + status-im.contexts.wallet.wallet-connect.events.sessions + [test-helpers.unit :as h])) + +(defn- find-fx + [fx name] + (some #(when (= name (first %)) %) fx)) + +(defn- get-fx-arg + "Finds the arg value for an effect, if present in the fx vector" + [fx fx-name arg-fn] + (-> fx + (find-fx fx-name) + second + arg-fn)) + +(h/deftest-event :wallet-connect/on-session-delete + [event-id dispatch] + (testing "successfully deletes the session" + (reset! rf-db/app-db {:profile/profile {:test-networks-enabled? false} + :wallet-connect/sessions [{:topic "topic" + :chains ["eip155:1"]}]}) + (let [fx (:fx (dispatch [event-id + {:topic "topic" + :chains ["eip155:1"]}])) + get-rpc-fx-arg (partial get-fx-arg fx :json-rpc/call)] + (are [expected result] (match? expected result) + :json-rpc/call (-> fx (find-fx :json-rpc/call) first) + "wallet_disconnectWalletConnectSession" (get-rpc-fx-arg (comp :method first)) + ["topic"] (get-rpc-fx-arg (comp :params first)) + [:wallet-connect/delete-session "topic"] (get-rpc-fx-arg (comp :on-success first))))) + + (testing "ignore the deletion if session topic not found" + (let [topic-1 "topic-1" + topic-2 "topic-2"] + (reset! rf-db/app-db {:profile/profile {:test-networks-enabled? false} + :wallet-connect/sessions [{:topic topic-1 + :chains ["eip155:1"]}]}) + (is (match? nil + (dispatch [event-id + {:topic topic-2 + :chains ["eip155:1"]}]))))) + + (testing "ignore the deletion if the event is for the wrong network mode (testnet/mainnet)" + (let [topic "topic"] + (reset! rf-db/app-db {:profile/profile {:test-networks-enabled? true} + :wallet-connect/sessions [{:topic topic + :chains ["eip155:1"]}]}) + (is (match? nil + (dispatch [event-id + {:topic topic + :chains ["eip155:11155111"]}])))))) + +(h/deftest-event :wallet-connect/disconnect-dapp + [event-id dispatch] + (testing "disconnecting from dApp when online" + (reset! rf-db/app-db {:network/status :online + :wallet-connect/web3-wallet "mock"}) + (let [fx (:fx (dispatch [event-id {:topic "topic"}]))] + (are [expected result] (match? expected result) + :effects.wallet-connect/disconnect (ffirst fx) + "topic" (-> fx first second :topic) + "mock" (-> fx first second :web3-wallet)))) + + (testing "showing no-internet toast when offline" + (reset! rf-db/app-db {:network/status :offline}) + (is (match? :wallet-connect/no-internet-toast + (-> (dispatch [event-id {:topic "topic"}]) + :fx + first + second + first))))) + +(h/deftest-event :wallet-connect/get-sessions + [event-id dispatch] + (testing "the fx includes only for the available accounts" + (reset! rf-db/app-db {:wallet {:accounts {"available" {:address "0x123" + :operable? true} + "watch-only" {:address "0x456" + :operable? true + :watch-only? true} + "non-operable" {:address "0x789" + :operable? false}}} + :network/status :online + :wallet-connect/web3-wallet "mock"}) + (let [fx (:fx (dispatch [event-id])) + get-sessions-fx-arg (partial get-fx-arg fx :effects.wallet-connect/get-sessions)] + (are [expected result] (match? expected result) + :effects.wallet-connect/get-sessions (-> fx (find-fx :effects.wallet-connect/get-sessions) first) + true (get-sessions-fx-arg :online?) + '("0x123") (get-sessions-fx-arg :addresses) + "mock" (get-sessions-fx-arg :web3-wallet))))) + +(h/deftest-event :wallet-connect/get-sessions-success + [event-id dispatch] + (testing "sessions are stored in the db" + (let [sessions '({:topic "123"} {:topic "456"})] + (is (match? {:db {:wallet-connect/sessions sessions}} + (dispatch [event-id sessions])))))) + +(h/deftest-event :wallet-connect/on-new-session + [event-id dispatch] + (testing "new session is added to db" + (let [sessions '({:topic "123"} {:topic "456"}) + new-session {:topic "789"}] + (reset! rf-db/app-db {:wallet-connect/sessions sessions}) + (is (match? {:db {:wallet-connect/sessions (conj sessions new-session)}} + (dispatch [event-id new-session])))))) + +(h/deftest-event :wallet-connect/delete-session + [event-id dispatch] + (testing "session is deleted from db" + (let [sessions '({:topic "123"} {:topic "456"}) + expected '({:topic "123"})] + (reset! rf-db/app-db {:wallet-connect/sessions sessions}) + (is (match? {:db {:wallet-connect/sessions expected}} + (dispatch [event-id "456"])))))) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs index bdc2474d79..29ec14eda9 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs @@ -58,6 +58,23 @@ (-> (rpc-events/call-async "wallet_getSuggestedFees" true chain-id) (promesa/then transforms/js->clj))) +(defn wallet-disconnect-persisted-session + [topic] + (rpc-events/call-async "wallet_disconnectWalletConnectSession" true topic)) + +(defn wallet-get-persisted-sessions + ([] + (let [now (-> (js/Date.) .getTime (quot 1000))] + (wallet-get-persisted-sessions now))) + ([expiry-timestamp] + (rpc-events/call-async "wallet_getWalletConnectActiveSessions" false expiry-timestamp))) + +(defn wallet-persist-session + [session] + (->> session + transforms/clj->json + (rpc-events/call-async "wallet_addWalletConnectSession" false))) + (defn wallet-get-transaction-estimated-time [chain-id max-fee-per-gas] (-> (rpc-events/call-async "wallet_getTransactionEstimatedTime" true chain-id max-fee-per-gas) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/sessions.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/sessions.cljs index b7be1e7bfe..95f74afa62 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils/sessions.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/sessions.cljs @@ -1,6 +1,12 @@ (ns status-im.contexts.wallet.wallet-connect.utils.sessions (:require [clojure.string :as string] + [promesa.core :as promesa] + [react-native.wallet-connect :as wallet-connect] + [status-im.constants :as constants] + [status-im.contexts.wallet.wallet-connect.utils.networks :as networks] + [status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc] + [taoensso.timbre :as log] [utils.transforms :as transforms])) (defn sdk-session->db-session @@ -42,3 +48,102 @@ first (string/split #":") last))) + +(defn- parse-session-accounts + [{:keys [sessionJson] :as session}] + (assoc session + :accounts + (-> sessionJson + :namespaces + :eip155 + :accounts))) + +(defn- find-inactive-sessions + [active-sessions persisted-sessions] + (->> persisted-sessions + (filter #(->> % + :topic + (contains? (->> active-sessions + (map :topic) + set)) + not)))) + +(defn get-persisted-sessions + [] + (-> (rpc/wallet-get-persisted-sessions) + (promesa/then #(map parse-session-accounts %)) + (promesa/catch (fn [err] + (throw (ex-info "Failed to get persisted WalletConnect sessions" + {:error err + :code :error/wc-get-persisted-sessions})))))) + +(defn get-active-sessions + [web3-wallet addresses] + (-> (wallet-connect/get-active-sessions web3-wallet) + (promesa/then #(->> + (transforms/js->clj %) + vals + (map sdk-session->db-session) + (filter-sessions-for-account-addresses addresses))) + (promesa/catch (fn [err] + (throw (ex-info "Failed to get active WalletConnect sessions" + {:error err + :code :error/wc-get-active-sessions})))))) + +(defn sync-persisted-sessions + [active-sessions persisted-sessions] + (-> (promesa/all + (for [topic (find-inactive-sessions active-sessions + persisted-sessions)] + (do (log/info "Syncing disconnected session with persistance" topic) + (rpc/wallet-disconnect-persisted-session topic)))) + (promesa/catch (fn [err] + (throw (ex-info "Failed to synchronize persisted sessions" + {:error err + :code :error/wc-sync-persisted-sessions})))))) + +(defn get-sessions + [web3-wallet addresses online?] + (promesa/let [persisted-sessions (get-persisted-sessions)] + (if online? + (promesa/let [active-sessions (get-active-sessions web3-wallet addresses)] + (sync-persisted-sessions active-sessions persisted-sessions) + active-sessions) + persisted-sessions))) + +(defn disconnect + [web3-wallet topic] + (let [reason (wallet-connect/get-sdk-error constants/wallet-connect-user-disconnected-reason-key)] + (-> + (promesa/do + (wallet-connect/disconnect-session {:web3-wallet web3-wallet + :topic topic + :reason reason}) + (rpc/wallet-disconnect-persisted-session topic)) + (promesa/catch (fn [err] + (throw (ex-info "Failed to disconnect dapp" + {:err err + :code :error/wc-disconnect-dapp}))))))) + +(defn approve + [{:keys [web3-wallet address session-networks proposal-request]}] + (let [{:keys [params id]} proposal-request + accounts (-> (partial networks/format-eip155-address address) + (map session-networks))] + (-> (promesa/let [session + (wallet-connect/approve-session + {:web3-wallet web3-wallet + :id id + :approved-namespaces (->> + {:eip155 + {:chains session-networks + :accounts accounts + :methods constants/wallet-connect-supported-methods + :events constants/wallet-connect-supported-events}} + (wallet-connect/build-approved-namespaces params))})] + (rpc/wallet-persist-session session) + (transforms/js->clj session)) + (promesa/catch (fn [err] + (throw (ex-info "Failed to approve session" + {:err err + :code :error/wc-approve}))))))) diff --git a/translations/en.json b/translations/en.json index 2ad6b23fd1..8a83b53ab5 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2698,6 +2698,7 @@ "wallet-connect": "Wallet Connect", "wallet-connect-2.0": "Wallet Connect 2.0", "wallet-connect-app-connected": "is connected", + "wallet-connect-connections-error": "Failed to get dApps connections", "wallet-connect-go-back": "Go back to your browser or dapp", "wallet-connect-label": "WalletConnect", "wallet-connect-networks-not-supported": "{{dapp}} requires an unsupported network.",