From a74a44e49272895b728bdf5962b2d4b2cd66bc4f Mon Sep 17 00:00:00 2001 From: frank Date: Fri, 14 Oct 2022 16:32:55 +0800 Subject: [PATCH] Allow dApps to suggest change of RPC (EIP-3085 EIP-3326) (#13716) --- src/status_im/browser/core.cljs | 44 ++++++---- src/status_im/browser/eip3085.cljs | 69 ++++++++++++++++ src/status_im/browser/eip3326.cljs | 43 ++++++++++ .../ui/screens/browser/eip3085/sheet.cljs | 81 +++++++++++++++++++ .../ui/screens/browser/eip3326/sheet.cljs | 58 +++++++++++++ 5 files changed, 278 insertions(+), 17 deletions(-) create mode 100644 src/status_im/browser/eip3085.cljs create mode 100644 src/status_im/browser/eip3326.cljs create mode 100644 src/status_im/ui/screens/browser/eip3085/sheet.cljs create mode 100644 src/status_im/ui/screens/browser/eip3326/sheet.cljs diff --git a/src/status_im/browser/core.cljs b/src/status_im/browser/core.cljs index 14d3884a57..37e25e50fa 100644 --- a/src/status_im/browser/core.cljs +++ b/src/status_im/browser/core.cljs @@ -22,7 +22,9 @@ [status-im.bottom-sheet.core :as bottom-sheet] [status-im.browser.webview-ref :as webview-ref] ["eth-phishing-detect" :as eth-phishing-detect] - [status-im.utils.debounce :as debounce])) + [status-im.utils.debounce :as debounce] + [status-im.browser.eip3085 :as eip3085] + [status-im.browser.eip3326 :as eip3326])) (fx/defn update-browser-option [{:keys [db]} option-key option-value] @@ -345,7 +347,7 @@ constants/web3-eth-sign constants/web3-keycard-sign-typed-data} method)) (fx/defn web3-send-async - [cofx {:keys [method params id] :as payload} message-id] + [cofx dapp-name {:keys [method params id] :as payload} message-id] (let [message? (web3-sign-message? method) dapps-address (get-in cofx [:db :multiaccount :dapps-address]) typed? (and (not= constants/web3-personal-sign method) (not= constants/web3-eth-sign method))] @@ -371,25 +373,33 @@ (dissoc :gasPrice))}) {:on-result [:browser.dapp/transaction-on-result message-id id] :on-error [:browser.dapp/transaction-on-error message-id]})))) - (if (#{"eth_accounts" "eth_coinbase"} method) + (cond + (#{"eth_accounts" "eth_coinbase"} method) (send-to-bridge cofx {:type constants/web3-send-async-callback :messageId message-id :result {:jsonrpc "2.0" :id (int id) :result (if (= method "eth_coinbase") dapps-address [dapps-address])}}) - (if (= method "personal_ecRecover") - {:signing.fx/recover-message {:params {:message (first params) - :signature (second params)} - :on-completed #(re-frame/dispatch [:browser.callback/call-rpc - {:type constants/web3-send-async-callback - :messageId message-id - :result (types/json->clj %)}])}} - {:browser/call-rpc [payload - #(re-frame/dispatch [:browser.callback/call-rpc - {:type constants/web3-send-async-callback - :messageId message-id - :error %1 - :result %2}])]}))))) + (= method "personal_ecRecover") + {:signing.fx/recover-message {:params {:message (first params) + :signature (second params)} + :on-completed #(re-frame/dispatch [:browser.callback/call-rpc + {:type constants/web3-send-async-callback + :messageId message-id + :result (types/json->clj %)}])}} + (= method "wallet_switchEthereumChain") + (eip3326/handle-switch-ethereum-chain cofx dapp-name id message-id (first params)) + + (= method "wallet_addEthereumChain") + (eip3085/handle-add-ethereum-chain cofx dapp-name id message-id (first params)) + + :else + {:browser/call-rpc [payload + #(re-frame/dispatch [:browser.callback/call-rpc + {:type constants/web3-send-async-callback + :messageId message-id + :error %1 + :result %2}])]})))) (fx/defn handle-no-permissions [cofx {:keys [method id]} message-id] (if (= method "eth_accounts") @@ -419,7 +429,7 @@ [{:keys [db] :as cofx} dapp-name {:keys [method] :as payload} message-id] (if (has-permissions? db dapp-name method) (handle-no-permissions cofx payload message-id) - (web3-send-async cofx payload message-id))) + (web3-send-async cofx dapp-name payload message-id))) (fx/defn handle-scanned-qr-code {:events [:browser.bridge.callback/qr-code-scanned]} diff --git a/src/status_im/browser/eip3085.cljs b/src/status_im/browser/eip3085.cljs new file mode 100644 index 0000000000..74175f2a32 --- /dev/null +++ b/src/status_im/browser/eip3085.cljs @@ -0,0 +1,69 @@ +;reference https://eips.ethereum.org/EIPS/eip-3085 EIP-3085: Wallet Add Ethereum Chain RPC Method (`wallet_addEthereumChain`) +(ns status-im.browser.eip3085 + (:require [status-im.constants :as constants] + [clojure.string :as string] + [re-frame.core :as re-frame] + [status-im.utils.fx :as fx] + [status-im.network.core :as network] + [taoensso.timbre :as log] + [status-im.utils.random :as random] + [status-im.ethereum.json-rpc :as json-rpc] + [status-im.ui.screens.browser.eip3085.sheet :as sheet])) + +(fx/defn send-success-call-to-bridge + {:events [:eip3085/send-success-call-to-bridge]} + [_ id messageId] + {:browser/send-to-bridge {:type constants/web3-send-async-callback + :messageId messageId + :result {:jsonrpc "2.0" + :id (int id) + :result nil}}}) + +(fx/defn allow-permission + {:events [:eip3085.ui/dapp-permission-allowed]} + [{:keys [db] :as cofx} message-id {:keys [new-networks id]}] + {:db (assoc db :networks/networks new-networks) + ::json-rpc/call [{:method "settings_saveSetting" + :params [:networks/networks (vals new-networks)] + :on-success #(re-frame/dispatch [:eip3085/send-success-call-to-bridge cofx id message-id]) + :on-error #(log/error "failed to perform settings_saveSetting" %)}] + :dispatch [:bottom-sheet/hide]}) + +(fx/defn deny-permission + {:events [:eip3085.ui/dapp-permission-denied]} + [_ message-id _] + {:browser/send-to-bridge {:type constants/web3-send-async-callback + :messageId message-id + :error {:code 4001 + :message "User rejected the request."}} + :dispatch [:bottom-sheet/hide]}) + +(fx/defn handle-add-ethereum-chain + {:events [:eip3085/handle-add-ethereum-chain]} + [{{:networks/keys [networks] :as db} :db :as cofx} + dapp-name id message-id {:keys [chainId blockExplorerUrls chainName iconUrls nativeCurrency rpcUrls] :as params}] + (let [manage {:name {:value chainName} + :symbol {:value (:symbol nativeCurrency)} + :url {:value (first rpcUrls)} + :network-id {:value chainId} + :chain {:value :custom}}] + (if (network/valid-manage? manage) + (let [{:keys [name url chain network-id symbol]} manage + random-id (string/replace (random/id) "-" "") + network (network/new-network random-id + (:value name) + (:value symbol) + (:value url) + (:value chain) + (:value network-id)) + new-networks (assoc networks random-id network) + params (assoc params :new-networks new-networks :id id :new-network network)] + (if (network/chain-id-available? networks network) + {:dispatch [:bottom-sheet/show-sheet {:content (fn [] + [sheet/permissions-panel dapp-name message-id params])}]} + (send-success-call-to-bridge cofx id message-id))) + {:browser/send-to-bridge {:type constants/web3-send-async-callback + :messageId message-id + :error {:code -32602 + :message "invalid network parameters"}}}))) + diff --git a/src/status_im/browser/eip3326.cljs b/src/status_im/browser/eip3326.cljs new file mode 100644 index 0000000000..22d1419e4b --- /dev/null +++ b/src/status_im/browser/eip3326.cljs @@ -0,0 +1,43 @@ +;reference https://eips.ethereum.org/EIPS/eip-3326 EIP-3326: Wallet Switch Ethereum Chain RPC Method (`wallet_switchEthereumChain`) +(ns status-im.browser.eip3326 + (:require [status-im.constants :as constants] + [status-im.utils.fx :as fx] + [status-im.ethereum.core :as ethereum] + [status-im.ui.screens.browser.eip3326.sheet :as sheet])) + +(fx/defn deny-permission + {:events [:eip3326.ui/dapp-permission-denied]} + [{:keys [db] :as cofx} message-id _] + {:browser/send-to-bridge {:type constants/web3-send-async-callback + :messageId message-id + :error {:code 4001 + :message "User rejected the request."}} + :dispatch [:bottom-sheet/hide]}) + +(fx/defn handle-switch-ethereum-chain + {:events [:eip3326/handle-switch-ethereum-chain]} + [{:keys [db] :as cofx} dapp-name id message-id {:keys [chainId] :as params}] + (let [target-chain-id (js/parseInt chainId 16) + networks (vals (get-in db [:networks/networks])) + exist-chain-ids (set (map ethereum/network->chain-id networks)) + current-chain-id (ethereum/chain-id db)] + (if (exist-chain-ids target-chain-id) + (if (= current-chain-id target-chain-id) + {:browser/send-to-bridge {:type constants/web3-send-async-callback + :messageId message-id + :result {:jsonrpc "2.0" + :id (int id) + :result nil}}} + (let [target-network (first (filter #(= (ethereum/network->chain-id %1) target-chain-id) networks)) + target-network-id (:id target-network) + current-network (ethereum/current-network db) + network-from (:name current-network) + network-to (:name target-network) + params (assoc params :target-network-id target-network-id :network-from network-from :network-to network-to)] + {:dispatch [:bottom-sheet/show-sheet {:content (fn [] + [sheet/permissions-panel dapp-name message-id params])}]})) + {:browser/send-to-bridge {:type constants/web3-send-async-callback + :messageId message-id + :error {:code 4902 + :message (str "Unrecognized chain ID: " target-chain-id ". Try adding the chain using wallet_addEthereumChain first.")}}}))) + diff --git a/src/status_im/ui/screens/browser/eip3085/sheet.cljs b/src/status_im/ui/screens/browser/eip3085/sheet.cljs new file mode 100644 index 0000000000..6d03a9005e --- /dev/null +++ b/src/status_im/ui/screens/browser/eip3085/sheet.cljs @@ -0,0 +1,81 @@ +(ns status-im.ui.screens.browser.eip3085.sheet + (:require [re-frame.core :as re-frame] + [status-im.i18n.i18n :as i18n] + [status-im.ui.components.chat-icon.screen :as chat-icon.screen] + [status-im.ui.components.icons.icons :as icons] + [status-im.ui.components.react :as react] + [status-im.ui.screens.browser.styles :as styles] + [quo.design-system.colors :as colors] + [quo.core :as quo] + [status-im.ui.components.copyable-text :as copyable-text]) + (:require-macros [status-im.utils.views :as views])) + +(views/defview permissions-panel [dapp-name message-id params] + (views/letsubs [{:keys [dapp? dapp]} [:get-current-browser]] + [react/view {} + [react/view styles/permissions-panel-icons-container + (if dapp? + [chat-icon.screen/dapp-icon-permission dapp 40] + [react/view styles/permissions-panel-dapp-icon-container + [icons/icon :main-icons/dapp {:color colors/gray}]]) + [react/view {:margin-left 8 :margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 8} + [react/view styles/dot]] + [react/view styles/permissions-panel-ok-icon-container + [icons/icon :tiny-icons/tiny-check styles/permissions-panel-ok-ico]] + [react/view {:margin-left 8 :margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 8} + [react/view styles/dot]] + [react/view styles/permissions-panel-wallet-icon-container + [icons/icon :main-icons/wallet {:color colors/white}]]] + [react/text {:style styles/permissions-panel-title-label :number-of-lines 2} + (str "\"" dapp-name "\" Allow this site to add a network?")] + [react/text {:style styles/permissions-panel-description-label :number-of-lines 4} "This will allow this network to be used within Status.Status does not verify custom networks.Learn about scams and network security risks."] + [react/scroll-view + [copyable-text/copyable-text-view + {:copied-text (:name (:new-network params))} + [quo/list-item + {:size :small + :accessibility-label :network-name + :title "Network Name" + :accessory :text + :accessory-text (:name (:new-network params))}]] + [copyable-text/copyable-text-view + {:copied-text (get-in params [:new-network :config :UpstreamConfig :URL])} + [quo/list-item + {:size :small + :accessibility-label :network-url + :title "Network URL" + :accessory :text + :accessory-text (get-in params [:new-network :config :UpstreamConfig :URL])}]] + [copyable-text/copyable-text-view + {:copied-text (str (get-in params [:new-network :config :NetworkId]))} + [quo/list-item + {:size :small + :accessibility-label :network-id + :title "Chain ID" + :accessory :text + :accessory-text (str (get-in params [:new-network :config :NetworkId]))}]]] + [react/view {:style {:flex-direction :row + :justify-content :center + :margin-horizontal 8 + :margin-top 24}} + [react/view {:flex 1 + :margin-horizontal 8} + [quo/button + {:theme :negative + :on-press #(re-frame/dispatch [:eip3085.ui/dapp-permission-denied message-id params])} + (i18n/label :t/deny)]] + [react/view {:flex 1 + :margin-horizontal 8} + [quo/button + {:theme :positive + :style {:margin-horizontal 8} + :on-press #(re-frame/dispatch [:eip3085.ui/dapp-permission-allowed message-id params])} + (i18n/label :t/allow)]]]])) diff --git a/src/status_im/ui/screens/browser/eip3326/sheet.cljs b/src/status_im/ui/screens/browser/eip3326/sheet.cljs new file mode 100644 index 0000000000..a215ec97f8 --- /dev/null +++ b/src/status_im/ui/screens/browser/eip3326/sheet.cljs @@ -0,0 +1,58 @@ +(ns status-im.ui.screens.browser.eip3326.sheet + (:require [re-frame.core :as re-frame] + [status-im.i18n.i18n :as i18n] + [status-im.ui.components.chat-icon.screen :as chat-icon.screen] + [status-im.ui.components.icons.icons :as icons] + [status-im.ui.components.react :as react] + [status-im.ui.screens.browser.styles :as styles] + [status-im.utils.debounce :as debounce] + [status-im.network.core :as network] + [quo.design-system.colors :as colors] + [quo.core :as quo]) + (:require-macros [status-im.utils.views :as views])) + +(views/defview permissions-panel [dapp-name message-id {:keys [network-from network-to target-network-id] :as params}] + (views/letsubs [{:keys [dapp? dapp]} [:get-current-browser]] + [react/view {} + [react/view styles/permissions-panel-icons-container + (if dapp? + [chat-icon.screen/dapp-icon-permission dapp 40] + [react/view styles/permissions-panel-dapp-icon-container + [icons/icon :main-icons/dapp {:color colors/gray}]]) + [react/view {:margin-left 8 :margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 8} + [react/view styles/dot]] + [react/view styles/permissions-panel-ok-icon-container + [icons/icon :tiny-icons/tiny-check styles/permissions-panel-ok-ico]] + [react/view {:margin-left 8 :margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 4} + [react/view styles/dot]] + [react/view {:margin-right 8} + [react/view styles/dot]] + [react/view styles/permissions-panel-wallet-icon-container + [icons/icon :main-icons/wallet {:color colors/white}]]] + [react/text {:style styles/permissions-panel-title-label :number-of-lines 2} + (str "\"" dapp-name "\" Allow this site to switch the network?")] + [react/text {:style styles/permissions-panel-description-label :number-of-lines 5} + (str "This will switch the selected network within Status to a previously added network:\n" network-from " -> " network-to "\nit will require login/logout")] + [react/view {:style {:flex-direction :row + :justify-content :center + :margin-horizontal 8 + :margin-top 24}} + [react/view {:flex 1 + :margin-horizontal 8} + [quo/button + {:theme :negative + :on-press #(re-frame/dispatch [:eip3326.ui/dapp-permission-denied message-id params])} + (i18n/label :t/deny)]] + [react/view {:flex 1 + :margin-horizontal 8} + [quo/button + {:theme :positive + :style {:margin-horizontal 8} + :on-press #(debounce/dispatch-and-chill [::network/connect-network-pressed target-network-id] 1000)} + (i18n/label :t/allow)]]]]))