[#20150] feat: wallet connect pairing via deep links (#21050)

* [#20150] feat: wallet connect pairing via deep links

* [#20150] feat: redirect to the dapp after finish response

* [#20150] test: add tests for events and utils

* [#20150] fix: reviewer's feedback
This commit is contained in:
Mohsen 2024-09-03 10:52:17 +03:00 committed by GitHub
parent e61702c8bb
commit 4a8bb704bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 141 additions and 27 deletions

View File

@ -38,9 +38,12 @@
[path] [path]
(map #(str % path) status-web-urls)) (map #(str % path) status-web-urls))
(def handled-schemes (set (into uri-schemes status-web-urls))) (def handled-schemes (set (into uri-schemes status-web-urls)))
(defn wc-normalize-uri
[uri]
(if (string/includes? uri "//wc:") (string/replace-first uri "/?" "?") uri))
(def group-chat-extractor (def group-chat-extractor
{[#"(.*)" :params] {"" :group-chat {[#"(.*)" :params] {"" :group-chat
"/" :group-chat}}) "/" :group-chat}})
@ -60,7 +63,9 @@
["wallet/" :account] :wallet-account ["wallet/" :account] :wallet-account
[user-with-data-path :user-data] :user [user-with-data-path :user-data] :user
"c" :community "c" :community
"u" :user} "u" :user
#"wc:([^\?]+)" :wallet-connect
"wc" :wallet-connect}
ethereum-scheme eip-extractor}]) ethereum-scheme eip-extractor}])
(defn parse-query-params (defn parse-query-params
@ -75,6 +80,11 @@
(when-not (string/blank? fragment) (when-not (string/blank? fragment)
fragment))) fragment)))
(defn remove-scheme
[uri]
(let [[_ url] (string/split uri #"://")]
url))
(defn match-uri (defn match-uri
[uri] [uri]
(let [;; bidi has trouble parse path with `=` in it extract `=` here and add back to parsed (let [;; bidi has trouble parse path with `=` in it extract `=` here and add back to parsed
@ -88,13 +98,17 @@
uri-without-equal-in-path uri-without-equal-in-path
(if equal-end-of-base64url (string/replace-first uri equal-end-of-base64url "") uri) (if equal-end-of-base64url (string/replace-first uri equal-end-of-base64url "") uri)
;;bidi has issue to detect wc regex with /?, couldn't fine any other workaround
normalize-uri
(wc-normalize-uri uri-without-equal-in-path)
;; fragment is the one after `#`, usually user-id, ens-name, community-id ;; fragment is the one after `#`, usually user-id, ens-name, community-id
fragment (parse-fragment uri) fragment (parse-fragment uri)
ens? (utils.ens/is-valid-eth-name? fragment) ens? (utils.ens/is-valid-eth-name? fragment)
compressed-key? (validators/valid-compressed-key? fragment) compressed-key? (validators/valid-compressed-key? fragment)
{:keys [handler route-params] :as parsed} {:keys [handler route-params query-params] :as parsed}
(assoc (bidi/match-route routes uri-without-equal-in-path) (assoc (bidi/match-route routes normalize-uri)
:uri uri :uri uri
:query-params (parse-query-params uri))] :query-params (parse-query-params uri))]
(cond-> parsed (cond-> parsed
@ -131,7 +145,13 @@
(update-in [:route-params :user-data] #(str % equal-end-of-base64url)) (update-in [:route-params :user-data] #(str % equal-end-of-base64url))
(and (= handler :user) compressed-key?) (and (= handler :user) compressed-key?)
(assoc-in [:route-params :user-id] fragment)))) (assoc-in [:route-params :user-id] fragment)
(= handler :wallet-connect)
(assoc-in [:route-params :uri]
(if (get query-params "uri")
(get query-params "uri")
(remove-scheme normalize-uri))))))
(defn match-contact-async (defn match-contact-async
[chain {:keys [user-id ens-name]} callback] [chain {:keys [user-id ens-name]} callback]
@ -328,6 +348,10 @@
(= handler :wallet-account) (= handler :wallet-account)
(cb (match-wallet-account route-params)) (cb (match-wallet-account route-params))
(= handler :wallet-connect)
(cb {:type :wallet-connect
:uri (:uri route-params)})
(address/address? uri) (address/address? uri)
(cb (address->eip681 uri)) (cb (address->eip681 uri))

View File

@ -105,6 +105,12 @@
[full-url] [full-url]
(log/info "universal-links: no handler for " full-url)) (log/info "universal-links: no handler for " full-url))
(rf/defn handle-wallet-connect
[_ uri]
(log/info "universal-links: handle-wallet-connect" uri)
{:dispatch-later {:ms 1500
:dispatch [:wallet-connect/on-scan-connection uri]}})
(defn dispatch-url (defn dispatch-url
"Dispatch url so we can get access to re-frame/db" "Dispatch url so we can get access to re-frame/db"
[url] [url]
@ -117,7 +123,7 @@
(rf/defn on-handle (rf/defn on-handle
{:events [:universal-links/match-value]} {:events [:universal-links/match-value]}
[cofx url {:keys [type chat-id community-id] :as data}] [cofx url {:keys [type chat-id community-id uri] :as data}]
(case type (case type
:group-chat (handle-group-chat cofx data) :group-chat (handle-group-chat cofx data)
:private-chat (handle-private-chat cofx data) :private-chat (handle-private-chat cofx data)
@ -130,6 +136,7 @@
:browser (handle-browse cofx data) :browser (handle-browse cofx data)
:eip681 (handle-eip681 cofx data) :eip681 (handle-eip681 cofx data)
:wallet-account (handle-wallet-account cofx data) :wallet-account (handle-wallet-account cofx data)
:wallet-connect (handle-wallet-connect cofx uri)
(handle-not-found url))) (handle-not-found url)))
(rf/defn route-url (rf/defn route-url

View File

@ -52,6 +52,19 @@
[proposal] [proposal]
(get-in proposal [:params :proposer :metadata])) (get-in proposal [:params :proposer :metadata]))
(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 (defn get-db-current-request-params
[db] [db]
(-> db (-> db

View File

@ -0,0 +1,27 @@
(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))))))

View File

@ -317,13 +317,17 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/persist-session :wallet-connect/persist-session
(fn [_ [session-info]] (fn [_ [session-info]]
{:fx [[:json-rpc/call (let [redirect-url (-> session-info
[{:method "wallet_addWalletConnectSession" (js->clj :keywordize-keys true)
:params [(js/JSON.stringify session-info)] (wallet-connect-core/get-dapp-redirect-url))]
:on-success (fn [] {:fx [[:json-rpc/call
(log/info "Wallet Connect session persisted") [{:method "wallet_addWalletConnectSession"
(rf/dispatch [:wallet-connect/fetch-persisted-sessions])) :params [(js/JSON.stringify session-info)]
:on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})) :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 (rf/reg-event-fx
:wallet-connect/disconnect-session :wallet-connect/disconnect-session

View File

@ -5,7 +5,8 @@
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] [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 :as wc-utils]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.i18n :as i18n])) [utils.i18n :as i18n]
[utils.transforms :as transforms]))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/respond-current-session :wallet-connect/respond-current-session
@ -132,8 +133,21 @@
:event :wallet-connect/send-response :event :wallet-connect/send-response
:wallet-connect-event event})) :wallet-connect-event event}))
:on-success (fn [] :on-success (fn []
(rf/dispatch [:wallet-connect/redirect-to-dapp])
(log/info "Successfully sent Wallet Connect response to dApp"))}]]})))) (log/info "Successfully sent Wallet Connect response to dApp"))}]]}))))
(rf/reg-event-fx
:wallet-connect/redirect-to-dapp
(fn [{:keys [db]} [url]]
(let [redirect-url (or url
(->> (get db :wallet-connect/current-request)
(wallet-connect-core/get-current-request-dapp
(get db :wallet-connect/sessions))
:sessionJson
transforms/json->clj
wallet-connect-core/get-dapp-redirect-url))]
{:fx [[:open-url redirect-url]]})))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/dismiss-request-modal :wallet-connect/dismiss-request-modal
(fn [{:keys [db]} _] (fn [{:keys [db]} _]
@ -143,12 +157,6 @@
wallet-connect-core/method-to-screen)] wallet-connect-core/method-to-screen)]
{:fx [[:dispatch [:dismiss-modal screen]]]}))) {:fx [[:dispatch [:dismiss-modal screen]]]})))
(rf/reg-event-fx
:wallet-connect/finish-session-request
(fn [_ [result]]
{:fx [[:dispatch [:wallet-connect/send-response {:result result}]]
[:dispatch [:wallet-connect/dismiss-request-modal]]]}))
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/dismiss-request-modal :wallet-connect/dismiss-request-modal
(fn [{:keys [db]} _] (fn [{:keys [db]} _]

View File

@ -0,0 +1,27 @@
(ns status-im.contexts.wallet.wallet-connect.responding-events-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
[test-helpers.unit :as h]
[utils.transforms :as transforms]))
(h/deftest-event :wallet-connect/redirect-to-dapp
[event-id dispatch]
(let [current-request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}}
session-json "{\"peer\":{\"metadata\":{\"redirect\":{\"native\":\"native://redirect-url\"}}}}"
sessions [{:url "https://dapp.com"
: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
(fn [_ _] (first sessions))
transforms/json->clj
(fn [json] (js/JSON.parse json))
wallet-connect-core/get-dapp-redirect-url
(fn [_] "native://redirect-url")]
(is (match? {:fx [[:open-url "native://redirect-url"]]}
(dispatch [event-id]))))))

View File

@ -1,11 +1,14 @@
(ns status-im.navigation.events (ns status-im.navigation.events
(:require (:require
[clojure.string :as string]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[react-native.core :as react]
[status-im.contexts.shell.jump-to.events :as shell.events] [status-im.contexts.shell.jump-to.events :as shell.events]
[status-im.contexts.shell.jump-to.state :as shell.state] [status-im.contexts.shell.jump-to.state :as shell.state]
[status-im.contexts.shell.jump-to.utils :as shell.utils] [status-im.contexts.shell.jump-to.utils :as shell.utils]
[status-im.feature-flags :as ff] [status-im.feature-flags :as ff]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[utils.url :as url]))
(defn- all-screens-params (defn- all-screens-params
[db view screen-params] [db view screen-params]
@ -142,3 +145,9 @@
{:fx [[:effects.share/open config]]}) {:fx [[:effects.share/open config]]})
(rf/reg-event-fx :open-share open-share) (rf/reg-event-fx :open-share open-share)
(rf/reg-fx
:open-url
(fn [url]
(when (not (string/blank? url))
(.openURL ^js react/linking (url/normalize-url url)))))

View File

@ -41,12 +41,7 @@
:<- [:wallet-connect/current-request] :<- [:wallet-connect/current-request]
:<- [:wallet-connect/sessions] :<- [:wallet-connect/sessions]
(fn [[request sessions]] (fn [[request sessions]]
(let [dapp-url (get-in request [:event :verifyContext :verified :origin])] (wallet-connect-core/get-current-request-dapp request sessions)))
(->> sessions
(filter (fn [session]
(= (utils.string/remove-trailing-slash dapp-url)
(utils.string/remove-trailing-slash (get session :url)))))
(first)))))
(rf/reg-sub (rf/reg-sub
:wallet-connect/sessions-for-current-account :wallet-connect/sessions-for-current-account