Restructure wallet-connect namespaces (#21167)

Co-authored-by: Mohsen <maxghafori@gmail.com>
This commit is contained in:
Lungu Cristian 2024-09-10 15:59:18 +03:00 committed by GitHub
parent d7242f1c70
commit 9135e915ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 903 additions and 863 deletions

View File

@ -6,7 +6,7 @@
[status-im.common.scan-qr-code.view :as scan-qr-code] [status-im.common.scan-qr-code.view :as scan-qr-code]
[status-im.common.validation.general :as validators] [status-im.common.validation.general :as validators]
[status-im.contexts.communities.events] [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] [status-im.feature-flags :as ff]
[utils.address :as utils-address] [utils.address :as utils-address]
[utils.debounce :as debounce] [utils.debounce :as debounce]
@ -100,7 +100,7 @@
nil nil
(and (and
(wc-utils/valid-uri? scanned-text) (wc-uri/valid-uri? scanned-text)
(ff/enabled? ::ff/wallet.wallet-connect)) (ff/enabled? ::ff/wallet.wallet-connect))
(handle-wallet-connect scanned-text) (handle-wallet-connect scanned-text)

View File

@ -9,7 +9,7 @@
[status-im.common.resources :as resources] [status-im.common.resources :as resources]
[status-im.contexts.wallet.connected-dapps.disconnect-dapp.view :as disconnect-dapp] [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.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.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.string])) [utils.string]))
@ -107,9 +107,14 @@
:content-container-style (style/dapps-list theme) :content-container-style (style/dapps-list theme)
:render-fn (fn [{:keys [topic pairingTopic name url iconUrl]}] :render-fn (fn [{:keys [topic pairingTopic name url iconUrl]}]
[quo/dapp [quo/dapp
{:dapp {:avatar (core/compute-dapp-icon-path iconUrl {:dapp {:avatar
url) (data-store/compute-dapp-icon-path
:name (core/compute-dapp-name name url) iconUrl
url)
:name
(data-store/compute-dapp-name
name
url)
:value url :value url
:topic topic :topic topic
:pairing-topic pairingTopic :pairing-topic pairingTopic

View File

@ -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))))

View File

@ -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))))))

View File

@ -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)}]]]}))

View File

@ -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)}]]]}))

View File

@ -1,12 +1,12 @@
(ns status-im.contexts.wallet.wallet-connect.effects (ns status-im.contexts.wallet.wallet-connect.events.effects
(:require (:require
[promesa.core :as promesa] [promesa.core :as promesa]
[re-frame.core :as rf] [re-frame.core :as rf]
[react-native.wallet-connect :as wallet-connect] [react-native.wallet-connect :as wallet-connect]
[status-im.config :as config] [status-im.config :as config]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.signing :as signing] [status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
[status-im.contexts.wallet.wallet-connect.transactions :as transactions] [status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.security.core :as security])) [utils.security.core :as security]))

View File

@ -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]]]}))))

View File

@ -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] (:require [cljs-bean.core :as bean]
[clojure.string :as string] [clojure.string :as string]
[native-module.core :as native-module] [native-module.core :as native-module]
[re-frame.core :as rf] [re-frame.core :as rf]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] [status-im.contexts.wallet.wallet-connect.utils.data-store :as
[status-im.contexts.wallet.wallet-connect.signing :as signing] data-store]
[status-im.contexts.wallet.wallet-connect.transactions :as transactions] [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] [taoensso.timbre :as log]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
@ -15,8 +17,8 @@
:wallet-connect/show-request-modal :wallet-connect/show-request-modal
(fn [{:keys [db]}] (fn [{:keys [db]}]
(let [event (get-in db [:wallet-connect/current-request :event]) (let [event (get-in db [:wallet-connect/current-request :event])
method (wallet-connect-core/get-request-method event) method (data-store/get-request-method event)
screen (wallet-connect-core/method-to-screen method)] screen (data-store/method-to-screen method)]
(if screen (if screen
{:fx [[:dispatch [:open-modal screen]]]} {:fx [[:dispatch [:open-modal screen]]]}
(log/error "Didn't find screen for Wallet Connect method" (log/error "Didn't find screen for Wallet Connect method"
@ -25,7 +27,7 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/process-session-request :wallet-connect/process-session-request
(fn [{:keys [db]} [event]] (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])] existing-event (get-in db [:wallet-connect/current-request :event])]
(log/info "Processing Wallet Connect session request" method) (log/info "Processing Wallet Connect session request" method)
;; NOTE: make sure we don't show two requests at the same time ;; NOTE: make sure we don't show two requests at the same time
@ -55,7 +57,7 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/process-personal-sign :wallet-connect/process-personal-sign
(fn [{:keys [db]}] (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)] parsed-data (native-module/hex-to-utf8 raw-data)]
{:db (update-in db {:db (update-in db
[:wallet-connect/current-request] [:wallet-connect/current-request]
@ -68,7 +70,7 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/process-eth-sign :wallet-connect/process-eth-sign
(fn [{:keys [db]}] (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)] parsed-data (native-module/hex-to-utf8 raw-data)]
{:db (update-in db {:db (update-in db
[:wallet-connect/current-request] [:wallet-connect/current-request]
@ -98,11 +100,11 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/process-eth-send-transaction :wallet-connect/process-eth-send-transaction
(fn [{:keys [db]}] (fn [{:keys [db]}]
(let [event (wallet-connect-core/get-db-current-request-event db) (let [event (data-store/get-db-current-request-event db)
tx (-> event wallet-connect-core/get-request-params first) tx (-> event data-store/get-request-params first)
chain-id (-> event chain-id (-> event
(get-in [:params :chainId]) (get-in [:params :chainId])
wallet-connect-core/eip155->chain-id)] networks/eip155->chain-id)]
{:fx [[:effects.wallet-connect/prepare-transaction {:fx [[:effects.wallet-connect/prepare-transaction
{:tx tx {:tx tx
:chain-id chain-id :chain-id chain-id
@ -112,11 +114,11 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet-connect/process-eth-sign-transaction :wallet-connect/process-eth-sign-transaction
(fn [{:keys [db]}] (fn [{:keys [db]}]
(let [event (wallet-connect-core/get-db-current-request-event db) (let [event (data-store/get-db-current-request-event db)
tx (-> event wallet-connect-core/get-request-params first) tx (-> event data-store/get-request-params first)
chain-id (-> event chain-id (-> event
(get-in [:params :chainId]) (get-in [:params :chainId])
wallet-connect-core/eip155->chain-id)] networks/eip155->chain-id)]
{:fx [[:effects.wallet-connect/prepare-transaction {:fx [[:effects.wallet-connect/prepare-transaction
{:tx tx {:tx tx
:chain-id chain-id :chain-id chain-id
@ -127,11 +129,11 @@
:wallet-connect/process-sign-typed :wallet-connect/process-sign-typed
(fn [{:keys [db]}] (fn [{:keys [db]}]
(try (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) 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]) (get-in [:params :chainId])
wallet-connect-core/eip155->chain-id) networks/eip155->chain-id)
data-chain-id (-> parsed-raw-data data-chain-id (-> parsed-raw-data
transforms/js->clj transforms/js->clj
signing/typed-data-chain-id) signing/typed-data-chain-id)
@ -156,20 +158,20 @@
[:wallet-connect/on-processing-error [:wallet-connect/on-processing-error
(ex-info "Failed to parse JSON typed data" (ex-info "Failed to parse JSON typed data"
{:error err {: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 (rf/reg-event-fx
:wallet-connect/wrong-typed-data-chain-id :wallet-connect/wrong-typed-data-chain-id
(fn [_ [{:keys [expected-chain-id wrong-chain-id]}]] (fn [_ [{:keys [expected-chain-id wrong-chain-id]}]]
(let [wrong-network-name (-> wrong-chain-id (let [wrong-network-name (-> wrong-chain-id
wallet-connect-core/chain-id->network-details networks/chain-id->network-details
:full-name) :full-name)
expected-network-name (-> expected-chain-id expected-network-name (-> expected-chain-id
wallet-connect-core/chain-id->network-details networks/chain-id->network-details
:full-name) :full-name)
toast-message (i18n/label :t/wallet-connect-typed-data-wrong-chain-id-warning toast-message (i18n/label :t/wallet-connect-typed-data-wrong-chain-id-warning
{:wrong-chain (or wrong-network-name {:wrong-chain (or wrong-network-name
(wallet-connect-core/chain-id->eip155 (networks/chain-id->eip155
wrong-chain-id)) wrong-chain-id))
:expected-chain expected-network-name})] :expected-chain expected-network-name})]
{:fx [[:dispatch {:fx [[:dispatch
@ -186,7 +188,7 @@
:wallet-connect/on-processing-error :wallet-connect/on-processing-error
(fn [{:keys [db]} [error]] (fn [{:keys [db]} [error]]
(let [{:keys [address event]} (get db :wallet-connect/current-request) (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" (log/error "Failed to process Wallet Connect request"
{:error error {:error error
:address address :address address

View File

@ -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] (:require [re-frame.core :as rf]
[react-native.wallet-connect :as wallet-connect] [react-native.wallet-connect :as wallet-connect]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] [status-im.contexts.wallet.wallet-connect.utils.data-store :as
[status-im.contexts.wallet.wallet-connect.utils :as wc-utils] data-store]
[status-im.contexts.wallet.wallet-connect.utils.uri :as uri]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
@ -12,10 +13,10 @@
:wallet-connect/respond-current-session :wallet-connect/respond-current-session
(fn [{:keys [db]} [password]] (fn [{:keys [db]} [password]]
(let [event (get-in db [:wallet-connect/current-request :event]) (let [event (get-in db [:wallet-connect/current-request :event])
method (wallet-connect-core/get-request-method event) method (data-store/get-request-method event)
screen (wallet-connect-core/method-to-screen method) screen (data-store/method-to-screen method)
expiry (get-in event [:params :request :expiryTimestamp])] expiry (get-in event [:params :request :expiryTimestamp])]
(if (wc-utils/timestamp-expired? expiry) (if (uri/timestamp-expired? expiry)
{:fx [[:dispatch {:fx [[:dispatch
[:toasts/upsert [:toasts/upsert
{:id :new-wallet-account-created {:id :new-wallet-account-created
@ -99,7 +100,7 @@
:wallet-connect/on-sign-error :wallet-connect/on-sign-error
(fn [{:keys [db]} [error]] (fn [{:keys [db]} [error]]
(let [{:keys [raw-data address event]} (get db :wallet-connect/current-request) (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" (log/error "Failed to sign Wallet Connect request"
{:error error {:error error
:address address :address address
@ -118,7 +119,7 @@
(fn [{:keys [db]} [{:keys [request result error]}]] (fn [{:keys [db]} [{:keys [request result error]}]]
(when-let [{:keys [id topic] :as event} (or request (when-let [{:keys [id topic] :as event} (or request
(get-in db [:wallet-connect/current-request :event]))] (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)] web3-wallet (get db :wallet-connect/web3-wallet)]
{:db (assoc-in db [:wallet-connect/current-request :response-sent?] true) {:db (assoc-in db [:wallet-connect/current-request :response-sent?] true)
:fx [[:effects.wallet-connect/respond-session-request :fx [[:effects.wallet-connect/respond-session-request
@ -142,11 +143,11 @@
(fn [{:keys [db]} [url]] (fn [{:keys [db]} [url]]
(let [redirect-url (or url (let [redirect-url (or url
(->> (get db :wallet-connect/current-request) (->> (get db :wallet-connect/current-request)
(wallet-connect-core/get-current-request-dapp (data-store/get-current-request-dapp
(get db :wallet-connect/sessions)) (get db :wallet-connect/sessions))
:sessionJson :sessionJson
transforms/json->clj transforms/json->clj
wallet-connect-core/get-dapp-redirect-url))] data-store/get-dapp-redirect-url))]
{:fx [[:open-url redirect-url]]}))) {:fx [[:open-url redirect-url]]})))
(rf/reg-event-fx (rf/reg-event-fx
@ -154,17 +155,8 @@
(fn [{:keys [db]} _] (fn [{:keys [db]} _]
(let [screen (-> db (let [screen (-> db
(get-in [:wallet-connect/current-request :event]) (get-in [:wallet-connect/current-request :event])
wallet-connect-core/get-request-method data-store/get-request-method
wallet-connect-core/method-to-screen)] data-store/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)]
{:fx [[:dispatch [:dismiss-modal screen]]]}))) {:fx [[:dispatch [:dismiss-modal screen]]]})))
(rf/reg-event-fx (rf/reg-event-fx

View File

@ -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 (:require
[cljs.test :refer-macros [is]] [cljs.test :refer-macros [is]]
matcher-combinators.test matcher-combinators.test
[re-frame.db :as rf-db] [re-frame.db :as rf-db]
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] status-im.contexts.wallet.wallet-connect.events.session-responses
status-im.contexts.wallet.wallet-connect.responding-events [status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store]
[test-helpers.unit :as h] [test-helpers.unit :as h]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
@ -16,11 +16,11 @@
:sessionJson session-json}]] :sessionJson session-json}]]
(reset! rf-db/app-db {:wallet-connect {:current-request current-request (reset! rf-db/app-db {:wallet-connect {:current-request current-request
:sessions sessions}}) :sessions sessions}})
(with-redefs [wallet-connect-core/get-current-request-dapp (with-redefs [data-store/get-current-request-dapp
(fn [_ _] (first sessions)) (fn [_ _] (first sessions))
transforms/json->clj transforms/json->clj
(fn [json] (js/JSON.parse json)) (fn [json] (js/JSON.parse json))
wallet-connect-core/get-dapp-redirect-url data-store/get-dapp-redirect-url
(fn [_] "native://redirect-url")] (fn [_] "native://redirect-url")]
(is (match? {:fx [[:open-url "native://redirect-url"]]} (is (match? {:fx [[:open-url "native://redirect-url"]]}

View File

@ -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" %)}]]]}))

View File

@ -2,14 +2,15 @@
(:require [clojure.string :as string] (:require [clojure.string :as string]
[quo.core :as quo] [quo.core :as quo]
[react-native.core :as rn] [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 (defn view
[{:keys [label dapp account]}] [{:keys [label dapp account]}]
[rn/view {:style style/header-container} [rn/view {:style style/header-container}
(let [{:keys [name iconUrl url]} dapp (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} [rn/view {:style style/dapp-container}
[quo/summary-tag [quo/summary-tag
{:type :dapp {:type :dapp

View File

@ -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])) (:require [quo.foundations.colors :as colors]))
(def dapp-avatar (def dapp-avatar

View File

@ -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 (:require
[clojure.string :as string] [clojure.string :as string]
[quo.core :as quo] [quo.core :as quo]
@ -6,8 +6,8 @@
[quo.theme] [quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[status-im.common.floating-button-page.view :as floating-button-page] [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.modals.session-proposal.style :as style]
[status-im.contexts.wallet.wallet-connect.session-proposal.style :as style] [status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.string])) [utils.string]))
@ -17,8 +17,8 @@
(let [proposer (rf/sub [:wallet-connect/session-proposer]) (let [proposer (rf/sub [:wallet-connect/session-proposer])
{:keys [icons name url]} (:metadata proposer) {:keys [icons name url]} (:metadata proposer)
first-icon (first icons) first-icon (first icons)
dapp-name (wallet-connect-core/compute-dapp-name name url) dapp-name (data-store/compute-dapp-name name url)
profile-picture (wallet-connect-core/compute-dapp-icon-path first-icon url)] profile-picture (data-store/compute-dapp-icon-path first-icon url)]
[:<> [:<>
[rn/view {:style style/dapp-avatar} [rn/view {:style style/dapp-avatar}
[quo/user-avatar [quo/user-avatar

View File

@ -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))

View File

@ -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))))))

View File

@ -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))))

View File

@ -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] (:require [oops.core :as oops]
[promesa.core :as promesa] [promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events] [status-im.common.json-rpc.events :as rpc-events]

View File

@ -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))

View File

@ -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] (:require [native-module.core :as native-module]
[promesa.core :as promesa] [promesa.core :as promesa]
[status-im.contexts.wallet.wallet-connect.core :as core] [status-im.contexts.wallet.wallet-connect.utils.data-store :as
[status-im.contexts.wallet.wallet-connect.rpc :as rpc] 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.hex :as hex]
[utils.number :as number] [utils.number :as number]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
@ -29,7 +31,7 @@
:password password} :password password}
transforms/clj->json transforms/clj->json
native-module/sign-message native-module/sign-message
(promesa/then core/extract-native-call-signature))) (promesa/then data-store/extract-native-call-signature)))
(defn personal-sign (defn personal-sign
[password address data] [password address data]
@ -40,7 +42,7 @@
(defn eth-sign-typed-data (defn eth-sign-typed-data
[password address data chain-id-eip155 version] [password address data chain-id-eip155 version]
(let [legacy? (= version :v1) (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 (rpc/wallet-safe-sign-typed-data data
address address
password password

View File

@ -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] (:require [cljs-bean.core :as bean]
[clojure.string :as string] [clojure.string :as string]
[native-module.core :as native-module] [native-module.core :as native-module]
[promesa.core :as promesa] [promesa.core :as promesa]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.core :as core] [status-im.contexts.wallet.wallet-connect.utils.data-store :as
[status-im.contexts.wallet.wallet-connect.rpc :as rpc] data-store]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.money :as money] [utils.money :as money]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
(defn transaction-request? (defn transaction-request?
[event] [event]
(->> (core/get-request-method event) (->> (data-store/get-request-method event)
(contains? #{constants/wallet-connect-eth-send-transaction-method (contains? #{constants/wallet-connect-eth-send-transaction-method
constants/wallet-connect-eth-sign-transaction-method}))) constants/wallet-connect-eth-sign-transaction-method})))

View File

@ -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])) (:require [react-native.wallet-connect :as wallet-connect]))
(defn version-supported? (defn version-supported?

View File

@ -48,7 +48,7 @@
status-im.contexts.wallet.send.events status-im.contexts.wallet.send.events
status-im.contexts.wallet.signals status-im.contexts.wallet.signals
status-im.contexts.wallet.swap.events 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.db :as db]
status-im.navigation.effects status-im.navigation.effects
status-im.navigation.events status-im.navigation.events

View File

@ -127,10 +127,11 @@
[status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal] [status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal]
[status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as [status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as
wallet-connect-send-transaction] 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-message.view :as wallet-connect-sign-message]
[status-im.contexts.wallet.wallet-connect.modals.sign-transaction.view :as [status-im.contexts.wallet.wallet-connect.modals.sign-transaction.view :as
wallet-connect-sign-transaction] 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.options :as options]
[status-im.navigation.transitions :as transitions])) [status-im.navigation.transitions :as transitions]))

View File

@ -20,12 +20,12 @@
status-im.subs.wallet.activities status-im.subs.wallet.activities
status-im.subs.wallet.buy status-im.subs.wallet.buy
status-im.subs.wallet.collectibles status-im.subs.wallet.collectibles
status-im.subs.wallet.dapps.core
status-im.subs.wallet.networks status-im.subs.wallet.networks
status-im.subs.wallet.saved-addresses status-im.subs.wallet.saved-addresses
status-im.subs.wallet.send status-im.subs.wallet.send
status-im.subs.wallet.swap status-im.subs.wallet.swap
status-im.subs.wallet.wallet status-im.subs.wallet.wallet))
status-im.subs.wallet.wallet-connect))
(defn reg-root-key-sub (defn reg-root-key-sub
[sub-name db-key] [sub-name db-key]

View File

@ -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})))

View File

@ -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])))

View File

@ -1,9 +1,9 @@
(ns status-im.subs.wallet.wallet-connect-test (ns status-im.subs.wallet.dapps.proposals-test
(:require (:require
[cljs.test :refer [is testing]] [cljs.test :refer [is testing]]
[re-frame.db :as rf-db] [re-frame.db :as rf-db]
status-im.subs.root status-im.subs.root
status-im.subs.wallet.wallet-connect status-im.subs.wallet.dapps.core
[test-helpers.unit :as h] [test-helpers.unit :as h]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))

View File

@ -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)

View File

@ -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)))

View File

@ -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)}))))

View File

@ -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])))