diff --git a/src/mocks/js_dependencies.cljs b/src/mocks/js_dependencies.cljs index 6e94615cc6..90407edca4 100644 --- a/src/mocks/js_dependencies.cljs +++ b/src/mocks/js_dependencies.cljs @@ -418,6 +418,7 @@ "react-native-transparent-video" react-native-transparent-video "react-native-orientation-locker" react-native-orientation-locker "react-native-gifted-charts" react-native-gifted-charts + "@walletconnect/react-native-compat" #js {} "../resources/data/emojis/en.json" (js/JSON.parse (slurp "./resources/data/emojis/en.json")) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index b1c9d5086d..99dc1121f0 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -257,6 +257,12 @@ (def regx-starts-with-uuid #"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") (def regx-full-or-partial-address #"^0x[a-fA-F0-9]{1,40}$") +;; Wallet Connect +(def ^:const optimism-crosschain-id "eip155:10") +(def ^:const wallet-connect-supported-methods ["eth_sendTransaction" "personal_sign"]) +(def ^:const wallet-connect-supported-events ["accountsChanged" "chainChanged"]) +(def ^:const wallet-connect-session-proposal-event "session_proposal") + (def ^:const dapp-permission-contact-code "contact-code") (def ^:const dapp-permission-web3 "web3") (def ^:const dapp-permission-qr-code "qr-code") diff --git a/src/status_im/contexts/profile/login/events.cljs b/src/status_im/contexts/profile/login/events.cljs index 444bad8753..839d191433 100644 --- a/src/status_im/contexts/profile/login/events.cljs +++ b/src/status_im/contexts/profile/login/events.cljs @@ -8,6 +8,7 @@ [status-im.constants :as constants] status-im.contexts.profile.login.effects [status-im.contexts.profile.rpc :as profile.rpc] + [status-im.feature-flags :as ff] [taoensso.timbre :as log] [utils.i18n :as i18n] [utils.re-frame :as rf] @@ -104,6 +105,8 @@ [:chat.ui/request-link-preview-whitelist] [:visibility-status-updates/fetch] [:switcher-cards/fetch] + (when (ff/enabled? ::ff/wallet.wallet-connect) + [:dispatch [:wallet-connect/init]]) (when-not (:universal-links/handling db) [:effects.chat/open-last-chat key-uid]) (when notifications-enabled? diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs new file mode 100644 index 0000000000..7d2f27089f --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -0,0 +1,41 @@ +(ns status-im.contexts.wallet.wallet-connect.effects + (:require [promesa.core :as promesa] + [re-frame.core :as rf] + [status-im.contexts.wallet.wallet-connect.utils :as wallet-connect.utils])) + +(rf/reg-fx + :effects.wallet-connect/init + (fn [{:keys [on-success on-fail]}] + (-> (wallet-connect.utils/init) + (promesa/then on-success) + (promesa/catch on-fail)))) + +(rf/reg-fx + :effects.wallet-connect/register-event-listener + (fn [web3-wallet wc-event handler] + (.on web3-wallet + wc-event + (fn [js-proposal] + (-> js-proposal + (js->clj :keywordize-keys true) + handler))))) + +(rf/reg-fx + :effects.wallet-connect/pair + (fn [{:keys [web3-wallet url on-success on-fail]}] + (-> (.. web3-wallet -core -pairing) + (.pair (clj->js {:uri url})) + (promesa/then on-success) + (promesa/catch on-fail)))) + +(rf/reg-fx + :effects.wallet-connect/approve-session + (fn [{:keys [web3-wallet proposal supported-namespaces on-success on-fail]}] + (let [{:keys [params id]} proposal + approved-namespaces (wallet-connect.utils/build-approved-namespaces params + supported-namespaces)] + (-> (.approveSession web3-wallet + (clj->js {:id id + :namespaces approved-namespaces})) + (promesa/then on-success) + (promesa/catch on-fail))))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs new file mode 100644 index 0000000000..f133fed86d --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -0,0 +1,89 @@ +(ns status-im.contexts.wallet.wallet-connect.events + (:require [re-frame.core :as rf] + [status-im.constants :as constants] + status-im.contexts.wallet.wallet-connect.effects + [status-im.contexts.wallet.wallet-connect.utils :as wallet-connect.utils] + [taoensso.timbre :as log])) + +(rf/reg-event-fx + :wallet-connect/init + (fn [] + {:fx [[:effects.wallet-connect/init + {:on-success #(rf/dispatch [:wallet-connect/on-init-success %]) + :on-fail #(rf/dispatch [:wallet-connect/on-init-fail %])}]]})) + +(rf/reg-event-fx + :wallet-connect/on-init-success + (fn [{:keys [db]} [web3-wallet]] + {:db (assoc db :wallet-connect/web3-wallet web3-wallet) + :fx [[:dispatch [:wallet-connect/register-event-listeners]]]})) + +(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 %])]]}))) + +(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]] + {:db (assoc db :wallet-connect/current-proposal proposal)})) + +(rf/reg-event-fx + :wallet-connect/reset-current-session + (fn [{:keys [db]}] + {:db (dissoc db :wallet-connect/current-proposal)})) + +(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") + :on-success #(log/info "dApp paired successfully")}]]}))) + +(rf/reg-event-fx + :wallet-connect/approve-session + (fn [{:keys [db]}] + ;; NOTE: hardcoding optimism for the base implementation + (let [crosschain-ids [constants/optimism-crosschain-id] + web3-wallet (get db :wallet-connect/web3-wallet) + current-proposal (get db :wallet-connect/current-proposal) + accounts (get-in db [:wallet :accounts]) + ;; NOTE: for now using the first account, but should be using the account selected by the + ;; user on the connection screen. The default would depend on where the connection started + ;; from: + ;; - global scanner -> first account in list + ;; - wallet account dapps -> account that is selected + address (-> accounts keys first) + formatted-address (wallet-connect.utils/format-address (first crosschain-ids) + address) + supported-namespaces (clj->js {:eip155 + {:chains crosschain-ids + :methods constants/wallet-connect-supported-methods + :events constants/wallet-connect-supported-events + :accounts [formatted-address]}})] + {:fx [[:effects.wallet-connect/approve-session + {:web3-wallet web3-wallet + :proposal current-proposal + :supported-namespaces supported-namespaces + :on-success (fn [] + (log/debug "Wallet Connect session approved") + (rf/dispatch [:wallet-connect/reset-current-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]))}]]}))) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils.cljs b/src/status_im/contexts/wallet/wallet_connect/utils.cljs index 73c5cd8a64..8cf5952d67 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils.cljs @@ -1,6 +1,10 @@ (ns status-im.contexts.wallet.wallet-connect.utils - (:require ["@walletconnect/core" :refer [Core]] + ;; NOTE: Not sorting namespaces since @walletconnect/react-native-compat should be the first + #_{:clj-kondo/ignore [:unsorted-required-namespaces]} + (:require ["@walletconnect/react-native-compat"] + ["@walletconnect/core" :refer [Core]] ["@walletconnect/web3wallet" :refer [Web3Wallet]] + ["@walletconnect/utils" :refer [buildApprovedNamespaces]] [status-im.config :as config] [utils.i18n :as i18n])) @@ -23,3 +27,13 @@ (Web3Wallet.init (clj->js {:core core :metadata wallet-connect-metadata})))) + +(defn build-approved-namespaces + [proposal supported-namespaces] + (buildApprovedNamespaces + (clj->js {:proposal proposal + :supportedNamespaces supported-namespaces}))) + +(defn format-address + [chain-id address] + (str chain-id ":" address)) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index a897316b17..b66ec418ed 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -40,6 +40,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.db :as db] status-im.navigation.effects status-im.navigation.events diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index a0831c1944..114b85c150 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -165,6 +165,10 @@ ;;wallet (reg-root-key-sub :wallet :wallet) +;;wallet-connect +(reg-root-key-sub :wallet-connect/web3-wallet :wallet-connect/web3-wallet) +(reg-root-key-sub :wallet-connect/current-proposal :wallet-connect/current-proposal) + ;;biometrics (reg-root-key-sub :biometrics :biometrics)