diff --git a/src/status_im/contexts/shell/qr_reader/view.cljs b/src/status_im/contexts/shell/qr_reader/view.cljs index 63ef09dd20..e010ae4ade 100644 --- a/src/status_im/contexts/shell/qr_reader/view.cljs +++ b/src/status_im/contexts/shell/qr_reader/view.cljs @@ -6,7 +6,7 @@ [status-im.common.scan-qr-code.view :as scan-qr-code] [status-im.common.validation.general :as validators] [status-im.contexts.communities.events] - [status-im.contexts.wallet.wallet-connect.utils :as wc-utils] + [status-im.contexts.wallet.wallet-connect.utils.uri :as wc-uri] [status-im.feature-flags :as ff] [utils.address :as utils-address] [utils.debounce :as debounce] @@ -100,7 +100,7 @@ nil (and - (wc-utils/valid-uri? scanned-text) + (wc-uri/valid-uri? scanned-text) (ff/enabled? ::ff/wallet.wallet-connect)) (handle-wallet-connect scanned-text) diff --git a/src/status_im/contexts/wallet/connected_dapps/view.cljs b/src/status_im/contexts/wallet/connected_dapps/view.cljs index 307b9df419..aed27909d8 100644 --- a/src/status_im/contexts/wallet/connected_dapps/view.cljs +++ b/src/status_im/contexts/wallet/connected_dapps/view.cljs @@ -9,7 +9,7 @@ [status-im.common.resources :as resources] [status-im.contexts.wallet.connected-dapps.disconnect-dapp.view :as disconnect-dapp] [status-im.contexts.wallet.connected-dapps.style :as style] - [status-im.contexts.wallet.wallet-connect.core :as core] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store] [utils.i18n :as i18n] [utils.re-frame :as rf] [utils.string])) @@ -107,9 +107,14 @@ :content-container-style (style/dapps-list theme) :render-fn (fn [{:keys [topic pairingTopic name url iconUrl]}] [quo/dapp - {:dapp {:avatar (core/compute-dapp-icon-path iconUrl - url) - :name (core/compute-dapp-name name url) + {:dapp {:avatar + (data-store/compute-dapp-icon-path + iconUrl + url) + :name + (data-store/compute-dapp-name + name + url) :value url :topic topic :pairing-topic pairingTopic diff --git a/src/status_im/contexts/wallet/wallet_connect/core.cljs b/src/status_im/contexts/wallet/wallet_connect/core.cljs deleted file mode 100644 index c38811ed37..0000000000 --- a/src/status_im/contexts/wallet/wallet_connect/core.cljs +++ /dev/null @@ -1,204 +0,0 @@ -(ns status-im.contexts.wallet.wallet-connect.core - (:require [clojure.edn :as edn] - [clojure.set :as set] - [clojure.string :as string] - [native-module.core :as native-module] - [status-im.constants :as constants] - [status-im.contexts.wallet.common.utils.networks :as networks] - [utils.security.core :as security] - [utils.string] - [utils.transforms :as transforms])) - -(def method-to-screen - {constants/wallet-connect-personal-sign-method :screen/wallet-connect.sign-message - constants/wallet-connect-eth-sign-typed-method :screen/wallet-connect.sign-message - constants/wallet-connect-eth-sign-method :screen/wallet-connect.sign-message - constants/wallet-connect-eth-sign-typed-v4-method :screen/wallet-connect.sign-message - constants/wallet-connect-eth-send-transaction-method :screen/wallet-connect.send-transaction - constants/wallet-connect-eth-sign-transaction-method :screen/wallet-connect.sign-transaction}) - -(defn extract-native-call-signature - [data] - (-> data transforms/json->clj :result)) - -(defn chain-id->eip155 - [chain-id] - (str "eip155:" chain-id)) - -(defn eip155->chain-id - [chain-id-str] - (-> chain-id-str - (string/split #":") - last - edn/read-string)) - -(defn format-eip155-address - [address chain-id] - (str chain-id ":" address)) - -(defn get-request-method - [event] - (get-in event [:params :request :method])) - -(defn get-request-params - [event] - (get-in event [:params :request :params])) - -(defn get-db-current-request-event - [db] - (get-in db [:wallet-connect/current-request :event])) - -(defn get-session-dapp-metadata - [proposal] - (let [metadata (get-in proposal [:params :proposer :metadata]) - origin (get-in proposal [:verifyContext :verified :origin])] - (or metadata {:url origin}))) - -(defn get-current-request-dapp - [request sessions] - (let [dapp-url (get-in request [:event :verifyContext :verified :origin])] - (->> sessions - (filter (fn [session] - (= (utils.string/remove-trailing-slash dapp-url) - (utils.string/remove-trailing-slash (get session :url))))) - first))) - -(defn get-dapp-redirect-url - [session] - (get-in session [:peer :metadata :redirect :native])) - -(defn get-db-current-request-params - [db] - (-> db - get-db-current-request-event - get-request-params)) - -(def ^:private sign-typed-data-by-version - {:v1 native-module/sign-typed-data - :v4 native-module/sign-typed-data-v4}) - -(defn sign-typed-data - [version data address password] - (let [f (get sign-typed-data-by-version version)] - (->> password - security/safe-unmask-data - (f data address)))) - -(defn get-proposal-networks - [proposal] - (let [required-namespaces (get-in proposal [:params :requiredNamespaces]) - optional-namespaces (get-in proposal [:params :optionalNamespaces])] - (->> [required-namespaces optional-namespaces] - (map #(get-in % [:eip155 :chains])) - (apply concat) - (into #{})))) - -(defn proposal-networks-intersection - [proposal supported-networks] - (let [proposed-networks (get-proposal-networks proposal)] - (->> supported-networks - (filter #(->> % - chain-id->eip155 - (contains? proposed-networks)))))) - -(defn required-networks-supported? - [proposal supported-networks] - (let [supported-namespaces #{:eip155} - required-namespaces (get-in proposal [:params :requiredNamespaces])] - (when (every? #(contains? supported-namespaces %) - (keys required-namespaces)) - (let [required-networks (get-in required-namespaces [:eip155 :chains]) - supported-eip155 (set (map chain-id->eip155 supported-networks))] - (every? #(contains? supported-eip155 %) - required-networks))))) - -(defn get-networks-by-mode - [db] - (let [test-mode? (get-in db [:profile/profile :test-networks-enabled?]) - networks (get-in db [:wallet :networks (if test-mode? :test :prod)])] - (mapv #(-> % :chain-id) networks))) - -(defn- add-full-testnet-name - "Updates the `:full-name` key with the full testnet name if using testnet `:chain-id`.\n - e.g. `{:full-name \"Mainnet\"}` -> `{:full-name \"Mainnet Sepolia\"`}`" - [network] - (let [add-testnet-name (fn [testnet-name] - (update network :full-name #(str % " " testnet-name)))] - (condp #(contains? %1 %2) (:chain-id network) - constants/sepolia-chain-ids (add-testnet-name constants/sepolia-full-name) - constants/goerli-chain-ids (add-testnet-name constants/goerli-full-name) - network))) - -(defn chain-id->network-details - [chain-id] - (-> chain-id - (networks/get-network-details) - (add-full-testnet-name))) - -(defn session-networks-allowed? - [testnet-mode? {:keys [chains]}] - (let [chain-ids (set (map (fn [chain] - (-> chain - (string/split ":") - second - js/parseInt)) - chains))] - (if testnet-mode? - (set/subset? chain-ids (set/union constants/sepolia-chain-ids constants/goerli-chain-ids)) - (set/subset? chain-ids constants/mainnet-chain-ids)))) - -(defn event-should-be-handled? - [db {:keys [topic]}] - (let [testnet-mode? (get-in db [:profile/profile :test-networks-enabled?])] - (some #(and (= (:topic %) topic) - (session-networks-allowed? testnet-mode? %)) - (:wallet-connect/sessions db)))) - -(defn sdk-session->db-session - [{:keys [topic expiry pairingTopic] :as session}] - {:topic topic - :expiry expiry - :sessionJson (transforms/clj->json session) - :pairingTopic pairingTopic - :name (get-in session [:peer :metadata :name]) - :iconUrl (get-in session [:peer :metadata :icons 0]) - :url (get-in session [:peer :metadata :url]) - :accounts (get-in session [:namespaces :eip155 :accounts]) - :chains (get-in session [:namespaces :eip155 :chains]) - :disconnected false}) - -(defn filter-operable-accounts - [accounts] - (filter #(and (:operable? %) - (not (:watch-only? %))) - accounts)) - -(defn filter-sessions-for-account-addresses - [account-addresses sessions] - (filter (fn [{:keys [accounts]}] - (some (fn [account] - (some (fn [account-address] - (clojure.string/includes? account account-address)) - account-addresses)) - accounts)) - sessions)) - -(defn compute-dapp-name - "Sometimes dapps have no name or an empty name. Return url as name in that case" - [name url] - (if (seq name) - name - (when (seq url) - (-> url - utils.string/remove-trailing-slash - utils.string/remove-http-prefix - string/capitalize)))) - -(defn compute-dapp-icon-path - "Some dapps have icons with relative paths, make paths absolute in those cases, send nil if icon is missing" - [icon-path url] - (when (and (seq icon-path) - (seq url)) - (if (string/starts-with? icon-path "http") - icon-path - (str (utils.string/remove-trailing-slash url) icon-path)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/core_test.cljs b/src/status_im/contexts/wallet/wallet_connect/core_test.cljs index adfe9b2fea..e69de29bb2 100644 --- a/src/status_im/contexts/wallet/wallet_connect/core_test.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/core_test.cljs @@ -1,27 +0,0 @@ -(ns status-im.contexts.wallet.wallet-connect.core-test - (:require - [cljs.test :refer-macros [deftest is testing]] - [status-im.contexts.wallet.wallet-connect.core :as sut])) - -(deftest get-current-request-dapp-test - (testing "returns the correct dapp based on the request's origin" - (let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}} - sessions [{:url "https://dapp.com"} - {:url "https://anotherdapp.com"}]] - (is (= {:url "https://dapp.com"} - (sut/get-current-request-dapp request sessions))))) - - (testing "returns nil if no matching dapp is found" - (let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}} - sessions [{:url "https://anotherdapp.com"}]] - (is (nil? (sut/get-current-request-dapp request sessions)))))) - -(deftest get-dapp-redirect-url-test - (testing "returns the native redirect URL if it exists" - (let [session {:peer {:metadata {:redirect {:native "native://redirect-url"}}}}] - (is (= "native://redirect-url" - (sut/get-dapp-redirect-url session))))) - - (testing "returns nil if no redirect URL is found" - (let [session {:peer {:metadata {}}}] - (is (nil? (sut/get-dapp-redirect-url session)))))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index 4848591354..e69de29bb2 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -1,358 +0,0 @@ -(ns status-im.contexts.wallet.wallet-connect.events - (:require [re-frame.core :as rf] - [react-native.wallet-connect :as wallet-connect] - [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] - status-im.contexts.wallet.wallet-connect.effects - status-im.contexts.wallet.wallet-connect.processing-events - status-im.contexts.wallet.wallet-connect.responding-events - [status-im.contexts.wallet.wallet-connect.utils :as wc-utils] - [taoensso.timbre :as log] - [utils.i18n :as i18n] - [utils.transforms :as types])) - -(rf/reg-event-fx - :wallet-connect/init - (fn [{:keys [db]}] - (let [network-status (:network/status db) - web3-wallet-missing? (-> db :wallet-connect/web3-wallet boolean not)] - (if (and (= network-status :online) web3-wallet-missing?) - (do (log/info "Initialising WalletConnect SDK") - {: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]]]})))) - -(rf/reg-event-fx - :wallet-connect/on-init-success - (fn [{:keys [db]} [web3-wallet]] - (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]]]})) - -(rf/reg-event-fx - :wallet-connect/reload-on-network-change - (fn [{:keys [db]} [is-connected?]] - (let [logged-in? (-> db :profile/profile boolean)] - (when (and is-connected? logged-in?) - (log/info "Re-Initialising WalletConnect SDK due to network change") - {:fx [[:dispatch [:wallet-connect/init]]]})))) - -(rf/reg-event-fx - :wallet-connect/register-event-listeners - (fn [{:keys [db]}] - (let [web3-wallet (get db :wallet-connect/web3-wallet)] - {:fx [[:effects.wallet-connect/register-event-listener - [web3-wallet - constants/wallet-connect-session-proposal-event - #(rf/dispatch [:wallet-connect/on-session-proposal %])]] - [:effects.wallet-connect/register-event-listener - [web3-wallet - constants/wallet-connect-session-request-event - #(rf/dispatch [:wallet-connect/on-session-request %])]] - [:effects.wallet-connect/register-event-listener - [web3-wallet - constants/wallet-connect-session-delete-event - #(rf/dispatch [:wallet-connect/on-session-delete %])]]]}))) - -(rf/reg-event-fx - :wallet-connect/on-init-fail - (fn [_ [error]] - (log/error "Failed to initialize Wallet Connect" - {:error error - :event :wallet-connect/on-init-fail}))) - -(rf/reg-event-fx - :wallet-connect/on-session-proposal - (fn [{:keys [db]} [proposal]] - (log/info "Received Wallet Connect session proposal: " proposal) - (let [accounts (get-in db [:wallet :accounts]) - current-viewing-address (get-in db [:wallet :current-viewing-account-address]) - available-accounts (wallet-connect-core/filter-operable-accounts (vals accounts)) - networks (wallet-connect-core/get-networks-by-mode db) - session-networks (wallet-connect-core/proposal-networks-intersection proposal - networks) - required-networks-supported? (wallet-connect-core/required-networks-supported? proposal - networks)] - (if (and (not-empty session-networks) required-networks-supported?) - {:db (update db - :wallet-connect/current-proposal assoc - :request proposal - :session-networks session-networks - :address (or current-viewing-address - (-> available-accounts - first - :address))) - :fx [[:dispatch - [:open-modal :screen/wallet.wallet-connect-session-proposal]]]} - {:fx [[:dispatch - [:wallet-connect/show-session-networks-unsupported-toast proposal]] - [:dispatch - [:wallet-connect/reject-session-proposal proposal]]]})))) - -(rf/reg-event-fx - :wallet-connect/show-session-networks-unsupported-toast - (fn [{:keys [db]} [proposal]] - (let [{:keys [name url]} (wallet-connect-core/get-session-dapp-metadata proposal)] - {:fx [[:dispatch - [:toasts/upsert - {:type :negative - :theme (:theme db) - :text (i18n/label :t/wallet-connect-networks-not-supported - {:dapp (wallet-connect-core/compute-dapp-name name url)})}]]]}))) - -(rf/reg-event-fx - :wallet-connect/on-session-request - (fn [{:keys [db]} [event]] - (if (wallet-connect-core/event-should-be-handled? db event) - {:fx [[:dispatch [:wallet-connect/process-session-request event]]]} - {:fx [[:dispatch - [:wallet-connect/show-session-networks-unsupported-toast event]] - [:dispatch - [:wallet-connect/send-response - {:request event - :error (wallet-connect/get-sdk-error - constants/wallet-connect-user-rejected-chains-error-key)}]]]}))) - -(rf/reg-event-fx - :wallet-connect/on-session-delete - (fn [{:keys [db]} [{:keys [topic] :as event}]] - (when (wallet-connect-core/event-should-be-handled? db event) - (log/info "Received Wallet Connect session delete from the SDK: " event) - {:fx [[:dispatch [:wallet-connect/disconnect-session topic]]]}))) - -(rf/reg-event-fx - :wallet-connect/reset-current-session-proposal - (fn [{:keys [db]}] - {:db (dissoc db :wallet-connect/current-proposal)})) - -(rf/reg-event-fx - :wallet-connect/set-current-proposal-address - (fn [{:keys [db]} [address]] - {:db (assoc-in db [:wallet-connect/current-proposal :address] address)})) - -(rf/reg-event-fx - :wallet-connect/reset-current-request - (fn [{:keys [db]}] - {:db (dissoc db :wallet-connect/current-request)})) - -(rf/reg-event-fx - :wallet-connect/disconnect-dapp - (fn [{:keys [db]} [{:keys [topic on-success on-fail]}]] - (let [web3-wallet (get db :wallet-connect/web3-wallet) - network-status (:network/status db)] - (log/info "Disconnecting dApp session" topic) - (if (= network-status :online) - {: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-session topic]) - (when on-success - (on-success)))}]]} - {:fx [[:dispatch [:wallet-connect/no-internet-toast]]]})))) - -(rf/reg-event-fx - :wallet-connect/pair - (fn [{:keys [db]} [url]] - (let [web3-wallet (get db :wallet-connect/web3-wallet)] - {:fx [[:effects.wallet-connect/pair - {:web3-wallet web3-wallet - :url url - :on-fail #(log/error "Failed to pair with dApp" {:error %}) - :on-success #(log/info "dApp paired successfully")}]]}))) - -(rf/reg-event-fx - :wallet-connect/approve-session - (fn [{:keys [db]}] - (let [web3-wallet (get db :wallet-connect/web3-wallet) - current-proposal (get-in db [:wallet-connect/current-proposal :request]) - session-networks (->> (get-in db [:wallet-connect/current-proposal :session-networks]) - (map wallet-connect-core/chain-id->eip155) - vec) - current-address (get-in db [:wallet-connect/current-proposal :address]) - accounts (-> (partial wallet-connect-core/format-eip155-address current-address) - (map session-networks)) - network-status (:network/status db) - expiry (get-in current-proposal [:params :expiryTimestamp])] - (if (= network-status :online) - {:db (assoc-in db [:wallet-connect/current-proposal :response-sent?] true) - :fx [(if (wc-utils/timestamp-expired? expiry) - [: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]))}]) - [:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]} - {:fx [[:dispatch [:wallet-connect/no-internet-toast]]]})))) - -(rf/reg-event-fx - :wallet-connect/on-scan-connection - (fn [{:keys [db]} [scanned-text]] - (let [network-status (:network/status db) - parsed-uri (wallet-connect/parse-uri scanned-text) - version (:version parsed-uri) - valid-wc-uri? (wc-utils/valid-wc-uri? parsed-uri) - expired? (-> parsed-uri - :expiryTimestamp - wc-utils/timestamp-expired?) - version-supported? (wc-utils/version-supported? version)] - (if (or (not valid-wc-uri?) - (not version-supported?) - (= network-status :offline) - expired?) - {:fx [[:dispatch - [:toasts/upsert - {:type :negative - :theme :dark - :text (cond (= network-status :offline) - (i18n/label :t/wallet-connect-no-internet-warning) - - (not valid-wc-uri?) - (i18n/label :t/wallet-connect-wrong-qr) - - expired? - (i18n/label :t/wallet-connect-qr-expired) - - (not version-supported?) - (i18n/label :t/wallet-connect-version-not-supported - {:version version}) - - :else - (i18n/label :t/something-went-wrong))}]]]} - {:fx [[:dispatch [:wallet-connect/pair scanned-text]]]})))) - -;; 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 - wallet-connect-core/filter-operable-accounts - (map :address)) - sessions (->> (js->clj sessions :keywordize-keys true) - vals - (map wallet-connect-core/sdk-session->db-session) - (wallet-connect-core/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-session topic]]) - expired-sessions) - :db (assoc db :wallet-connect/sessions sessions)}))) - -(rf/reg-event-fx - :wallet-connect/fetch-active-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 %])}]]}))) - -(rf/reg-event-fx - :wallet-connect/fetch-persisted-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')}))) - -(rf/reg-event-fx - :wallet-connect/fetch-persisted-sessions-fail - (fn [_ [error]] - (log/info "Wallet Connect fetch persisted sessions failed" error) - {:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]]})) - -(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]}]]]}))) - -(rf/reg-event-fx - :wallet-connect/persist-session - (fn [_ [session-info]] - (let [redirect-url (-> session-info - (js->clj :keywordize-keys true) - (wallet-connect-core/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-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" %)}]]]})) - -(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)}]]]})) diff --git a/src/status_im/contexts/wallet/wallet_connect/events/core.cljs b/src/status_im/contexts/wallet/wallet_connect/events/core.cljs new file mode 100644 index 0000000000..5a3a951250 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/events/core.cljs @@ -0,0 +1,92 @@ +(ns status-im.contexts.wallet.wallet-connect.events.core + (:require [re-frame.core :as rf] + [react-native.wallet-connect :as wallet-connect] + [status-im.constants :as constants] + status-im.contexts.wallet.wallet-connect.events.effects + status-im.contexts.wallet.wallet-connect.events.session-proposals + status-im.contexts.wallet.wallet-connect.events.session-requests + status-im.contexts.wallet.wallet-connect.events.session-responses + status-im.contexts.wallet.wallet-connect.events.sessions + [status-im.contexts.wallet.wallet-connect.utils.networks :as networks] + [taoensso.timbre :as log] + [utils.i18n :as i18n])) + +(rf/reg-event-fx + :wallet-connect/init + (fn [{:keys [db]}] + (let [network-status (:network/status db) + web3-wallet-missing? (-> db :wallet-connect/web3-wallet boolean not)] + (if (and (= network-status :online) web3-wallet-missing?) + (do (log/info "Initialising WalletConnect SDK") + {: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]]]})))) + +(rf/reg-event-fx + :wallet-connect/on-init-success + (fn [{:keys [db]} [web3-wallet]] + (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]]]})) + +(rf/reg-event-fx + :wallet-connect/reload-on-network-change + (fn [{:keys [db]} [is-connected?]] + (let [logged-in? (-> db :profile/profile boolean)] + (when (and is-connected? logged-in?) + (log/info "Re-Initialising WalletConnect SDK due to network change") + {:fx [[:dispatch [:wallet-connect/init]]]})))) + +(rf/reg-event-fx + :wallet-connect/register-event-listeners + (fn [{:keys [db]}] + (let [web3-wallet (get db :wallet-connect/web3-wallet)] + {:fx [[:effects.wallet-connect/register-event-listener + [web3-wallet + constants/wallet-connect-session-proposal-event + #(rf/dispatch [:wallet-connect/on-session-proposal %])]] + [:effects.wallet-connect/register-event-listener + [web3-wallet + constants/wallet-connect-session-request-event + #(rf/dispatch [:wallet-connect/on-session-request %])]] + [:effects.wallet-connect/register-event-listener + [web3-wallet + constants/wallet-connect-session-delete-event + #(rf/dispatch [:wallet-connect/on-session-delete %])]]]}))) + +(rf/reg-event-fx + :wallet-connect/on-init-fail + (fn [_ [error]] + (log/error "Failed to initialize Wallet Connect" + {:error error + :event :wallet-connect/on-init-fail}))) + +(rf/reg-event-fx + :wallet-connect/on-session-request + (fn [{:keys [db]} [event]] + (if (networks/event-should-be-handled? db event) + {:fx [[:dispatch [:wallet-connect/process-session-request event]]]} + {:fx [[:dispatch + [:wallet-connect/show-session-networks-unsupported-toast event]] + [:dispatch + [:wallet-connect/send-response + {:request event + :error (wallet-connect/get-sdk-error + constants/wallet-connect-user-rejected-chains-error-key)}]]]}))) + +(rf/reg-event-fx + :wallet-connect/reset-current-request + (fn [{:keys [db]}] + {:db (dissoc db :wallet-connect/current-request)})) + +(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)}]]]})) diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/events/effects.cljs similarity index 96% rename from src/status_im/contexts/wallet/wallet_connect/effects.cljs rename to src/status_im/contexts/wallet/wallet_connect/events/effects.cljs index f08588d932..320b500329 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/effects.cljs @@ -1,12 +1,12 @@ -(ns status-im.contexts.wallet.wallet-connect.effects +(ns status-im.contexts.wallet.wallet-connect.events.effects (:require [promesa.core :as promesa] [re-frame.core :as rf] [react-native.wallet-connect :as wallet-connect] [status-im.config :as config] [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.signing :as signing] - [status-im.contexts.wallet.wallet-connect.transactions :as transactions] + [status-im.contexts.wallet.wallet-connect.utils.signing :as signing] + [status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions] [utils.i18n :as i18n] [utils.security.core :as security])) 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 new file mode 100644 index 0000000000..06384697bd --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_proposals.cljs @@ -0,0 +1,141 @@ +(ns status-im.contexts.wallet.wallet-connect.events.session-proposals + (:require [re-frame.core :as rf] + [react-native.wallet-connect :as wallet-connect] + [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] + [status-im.contexts.wallet.wallet-connect.utils.uri :as uri] + [taoensso.timbre :as log] + [utils.i18n :as i18n])) + +(rf/reg-event-fx + :wallet-connect/pair + (fn [{:keys [db]} [url]] + (let [web3-wallet (get db :wallet-connect/web3-wallet)] + {:fx [[:effects.wallet-connect/pair + {:web3-wallet web3-wallet + :url url + :on-fail #(log/error "Failed to pair with dApp" {:error %}) + :on-success #(log/info "dApp paired successfully")}]]}))) + +(rf/reg-event-fx + :wallet-connect/on-scan-connection + (fn [{:keys [db]} [scanned-text]] + (let [network-status (:network/status db) + parsed-uri (wallet-connect/parse-uri scanned-text) + version (:version parsed-uri) + valid-wc-uri? (uri/valid-wc-uri? parsed-uri) + expired? (-> parsed-uri + :expiryTimestamp + uri/timestamp-expired?) + version-supported? (uri/version-supported? version)] + (if (or (not valid-wc-uri?) + (not version-supported?) + (= network-status :offline) + expired?) + {:fx [[:dispatch + [:toasts/upsert + {:type :negative + :theme :dark + :text (cond (= network-status :offline) + (i18n/label :t/wallet-connect-no-internet-warning) + + (not valid-wc-uri?) + (i18n/label :t/wallet-connect-wrong-qr) + + expired? + (i18n/label :t/wallet-connect-qr-expired) + + (not version-supported?) + (i18n/label :t/wallet-connect-version-not-supported + {:version version}) + + :else + (i18n/label :t/something-went-wrong))}]]]} + {:fx [[:dispatch [:wallet-connect/pair scanned-text]]]})))) + +(rf/reg-event-fx + :wallet-connect/on-session-proposal + (fn [{:keys [db]} [proposal]] + (log/info "Received Wallet Connect session proposal: " proposal) + (let [accounts (get-in db [:wallet :accounts]) + current-viewing-address (get-in db [:wallet :current-viewing-account-address]) + available-accounts (sessions/filter-operable-accounts (vals accounts)) + networks (networks/get-networks-by-mode db) + session-networks (networks/proposal-networks-intersection proposal networks) + required-networks-supported? (networks/required-networks-supported? proposal networks)] + (if (and (not-empty session-networks) required-networks-supported?) + {:db (update db + :wallet-connect/current-proposal assoc + :request proposal + :session-networks session-networks + :address (or current-viewing-address + (-> available-accounts + first + :address))) + :fx [[:dispatch [:open-modal :screen/wallet.wallet-connect-session-proposal]]]} + {:fx [[:dispatch [:wallet-connect/show-session-networks-unsupported-toast proposal]] + [:dispatch [:wallet-connect/reject-session-proposal proposal]]]})))) + +(rf/reg-event-fx + :wallet-connect/show-session-networks-unsupported-toast + (fn [{:keys [db]} [proposal]] + (let [{:keys [name url]} (data-store/get-session-dapp-metadata proposal)] + {:fx [[:dispatch + [:toasts/upsert + {:type :negative + :theme (:theme db) + :text (i18n/label :t/wallet-connect-networks-not-supported + {:dapp (data-store/compute-dapp-name name url)})}]]]}))) + +(rf/reg-event-fx + :wallet-connect/reset-current-session-proposal + (fn [{:keys [db]}] + {:db (dissoc db :wallet-connect/current-proposal)})) + +(rf/reg-event-fx + :wallet-connect/set-current-proposal-address + (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]}] + (let [web3-wallet (get db :wallet-connect/web3-wallet) + current-proposal (get-in db [:wallet-connect/current-proposal :request]) + session-networks (->> (get-in db [:wallet-connect/current-proposal :session-networks]) + (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])] + (if (= network-status :online) + {:db (assoc-in db [:wallet-connect/current-proposal :response-sent?] true) + :fx [(if (uri/timestamp-expired? expiry) + [: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]))}]) + [:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]} + {:fx [[:dispatch [:wallet-connect/no-internet-toast]]]})))) diff --git a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs b/src/status_im/contexts/wallet/wallet_connect/events/session_requests.cljs similarity index 82% rename from src/status_im/contexts/wallet/wallet_connect/processing_events.cljs rename to src/status_im/contexts/wallet/wallet_connect/events/session_requests.cljs index 224720c98a..fea1ec1f54 100644 --- a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_requests.cljs @@ -1,12 +1,14 @@ -(ns status-im.contexts.wallet.wallet-connect.processing-events +(ns status-im.contexts.wallet.wallet-connect.events.session-requests (:require [cljs-bean.core :as bean] [clojure.string :as string] [native-module.core :as native-module] [re-frame.core :as rf] [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] - [status-im.contexts.wallet.wallet-connect.signing :as signing] - [status-im.contexts.wallet.wallet-connect.transactions :as transactions] + [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.signing :as signing] + [status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions] [taoensso.timbre :as log] [utils.i18n :as i18n] [utils.transforms :as transforms])) @@ -15,8 +17,8 @@ :wallet-connect/show-request-modal (fn [{:keys [db]}] (let [event (get-in db [:wallet-connect/current-request :event]) - method (wallet-connect-core/get-request-method event) - screen (wallet-connect-core/method-to-screen method)] + method (data-store/get-request-method event) + screen (data-store/method-to-screen method)] (if screen {:fx [[:dispatch [:open-modal screen]]]} (log/error "Didn't find screen for Wallet Connect method" @@ -25,7 +27,7 @@ (rf/reg-event-fx :wallet-connect/process-session-request (fn [{:keys [db]} [event]] - (let [method (wallet-connect-core/get-request-method event) + (let [method (data-store/get-request-method event) existing-event (get-in db [:wallet-connect/current-request :event])] (log/info "Processing Wallet Connect session request" method) ;; NOTE: make sure we don't show two requests at the same time @@ -55,7 +57,7 @@ (rf/reg-event-fx :wallet-connect/process-personal-sign (fn [{:keys [db]}] - (let [[raw-data address] (wallet-connect-core/get-db-current-request-params db) + (let [[raw-data address] (data-store/get-db-current-request-params db) parsed-data (native-module/hex-to-utf8 raw-data)] {:db (update-in db [:wallet-connect/current-request] @@ -68,7 +70,7 @@ (rf/reg-event-fx :wallet-connect/process-eth-sign (fn [{:keys [db]}] - (let [[address raw-data] (wallet-connect-core/get-db-current-request-params db) + (let [[address raw-data] (data-store/get-db-current-request-params db) parsed-data (native-module/hex-to-utf8 raw-data)] {:db (update-in db [:wallet-connect/current-request] @@ -98,11 +100,11 @@ (rf/reg-event-fx :wallet-connect/process-eth-send-transaction (fn [{:keys [db]}] - (let [event (wallet-connect-core/get-db-current-request-event db) - tx (-> event wallet-connect-core/get-request-params first) + (let [event (data-store/get-db-current-request-event db) + tx (-> event data-store/get-request-params first) chain-id (-> event (get-in [:params :chainId]) - wallet-connect-core/eip155->chain-id)] + networks/eip155->chain-id)] {:fx [[:effects.wallet-connect/prepare-transaction {:tx tx :chain-id chain-id @@ -112,11 +114,11 @@ (rf/reg-event-fx :wallet-connect/process-eth-sign-transaction (fn [{:keys [db]}] - (let [event (wallet-connect-core/get-db-current-request-event db) - tx (-> event wallet-connect-core/get-request-params first) + (let [event (data-store/get-db-current-request-event db) + tx (-> event data-store/get-request-params first) chain-id (-> event (get-in [:params :chainId]) - wallet-connect-core/eip155->chain-id)] + networks/eip155->chain-id)] {:fx [[:effects.wallet-connect/prepare-transaction {:tx tx :chain-id chain-id @@ -127,11 +129,11 @@ :wallet-connect/process-sign-typed (fn [{:keys [db]}] (try - (let [[address raw-data] (wallet-connect-core/get-db-current-request-params db) + (let [[address raw-data] (data-store/get-db-current-request-params db) parsed-raw-data (transforms/js-parse raw-data) - session-chain-id (-> (wallet-connect-core/get-db-current-request-event db) + session-chain-id (-> (data-store/get-db-current-request-event db) (get-in [:params :chainId]) - wallet-connect-core/eip155->chain-id) + networks/eip155->chain-id) data-chain-id (-> parsed-raw-data transforms/js->clj signing/typed-data-chain-id) @@ -156,20 +158,20 @@ [:wallet-connect/on-processing-error (ex-info "Failed to parse JSON typed data" {:error err - :data (wallet-connect-core/get-db-current-request-params db)})]]]})))) + :data (data-store/get-db-current-request-params db)})]]]})))) (rf/reg-event-fx :wallet-connect/wrong-typed-data-chain-id (fn [_ [{:keys [expected-chain-id wrong-chain-id]}]] (let [wrong-network-name (-> wrong-chain-id - wallet-connect-core/chain-id->network-details + networks/chain-id->network-details :full-name) expected-network-name (-> expected-chain-id - wallet-connect-core/chain-id->network-details + networks/chain-id->network-details :full-name) toast-message (i18n/label :t/wallet-connect-typed-data-wrong-chain-id-warning {:wrong-chain (or wrong-network-name - (wallet-connect-core/chain-id->eip155 + (networks/chain-id->eip155 wrong-chain-id)) :expected-chain expected-network-name})] {:fx [[:dispatch @@ -186,7 +188,7 @@ :wallet-connect/on-processing-error (fn [{:keys [db]} [error]] (let [{:keys [address event]} (get db :wallet-connect/current-request) - method (wallet-connect-core/get-request-method event)] + method (data-store/get-request-method event)] (log/error "Failed to process Wallet Connect request" {:error error :address address diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs b/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs similarity index 87% rename from src/status_im/contexts/wallet/wallet_connect/responding_events.cljs rename to src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs index f3138e7299..29f4a467f7 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs @@ -1,9 +1,10 @@ -(ns status-im.contexts.wallet.wallet-connect.responding-events +(ns status-im.contexts.wallet.wallet-connect.events.session-responses (:require [re-frame.core :as rf] [react-native.wallet-connect :as wallet-connect] [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] - [status-im.contexts.wallet.wallet-connect.utils :as wc-utils] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as + data-store] + [status-im.contexts.wallet.wallet-connect.utils.uri :as uri] [taoensso.timbre :as log] [utils.i18n :as i18n] [utils.transforms :as transforms])) @@ -12,10 +13,10 @@ :wallet-connect/respond-current-session (fn [{:keys [db]} [password]] (let [event (get-in db [:wallet-connect/current-request :event]) - method (wallet-connect-core/get-request-method event) - screen (wallet-connect-core/method-to-screen method) + method (data-store/get-request-method event) + screen (data-store/method-to-screen method) expiry (get-in event [:params :request :expiryTimestamp])] - (if (wc-utils/timestamp-expired? expiry) + (if (uri/timestamp-expired? expiry) {:fx [[:dispatch [:toasts/upsert {:id :new-wallet-account-created @@ -99,7 +100,7 @@ :wallet-connect/on-sign-error (fn [{:keys [db]} [error]] (let [{:keys [raw-data address event]} (get db :wallet-connect/current-request) - method (wallet-connect-core/get-request-method event)] + method (data-store/get-request-method event)] (log/error "Failed to sign Wallet Connect request" {:error error :address address @@ -118,7 +119,7 @@ (fn [{:keys [db]} [{:keys [request result error]}]] (when-let [{:keys [id topic] :as event} (or request (get-in db [:wallet-connect/current-request :event]))] - (let [method (wallet-connect-core/get-request-method event) + (let [method (data-store/get-request-method event) web3-wallet (get db :wallet-connect/web3-wallet)] {:db (assoc-in db [:wallet-connect/current-request :response-sent?] true) :fx [[:effects.wallet-connect/respond-session-request @@ -142,11 +143,11 @@ (fn [{:keys [db]} [url]] (let [redirect-url (or url (->> (get db :wallet-connect/current-request) - (wallet-connect-core/get-current-request-dapp + (data-store/get-current-request-dapp (get db :wallet-connect/sessions)) :sessionJson transforms/json->clj - wallet-connect-core/get-dapp-redirect-url))] + data-store/get-dapp-redirect-url))] {:fx [[:open-url redirect-url]]}))) (rf/reg-event-fx @@ -154,17 +155,8 @@ (fn [{:keys [db]} _] (let [screen (-> db (get-in [:wallet-connect/current-request :event]) - wallet-connect-core/get-request-method - wallet-connect-core/method-to-screen)] - {:fx [[:dispatch [:dismiss-modal screen]]]}))) - -(rf/reg-event-fx - :wallet-connect/dismiss-request-modal - (fn [{:keys [db]} _] - (let [screen (-> db - (get-in [:wallet-connect/current-request :event]) - wallet-connect-core/get-request-method - wallet-connect-core/method-to-screen)] + data-store/get-request-method + data-store/method-to-screen)] {:fx [[:dispatch [:dismiss-modal screen]]]}))) (rf/reg-event-fx diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events_test.cljs b/src/status_im/contexts/wallet/wallet_connect/events/session_responses_test.cljs similarity index 75% rename from src/status_im/contexts/wallet/wallet_connect/responding_events_test.cljs rename to src/status_im/contexts/wallet/wallet_connect/events/session_responses_test.cljs index f6ec8eb613..e25ecd643b 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events_test.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_responses_test.cljs @@ -1,10 +1,10 @@ -(ns status-im.contexts.wallet.wallet-connect.responding-events-test +(ns status-im.contexts.wallet.wallet-connect.events.session-responses-test (:require [cljs.test :refer-macros [is]] matcher-combinators.test [re-frame.db :as rf-db] - [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] - status-im.contexts.wallet.wallet-connect.responding-events + status-im.contexts.wallet.wallet-connect.events.session-responses + [status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store] [test-helpers.unit :as h] [utils.transforms :as transforms])) @@ -16,11 +16,11 @@ :sessionJson session-json}]] (reset! rf-db/app-db {:wallet-connect {:current-request current-request :sessions sessions}}) - (with-redefs [wallet-connect-core/get-current-request-dapp + (with-redefs [data-store/get-current-request-dapp (fn [_ _] (first sessions)) transforms/json->clj (fn [json] (js/JSON.parse json)) - wallet-connect-core/get-dapp-redirect-url + data-store/get-dapp-redirect-url (fn [_] "native://redirect-url")] (is (match? {:fx [[:open-url "native://redirect-url"]]} diff --git a/src/status_im/contexts/wallet/wallet_connect/events/sessions.cljs b/src/status_im/contexts/wallet/wallet_connect/events/sessions.cljs new file mode 100644 index 0000000000..515271e40f --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/events/sessions.cljs @@ -0,0 +1,141 @@ +(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])) + +(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]]]}))) + +(rf/reg-event-fx + :wallet-connect/disconnect-dapp + (fn [{:keys [db]} [{:keys [topic on-success on-fail]}]] + (let [web3-wallet (get db :wallet-connect/web3-wallet) + network-status (:network/status db)] + (log/info "Disconnecting dApp session" topic) + (if (= network-status :online) + {: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]) + (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 + (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 %])}]]}))) + +(rf/reg-event-fx + :wallet-connect/fetch-persisted-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')}))) + +(rf/reg-event-fx + :wallet-connect/fetch-persisted-sessions-fail + (fn [_ [error]] + (log/info "Wallet Connect fetch persisted sessions failed" error) + {:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]]})) + +(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]}]]]}))) + +(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 + (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" %)}]]]})) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs index ae3dc1d424..f0eda3907d 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/header/view.cljs @@ -2,14 +2,15 @@ (:require [clojure.string :as string] [quo.core :as quo] [react-native.core :as rn] - [status-im.contexts.wallet.wallet-connect.core :as core] - [status-im.contexts.wallet.wallet-connect.modals.common.header.style :as style])) + [status-im.contexts.wallet.wallet-connect.modals.common.header.style :as style] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as + data-store])) (defn view [{:keys [label dapp account]}] [rn/view {:style style/header-container} (let [{:keys [name iconUrl url]} dapp - image-source (core/compute-dapp-icon-path iconUrl url)] + image-source (data-store/compute-dapp-icon-path iconUrl url)] [rn/view {:style style/dapp-container} [quo/summary-tag {:type :dapp diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/style.cljs similarity index 92% rename from src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs rename to src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/style.cljs index af73ef6c62..d589905116 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/style.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/style.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.wallet.wallet-connect.session-proposal.style +(ns status-im.contexts.wallet.wallet-connect.modals.session-proposal.style (:require [quo.foundations.colors :as colors])) (def dapp-avatar diff --git a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs similarity index 94% rename from src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs rename to src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs index c989eb515b..32aa536893 100644 --- a/src/status_im/contexts/wallet/wallet_connect/session_proposal/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.wallet.wallet-connect.session-proposal.view +(ns status-im.contexts.wallet.wallet-connect.modals.session-proposal.view (:require [clojure.string :as string] [quo.core :as quo] @@ -6,8 +6,8 @@ [quo.theme] [react-native.core :as rn] [status-im.common.floating-button-page.view :as floating-button-page] - [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] - [status-im.contexts.wallet.wallet-connect.session-proposal.style :as style] + [status-im.contexts.wallet.wallet-connect.modals.session-proposal.style :as style] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store] [utils.i18n :as i18n] [utils.re-frame :as rf] [utils.string])) @@ -17,8 +17,8 @@ (let [proposer (rf/sub [:wallet-connect/session-proposer]) {:keys [icons name url]} (:metadata proposer) first-icon (first icons) - dapp-name (wallet-connect-core/compute-dapp-name name url) - profile-picture (wallet-connect-core/compute-dapp-icon-path first-icon url)] + dapp-name (data-store/compute-dapp-name name url) + profile-picture (data-store/compute-dapp-icon-path first-icon url)] [:<> [rn/view {:style style/dapp-avatar} [quo/user-avatar diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/data_store.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/data_store.cljs new file mode 100644 index 0000000000..c7b52b7d74 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/utils/data_store.cljs @@ -0,0 +1,75 @@ +(ns status-im.contexts.wallet.wallet-connect.utils.data-store + (:require + [clojure.string :as string] + [status-im.constants :as constants] + utils.string + [utils.transforms :as transforms])) + +(defn compute-dapp-name + "Sometimes dapps have no name or an empty name. Return url as name in that case" + [name url] + (if (seq name) + name + (when (seq url) + (-> url + utils.string/remove-trailing-slash + utils.string/remove-http-prefix + string/capitalize)))) + +(defn compute-dapp-icon-path + "Some dapps have icons with relative paths, make paths absolute in those cases, send nil if icon is missing" + [icon-path url] + (when (and (seq icon-path) + (seq url)) + (if (string/starts-with? icon-path "http") + icon-path + (str (utils.string/remove-trailing-slash url) icon-path)))) + +(def method-to-screen + {constants/wallet-connect-personal-sign-method :screen/wallet-connect.sign-message + constants/wallet-connect-eth-sign-typed-method :screen/wallet-connect.sign-message + constants/wallet-connect-eth-sign-method :screen/wallet-connect.sign-message + constants/wallet-connect-eth-sign-typed-v4-method :screen/wallet-connect.sign-message + constants/wallet-connect-eth-send-transaction-method :screen/wallet-connect.send-transaction + constants/wallet-connect-eth-sign-transaction-method :screen/wallet-connect.sign-transaction}) + +(defn extract-native-call-signature + [data] + (-> data transforms/json->clj :result)) + +(defn get-request-method + [event] + (get-in event [:params :request :method])) + +(defn get-request-params + [event] + (get-in event [:params :request :params])) + +(defn get-db-current-request-event + [db] + (get-in db [:wallet-connect/current-request :event])) + +(defn get-session-dapp-metadata + [proposal] + (let [metadata (get-in proposal [:params :proposer :metadata]) + origin (get-in proposal [:verifyContext :verified :origin])] + (or metadata {:url origin}))) + +(defn get-current-request-dapp + [request sessions] + (let [dapp-url (get-in request [:event :verifyContext :verified :origin])] + (->> sessions + (filter (fn [session] + (= (utils.string/remove-trailing-slash dapp-url) + (utils.string/remove-trailing-slash (get session :url))))) + first))) + +(defn get-dapp-redirect-url + [session] + (get-in session [:peer :metadata :redirect :native])) + +(defn get-db-current-request-params + [db] + (-> db + get-db-current-request-event + get-request-params)) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/data_store_test.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/data_store_test.cljs new file mode 100644 index 0000000000..6202ec692f --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/utils/data_store_test.cljs @@ -0,0 +1,27 @@ +(ns status-im.contexts.wallet.wallet-connect.utils.data-store-test + (:require + [cljs.test :refer-macros [deftest is testing]] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as sut])) + +(deftest get-current-request-dapp-test + (testing "returns the correct dapp based on the request's origin" + (let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}} + sessions [{:url "https://dapp.com"} + {:url "https://anotherdapp.com"}]] + (is (= {:url "https://dapp.com"} + (sut/get-current-request-dapp request sessions))))) + + (testing "returns nil if no matching dapp is found" + (let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}} + sessions [{:url "https://anotherdapp.com"}]] + (is (nil? (sut/get-current-request-dapp request sessions)))))) + +(deftest get-dapp-redirect-url-test + (testing "returns the native redirect URL if it exists" + (let [session {:peer {:metadata {:redirect {:native "native://redirect-url"}}}}] + (is (= "native://redirect-url" + (sut/get-dapp-redirect-url session))))) + + (testing "returns nil if no redirect URL is found" + (let [session {:peer {:metadata {}}}] + (is (nil? (sut/get-dapp-redirect-url session)))))) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/networks.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/networks.cljs new file mode 100644 index 0000000000..c0a99ccf49 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/utils/networks.cljs @@ -0,0 +1,92 @@ +(ns status-im.contexts.wallet.wallet-connect.utils.networks + (:require [clojure.edn :as edn] + [clojure.set :as set] + [clojure.string :as string] + [status-im.constants :as constants] + [status-im.contexts.wallet.common.utils.networks :as networks] + [utils.string])) + +(defn chain-id->eip155 + [chain-id] + (str "eip155:" chain-id)) + +(defn eip155->chain-id + [chain-id-str] + (-> chain-id-str + (string/split #":") + last + edn/read-string)) + +(defn format-eip155-address + [address chain-id] + (str chain-id ":" address)) + +(defn- add-full-testnet-name + "Updates the `:full-name` key with the full testnet name if using testnet `:chain-id`.\n + e.g. `{:full-name \"Mainnet\"}` -> `{:full-name \"Mainnet Sepolia\"`}`" + [network] + (let [add-testnet-name (fn [testnet-name] + (update network :full-name #(str % " " testnet-name)))] + (condp #(contains? %1 %2) (:chain-id network) + constants/sepolia-chain-ids (add-testnet-name constants/sepolia-full-name) + constants/goerli-chain-ids (add-testnet-name constants/goerli-full-name) + network))) + +(defn chain-id->network-details + [chain-id] + (-> chain-id + (networks/get-network-details) + (add-full-testnet-name))) + +(defn session-networks-allowed? + [testnet-mode? {:keys [chains]}] + (let [chain-ids (set (map (fn [chain] + (-> chain + (string/split ":") + second + js/parseInt)) + chains))] + (if testnet-mode? + (set/subset? chain-ids (set/union constants/sepolia-chain-ids constants/goerli-chain-ids)) + (set/subset? chain-ids constants/mainnet-chain-ids)))) + +(defn get-proposal-networks + [proposal] + (let [required-namespaces (get-in proposal [:params :requiredNamespaces]) + optional-namespaces (get-in proposal [:params :optionalNamespaces])] + (->> [required-namespaces optional-namespaces] + (map #(get-in % [:eip155 :chains])) + (apply concat) + (into #{})))) + +(defn proposal-networks-intersection + [proposal supported-networks] + (let [proposed-networks (get-proposal-networks proposal)] + (->> supported-networks + (filter #(->> % + chain-id->eip155 + (contains? proposed-networks)))))) + +(defn required-networks-supported? + [proposal supported-networks] + (let [supported-namespaces #{:eip155} + required-namespaces (get-in proposal [:params :requiredNamespaces])] + (when (every? #(contains? supported-namespaces %) + (keys required-namespaces)) + (let [required-networks (get-in required-namespaces [:eip155 :chains]) + supported-eip155 (set (map chain-id->eip155 supported-networks))] + (every? #(contains? supported-eip155 %) + required-networks))))) + +(defn get-networks-by-mode + [db] + (let [test-mode? (get-in db [:profile/profile :test-networks-enabled?]) + networks (get-in db [:wallet :networks (if test-mode? :test :prod)])] + (mapv #(-> % :chain-id) networks))) + +(defn event-should-be-handled? + [db {:keys [topic]}] + (let [testnet-mode? (get-in db [:profile/profile :test-networks-enabled?])] + (some #(and (= (:topic %) topic) + (session-networks-allowed? testnet-mode? %)) + (:wallet-connect/sessions db)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/rpc.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs similarity index 97% rename from src/status_im/contexts/wallet/wallet_connect/rpc.cljs rename to src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs index cfba96da43..e7e4e16429 100644 --- a/src/status_im/contexts/wallet/wallet_connect/rpc.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.wallet.wallet-connect.rpc +(ns status-im.contexts.wallet.wallet-connect.utils.rpc (:require [oops.core :as oops] [promesa.core :as promesa] [status-im.common.json-rpc.events :as rpc-events] diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/sessions.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/sessions.cljs new file mode 100644 index 0000000000..be16cbd8d7 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/utils/sessions.cljs @@ -0,0 +1,33 @@ +(ns status-im.contexts.wallet.wallet-connect.utils.sessions + (:require + [clojure.string :as string] + [utils.transforms :as transforms])) + +(defn sdk-session->db-session + [{:keys [topic expiry pairingTopic] :as session}] + {:topic topic + :expiry expiry + :sessionJson (transforms/clj->json session) + :pairingTopic pairingTopic + :name (get-in session [:peer :metadata :name]) + :iconUrl (get-in session [:peer :metadata :icons 0]) + :url (get-in session [:peer :metadata :url]) + :accounts (get-in session [:namespaces :eip155 :accounts]) + :chains (get-in session [:namespaces :eip155 :chains]) + :disconnected false}) + +(defn filter-operable-accounts + [accounts] + (filter #(and (:operable? %) + (not (:watch-only? %))) + accounts)) + +(defn filter-sessions-for-account-addresses + [account-addresses sessions] + (filter (fn [{:keys [accounts]}] + (some (fn [account] + (some (fn [account-address] + (string/includes? account account-address)) + account-addresses)) + accounts)) + sessions)) diff --git a/src/status_im/contexts/wallet/wallet_connect/signing.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/signing.cljs similarity index 77% rename from src/status_im/contexts/wallet/wallet_connect/signing.cljs rename to src/status_im/contexts/wallet/wallet_connect/utils/signing.cljs index 83f9dd3cdf..2c18298c89 100644 --- a/src/status_im/contexts/wallet/wallet_connect/signing.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/signing.cljs @@ -1,8 +1,10 @@ -(ns status-im.contexts.wallet.wallet-connect.signing +(ns status-im.contexts.wallet.wallet-connect.utils.signing (:require [native-module.core :as native-module] [promesa.core :as promesa] - [status-im.contexts.wallet.wallet-connect.core :as core] - [status-im.contexts.wallet.wallet-connect.rpc :as rpc] + [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.rpc :as rpc] [utils.hex :as hex] [utils.number :as number] [utils.transforms :as transforms])) @@ -29,7 +31,7 @@ :password password} transforms/clj->json native-module/sign-message - (promesa/then core/extract-native-call-signature))) + (promesa/then data-store/extract-native-call-signature))) (defn personal-sign [password address data] @@ -40,7 +42,7 @@ (defn eth-sign-typed-data [password address data chain-id-eip155 version] (let [legacy? (= version :v1) - chain-id (core/eip155->chain-id chain-id-eip155)] + chain-id (networks/eip155->chain-id chain-id-eip155)] (rpc/wallet-safe-sign-typed-data data address password diff --git a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/transactions.cljs similarity index 95% rename from src/status_im/contexts/wallet/wallet_connect/transactions.cljs rename to src/status_im/contexts/wallet/wallet_connect/utils/transactions.cljs index d74b5686cc..ceebd945a5 100644 --- a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/transactions.cljs @@ -1,17 +1,18 @@ -(ns status-im.contexts.wallet.wallet-connect.transactions +(ns status-im.contexts.wallet.wallet-connect.utils.transactions (:require [cljs-bean.core :as bean] [clojure.string :as string] [native-module.core :as native-module] [promesa.core :as promesa] [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.core :as core] - [status-im.contexts.wallet.wallet-connect.rpc :as rpc] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as + data-store] + [status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc] [utils.money :as money] [utils.transforms :as transforms])) (defn transaction-request? [event] - (->> (core/get-request-method event) + (->> (data-store/get-request-method event) (contains? #{constants/wallet-connect-eth-send-transaction-method constants/wallet-connect-eth-sign-transaction-method}))) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/uri.cljs similarity index 92% rename from src/status_im/contexts/wallet/wallet_connect/utils.cljs rename to src/status_im/contexts/wallet/wallet_connect/utils/uri.cljs index 0f53bcc768..1525eb72da 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/uri.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.wallet.wallet-connect.utils +(ns status-im.contexts.wallet.wallet-connect.utils.uri (:require [react-native.wallet-connect :as wallet-connect])) (defn version-supported? diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index ad46c42aa9..9489186e91 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -48,7 +48,7 @@ status-im.contexts.wallet.send.events status-im.contexts.wallet.signals status-im.contexts.wallet.swap.events - status-im.contexts.wallet.wallet-connect.events + status-im.contexts.wallet.wallet-connect.events.core [status-im.db :as db] status-im.navigation.effects status-im.navigation.events diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 5f93bde02e..4dbe474212 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -127,10 +127,11 @@ [status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal] [status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as wallet-connect-send-transaction] + [status-im.contexts.wallet.wallet-connect.modals.session-proposal.view :as + wallet-connect-session-proposal] [status-im.contexts.wallet.wallet-connect.modals.sign-message.view :as wallet-connect-sign-message] [status-im.contexts.wallet.wallet-connect.modals.sign-transaction.view :as wallet-connect-sign-transaction] - [status-im.contexts.wallet.wallet-connect.session-proposal.view :as wallet-connect-session-proposal] [status-im.navigation.options :as options] [status-im.navigation.transitions :as transitions])) diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index 3c0323dc11..8515c2a234 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -20,12 +20,12 @@ status-im.subs.wallet.activities status-im.subs.wallet.buy status-im.subs.wallet.collectibles + status-im.subs.wallet.dapps.core status-im.subs.wallet.networks status-im.subs.wallet.saved-addresses status-im.subs.wallet.send status-im.subs.wallet.swap - status-im.subs.wallet.wallet - status-im.subs.wallet.wallet-connect)) + status-im.subs.wallet.wallet)) (defn reg-root-key-sub [sub-name db-key] diff --git a/src/status_im/subs/wallet/dapps/core.cljs b/src/status_im/subs/wallet/dapps/core.cljs new file mode 100644 index 0000000000..252a3393bd --- /dev/null +++ b/src/status_im/subs/wallet/dapps/core.cljs @@ -0,0 +1,17 @@ +(ns status-im.subs.wallet.dapps.core + (:require [re-frame.core :as rf] + [status-im.contexts.wallet.common.utils :as wallet-utils] + status-im.subs.wallet.dapps.proposals + status-im.subs.wallet.dapps.requests + status-im.subs.wallet.dapps.sessions + status-im.subs.wallet.dapps.transactions + [utils.string])) + +(rf/reg-sub + :wallet-connect/account-details-by-address + :<- [:wallet/accounts-without-watched-accounts] + (fn [accounts [_ address]] + (let [{:keys [customization-color name emoji]} (wallet-utils/get-account-by-address accounts address)] + {:customization-color customization-color + :name name + :emoji emoji}))) diff --git a/src/status_im/subs/wallet/dapps/proposals.cljs b/src/status_im/subs/wallet/dapps/proposals.cljs new file mode 100644 index 0000000000..d7d95021e6 --- /dev/null +++ b/src/status_im/subs/wallet/dapps/proposals.cljs @@ -0,0 +1,45 @@ +(ns status-im.subs.wallet.dapps.proposals + (:require [re-frame.core :as rf] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as + data-store] + [utils.string])) + +(rf/reg-sub + :wallet-connect/current-proposal-request + :<- [:wallet-connect/current-proposal] + :-> :request) + +(rf/reg-sub + :wallet-connect/session-proposal-networks + :<- [:wallet-connect/current-proposal] + :-> :session-networks) + +(rf/reg-sub + :wallet-connect/session-proposer + :<- [:wallet-connect/current-proposal-request] + (fn [proposal] + (-> proposal :params :proposer))) + +(rf/reg-sub + :wallet-connect/session-proposer-name + :<- [:wallet-connect/session-proposer] + (fn [proposer] + (let [{:keys [name url]} (-> proposer :metadata)] + (data-store/compute-dapp-name name url)))) + +(rf/reg-sub + :wallet-connect/session-proposal-network-details + :<- [:wallet-connect/session-proposal-networks] + :<- [:wallet/network-details] + (fn [[session-networks network-details]] + (let [supported-networks (map :chain-id network-details) + session-networks (filterv #(contains? (set session-networks) (:chain-id %)) + network-details) + all-networks-in-session? (= (count supported-networks) (count session-networks))] + {:session-networks session-networks + :all-networks-in-session? all-networks-in-session?}))) + +(rf/reg-sub + :wallet-connect/current-proposal-address + (fn [db] + (get-in db [:wallet-connect/current-proposal :address]))) diff --git a/src/status_im/subs/wallet/wallet_connect_test.cljs b/src/status_im/subs/wallet/dapps/proposals_test.cljs similarity index 98% rename from src/status_im/subs/wallet/wallet_connect_test.cljs rename to src/status_im/subs/wallet/dapps/proposals_test.cljs index 936f48fc78..374cfcf6ff 100644 --- a/src/status_im/subs/wallet/wallet_connect_test.cljs +++ b/src/status_im/subs/wallet/dapps/proposals_test.cljs @@ -1,9 +1,9 @@ -(ns status-im.subs.wallet.wallet-connect-test +(ns status-im.subs.wallet.dapps.proposals-test (:require [cljs.test :refer [is testing]] [re-frame.db :as rf-db] status-im.subs.root - status-im.subs.wallet.wallet-connect + status-im.subs.wallet.dapps.core [test-helpers.unit :as h] [utils.re-frame :as rf])) diff --git a/src/status_im/subs/wallet/dapps/requests.cljs b/src/status_im/subs/wallet/dapps/requests.cljs new file mode 100644 index 0000000000..d78ed28d7f --- /dev/null +++ b/src/status_im/subs/wallet/dapps/requests.cljs @@ -0,0 +1,47 @@ +(ns status-im.subs.wallet.dapps.requests + (:require [re-frame.core :as rf] + [status-im.contexts.wallet.common.utils :as wallet-utils] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as + data-store] + [status-im.contexts.wallet.wallet-connect.utils.networks :as networks] + [utils.string])) + +(rf/reg-sub + :wallet-connect/current-request-address + :<- [:wallet-connect/current-request] + :-> :address) + +(rf/reg-sub + :wallet-connect/current-request-display-data + :<- [:wallet-connect/current-request] + :-> :display-data) + +(rf/reg-sub + :wallet-connect/current-request-account-details + :<- [:wallet-connect/current-request-address] + :<- [:wallet/accounts-without-watched-accounts] + (fn [[address accounts]] + (let [{:keys [customization-color name emoji]} (wallet-utils/get-account-by-address accounts address)] + {:customization-color customization-color + :name name + :emoji emoji}))) + +(rf/reg-sub + :wallet-connect/current-request-dapp + :<- [:wallet-connect/current-request] + :<- [:wallet-connect/sessions] + (fn [[request sessions]] + (data-store/get-current-request-dapp request sessions))) + +(rf/reg-sub + :wallet-connect/chain-id + :<- [:wallet-connect/current-request] + (fn [request] + (-> request + (get-in [:event :params :chainId]) + (networks/eip155->chain-id)))) + +(rf/reg-sub + :wallet-connect/current-request-network + :<- [:wallet-connect/chain-id] + networks/chain-id->network-details) diff --git a/src/status_im/subs/wallet/dapps/sessions.cljs b/src/status_im/subs/wallet/dapps/sessions.cljs new file mode 100644 index 0000000000..d57989b763 --- /dev/null +++ b/src/status_im/subs/wallet/dapps/sessions.cljs @@ -0,0 +1,25 @@ +(ns status-im.subs.wallet.dapps.sessions + (:require [clojure.string :as string] + [re-frame.core :as rf] + [status-im.contexts.wallet.wallet-connect.utils.networks :as networks] + [utils.string])) + +(rf/reg-sub + :wallet-connect/sessions-for-current-account + :<- [:wallet-connect/sessions] + :<- [:wallet/current-viewing-account-address] + (fn [[sessions address]] + (filter + (fn [{:keys [accounts]}] + (some #(string/includes? % address) accounts)) + sessions))) + +(rf/reg-sub + :wallet-connect/sessions-for-current-account-and-networks + :<- [:wallet-connect/sessions-for-current-account] + :<- [:profile/test-networks-enabled?] + (fn [[sessions testnet-mode?]] + (filter + (partial networks/session-networks-allowed? testnet-mode?) + sessions))) + diff --git a/src/status_im/subs/wallet/dapps/transactions.cljs b/src/status_im/subs/wallet/dapps/transactions.cljs new file mode 100644 index 0000000000..27490e30e3 --- /dev/null +++ b/src/status_im/subs/wallet/dapps/transactions.cljs @@ -0,0 +1,80 @@ +(ns status-im.subs.wallet.dapps.transactions + (:require [re-frame.core :as rf] + [status-im.contexts.wallet.common.utils :as wallet-utils] + [status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions] + [utils.money :as money] + [utils.string])) + +(rf/reg-sub + :wallet-connect/transaction-args + :<- [:wallet-connect/current-request] + (fn [{:keys [event transaction]}] + (when (transactions/transaction-request? event) + transaction))) + +(rf/reg-sub + :wallet-connect/transaction-suggested-fees + :<- [:wallet-connect/current-request] + (fn [{:keys [event raw-data]}] + (when (transactions/transaction-request? event) + (:suggested-fees raw-data)))) + +(rf/reg-sub + :wallet-connect/transaction-max-fees-wei + :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/transaction-suggested-fees] + (fn [[transaction suggested-fees]] + (when transaction + (let [{:keys [gasPrice gas gasLimit maxFeePerGas]} transaction + eip-1559-chain? (:eip1559Enabled suggested-fees) + gas-limit (or gasLimit gas) + max-gas-fee (if eip-1559-chain? maxFeePerGas gasPrice)] + (money/bignumber (* max-gas-fee gas-limit)))))) + +(rf/reg-sub + :wallet-connect/account-eth-token + :<- [:wallet-connect/current-request-address] + :<- [:wallet/accounts] + (fn [[address accounts]] + (let [fee-token "ETH" + find-account #(when (= (:address %) address) %) + find-token #(when (= (:symbol %) fee-token) %)] + (->> accounts + (some find-account) + :tokens + (some find-token))))) + +(rf/reg-sub + :wallet-connect/current-request-transaction-information + :<- [:wallet-connect/chain-id] + :<- [:wallet-connect/transaction-max-fees-wei] + :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/account-eth-token] + :<- [:profile/currency] + :<- [:profile/currency-symbol] + (fn [[chain-id max-fees-wei transaction eth-token currency currency-symbol]] + (when transaction + (let [max-fees-ether (money/wei->ether max-fees-wei) + max-fees-fiat (wallet-utils/calculate-token-fiat-value {:currency currency + :balance max-fees-ether + :token eth-token}) + max-fees-fiat-formatted (-> (wallet-utils/get-standard-crypto-format eth-token max-fees-ether) + (wallet-utils/get-standard-fiat-format currency-symbol + max-fees-fiat)) + balance (-> eth-token + (get-in [:balances-per-chain chain-id :raw-balance]) + money/bignumber) + tx-value (money/bignumber (:value transaction)) + total-transaction-value (money/add max-fees-wei tx-value)] + {:total-transaction-value total-transaction-value + :balance balance + :max-fees max-fees-wei + :max-fees-fiat-value max-fees-fiat + :max-fees-fiat-formatted max-fees-fiat-formatted + :error-state (cond + (not (money/sufficient-funds? tx-value balance)) + :not-enough-assets + + (not (money/sufficient-funds? total-transaction-value + balance)) + :not-enough-assets-to-pay-gas-fees)})))) diff --git a/src/status_im/subs/wallet/wallet_connect.cljs b/src/status_im/subs/wallet/wallet_connect.cljs index f9a233d222..e69de29bb2 100644 --- a/src/status_im/subs/wallet/wallet_connect.cljs +++ b/src/status_im/subs/wallet/wallet_connect.cljs @@ -1,190 +0,0 @@ -(ns status-im.subs.wallet.wallet-connect - (:require [clojure.string :as string] - [re-frame.core :as rf] - [status-im.contexts.wallet.common.utils :as wallet-utils] - [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] - [status-im.contexts.wallet.wallet-connect.transactions :as transactions] - [utils.money :as money] - [utils.string])) - -(rf/reg-sub - :wallet-connect/current-request-address - :<- [:wallet-connect/current-request] - :-> :address) - -(rf/reg-sub - :wallet-connect/current-request-display-data - :<- [:wallet-connect/current-request] - :-> :display-data) - -(rf/reg-sub - :wallet-connect/account-details-by-address - :<- [:wallet/accounts-without-watched-accounts] - (fn [accounts [_ address]] - (let [{:keys [customization-color name emoji]} (wallet-utils/get-account-by-address accounts address)] - {:customization-color customization-color - :name name - :emoji emoji}))) - -(rf/reg-sub - :wallet-connect/current-request-account-details - :<- [:wallet-connect/current-request-address] - :<- [:wallet/accounts-without-watched-accounts] - (fn [[address accounts]] - (let [{:keys [customization-color name emoji]} (wallet-utils/get-account-by-address accounts address)] - {:customization-color customization-color - :name name - :emoji emoji}))) - -(rf/reg-sub - :wallet-connect/current-request-dapp - :<- [:wallet-connect/current-request] - :<- [:wallet-connect/sessions] - (fn [[request sessions]] - (wallet-connect-core/get-current-request-dapp request sessions))) - -(rf/reg-sub - :wallet-connect/sessions-for-current-account - :<- [:wallet-connect/sessions] - :<- [:wallet/current-viewing-account-address] - (fn [[sessions address]] - (filter - (fn [{:keys [accounts]}] - (some #(string/includes? % address) accounts)) - sessions))) - -(rf/reg-sub - :wallet-connect/sessions-for-current-account-and-networks - :<- [:wallet-connect/sessions-for-current-account] - :<- [:profile/test-networks-enabled?] - (fn [[sessions testnet-mode?]] - (filter - (partial wallet-connect-core/session-networks-allowed? testnet-mode?) - sessions))) - -(rf/reg-sub - :wallet-connect/chain-id - :<- [:wallet-connect/current-request] - (fn [request] - (-> request - (get-in [:event :params :chainId]) - (wallet-connect-core/eip155->chain-id)))) - -(rf/reg-sub - :wallet-connect/current-request-network - :<- [:wallet-connect/chain-id] - wallet-connect-core/chain-id->network-details) - -(rf/reg-sub - :wallet-connect/transaction-args - :<- [:wallet-connect/current-request] - (fn [{:keys [event transaction]}] - (when (transactions/transaction-request? event) - transaction))) - -(rf/reg-sub - :wallet-connect/transaction-suggested-fees - :<- [:wallet-connect/current-request] - (fn [{:keys [event raw-data]}] - (when (transactions/transaction-request? event) - (:suggested-fees raw-data)))) - -(rf/reg-sub - :wallet-connect/transaction-max-fees-wei - :<- [:wallet-connect/transaction-args] - :<- [:wallet-connect/transaction-suggested-fees] - (fn [[transaction suggested-fees]] - (when transaction - (let [{:keys [gasPrice gas gasLimit maxFeePerGas]} transaction - eip-1559-chain? (:eip1559Enabled suggested-fees) - gas-limit (or gasLimit gas) - max-gas-fee (if eip-1559-chain? maxFeePerGas gasPrice)] - (money/bignumber (* max-gas-fee gas-limit)))))) - -(rf/reg-sub - :wallet-connect/account-eth-token - :<- [:wallet-connect/current-request-address] - :<- [:wallet/accounts] - (fn [[address accounts]] - (let [fee-token "ETH" - find-account #(when (= (:address %) address) %) - find-token #(when (= (:symbol %) fee-token) %)] - (->> accounts - (some find-account) - :tokens - (some find-token))))) - -(rf/reg-sub - :wallet-connect/current-request-transaction-information - :<- [:wallet-connect/chain-id] - :<- [:wallet-connect/transaction-max-fees-wei] - :<- [:wallet-connect/transaction-args] - :<- [:wallet-connect/account-eth-token] - :<- [:profile/currency] - :<- [:profile/currency-symbol] - (fn [[chain-id max-fees-wei transaction eth-token currency currency-symbol]] - (when transaction - (let [max-fees-ether (money/wei->ether max-fees-wei) - max-fees-fiat (wallet-utils/calculate-token-fiat-value {:currency currency - :balance max-fees-ether - :token eth-token}) - max-fees-fiat-formatted (-> (wallet-utils/get-standard-crypto-format eth-token max-fees-ether) - (wallet-utils/get-standard-fiat-format currency-symbol - max-fees-fiat)) - balance (-> eth-token - (get-in [:balances-per-chain chain-id :raw-balance]) - money/bignumber) - tx-value (money/bignumber (:value transaction)) - total-transaction-value (money/add max-fees-wei tx-value)] - {:total-transaction-value total-transaction-value - :balance balance - :max-fees max-fees-wei - :max-fees-fiat-value max-fees-fiat - :max-fees-fiat-formatted max-fees-fiat-formatted - :error-state (cond - (not (money/sufficient-funds? tx-value balance)) - :not-enough-assets - - (not (money/sufficient-funds? total-transaction-value - balance)) - :not-enough-assets-to-pay-gas-fees)})))) - -(rf/reg-sub - :wallet-connect/current-proposal-request - :<- [:wallet-connect/current-proposal] - :-> :request) - -(rf/reg-sub - :wallet-connect/session-proposal-networks - :<- [:wallet-connect/current-proposal] - :-> :session-networks) - -(rf/reg-sub - :wallet-connect/session-proposer - :<- [:wallet-connect/current-proposal-request] - (fn [proposal] - (-> proposal :params :proposer))) - -(rf/reg-sub - :wallet-connect/session-proposer-name - :<- [:wallet-connect/session-proposer] - (fn [proposer] - (let [{:keys [name url]} (-> proposer :metadata)] - (wallet-connect-core/compute-dapp-name name url)))) - -(rf/reg-sub - :wallet-connect/session-proposal-network-details - :<- [:wallet-connect/session-proposal-networks] - :<- [:wallet/network-details] - (fn [[session-networks network-details]] - (let [supported-networks (map :chain-id network-details) - session-networks (filterv #(contains? (set session-networks) (:chain-id %)) - network-details) - all-networks-in-session? (= (count supported-networks) (count session-networks))] - {:session-networks session-networks - :all-networks-in-session? all-networks-in-session?}))) - -(rf/reg-sub - :wallet-connect/current-proposal-address - (fn [db] - (get-in db [:wallet-connect/current-proposal :address])))