Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dapps Typed Data request improvements #21333

Merged
merged 9 commits into from
Oct 2, 2024
11 changes: 6 additions & 5 deletions src/status_im/contexts/wallet/wallet_connect/events/effects.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
[utils.i18n :as i18n]
[utils.security.core :as security]))

Expand Down Expand Up @@ -123,11 +124,11 @@
(rf/reg-fx
:effects.wallet-connect/sign-typed-data
(fn [{:keys [password address data version chain-id on-success on-error]}]
(-> (signing/eth-sign-typed-data (security/safe-unmask-data password)
address
data
chain-id
version)
(-> (typed-data/sign (security/safe-unmask-data password)
address
data
chain-id
version)
(promesa/then on-success)
(promesa/catch on-error))))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
Expand Down Expand Up @@ -135,16 +135,13 @@
(fn [{:keys [db]}]
(try
(let [[address raw-data] (data-store/get-db-current-request-params db)
parsed-raw-data (transforms/js-parse raw-data)
session-chain-id (-> (data-store/get-db-current-request-event db)
(get-in [:params :chainId])
networks/eip155->chain-id)
data-chain-id (-> parsed-raw-data
transforms/js->clj
signing/typed-data-chain-id)
parsed-data (-> parsed-raw-data
(transforms/js-dissoc :types :primaryType)
(transforms/js-stringify 2))]
typed-data (-> raw-data
transforms/js-parse
transforms/js->clj)
data-chain-id (typed-data/get-chain-id typed-data)]
(if (and data-chain-id
(not= session-chain-id data-chain-id))
{:fx [[:dispatch
Expand All @@ -155,7 +152,7 @@
[:wallet-connect/current-request]
assoc
:address (string/lower-case address)
:display-data (or parsed-data raw-data)
:display-data (typed-data/flatten-typed-data typed-data)
:raw-data raw-data)
:fx [[:dispatch [:wallet-connect/show-request-modal]]]}))
(catch js/Error err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@

(def data-item
{:flex 1
:background-color :transparent
:margin-bottom 8
:margin-top 2})
:background-color :transparent})

(def typed-data-item
(assoc data-item :margin-bottom 4))

(def data-border-container
{:padding-horizontal 12
:padding-top 8
:margin-top 10.5
:margin-bottom 0
:border-width 1
:border-color colors/neutral-10
:border-radius 16})

(def data-item-container
{:padding 10
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
(ns status-im.contexts.wallet.wallet-connect.modals.sign-message.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.safe-area :as safe-area]
[status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block]
[status-im.common.raw-data-block.view :as data-block]
[status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as
fees-data-item]
[status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer]
Expand All @@ -12,6 +13,37 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))

(defn typed-data-field
[{:keys [label value]}]
[quo/data-item
{:card? false
:container-style style/typed-data-item
:title label
:subtitle value}])

(defn typed-data-view
[]
(let [display-data (rf/sub [:wallet-connect/current-request-display-data])]
[gesture/flat-list
{:data display-data
:style style/data-border-container
:over-scroll-mode :never
:content-container-style {:padding-bottom 12}
:render-fn typed-data-field
:shows-vertical-scroll-indicator false}]))

(defn message-data-view
[]
(let [display-data (rf/sub [:wallet-connect/current-request-display-data])]
[data-block/view display-data]))

(defn display-data-view
[]
(let [typed-data? (rf/sub [:wallet-connect/typed-data-request?])]
(if typed-data?
[typed-data-view]
[message-data-view])))

(defn view
[]
(let [bottom (safe-area/get-bottom)
Expand All @@ -29,7 +61,7 @@
{:label (i18n/label :t/wallet-connect-sign-message-header)
:dapp dapp
:account account}]
[data-block/view]]
[display-data-view]]
[footer/view
{:warning-label (i18n/label :t/wallet-connect-sign-warning)
:slide-button-text (i18n/label :t/slide-to-sign)}
Expand Down
42 changes: 8 additions & 34 deletions src/status_im/contexts/wallet/wallet_connect/utils/signing.cljs
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
(ns status-im.contexts.wallet.wallet-connect.utils.signing
(:require [native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.hex :as hex]
[utils.number :as number]
[utils.transforms :as transforms]))

(defn typed-data-chain-id
"Returns the `:chain-id` from typed data if it's present and if the EIP712 domain defines it. Without
the `:chain-id` in the domain type, it will not be signed as part of the typed-data."
[typed-data]
(let [chain-id-type? (->> typed-data
:types
:EIP712Domain
(some #(= "chainId" (:name %))))
data-chain-id (-> typed-data
:domain
:chainId
number/parse-int)]
(when chain-id-type?
data-chain-id)))
(:require
[native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.hex :as hex]
[utils.transforms :as transforms]))

(defn eth-sign
[password address data]
Expand All @@ -38,13 +22,3 @@
(-> (rpc/wallet-hash-message-eip-191 data)
(promesa/then #(rpc/wallet-sign-message % address password))
(promesa/then hex/prefix-hex)))

(defn eth-sign-typed-data
[password address data chain-id-eip155 version]
(let [legacy? (= version :v1)
chain-id (networks/eip155->chain-id chain-id-eip155)]
(rpc/wallet-safe-sign-typed-data data
address
password
chain-id
legacy?)))
102 changes: 102 additions & 0 deletions src/status_im/contexts/wallet/wallet_connect/utils/typed_data.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
(ns status-im.contexts.wallet.wallet-connect.utils.typed-data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clauxx, is the Desktop app integrating now or will integrate with WC? Sorry if this is an obvious question, but my point is that I feel this logic doesn't belong in clients and should be in status-go. A lot of wallet-related code is living in the wrong place in my opinion. In the not so distant past, our high-level architectural goal was to build light clients and I think the goal still stands.

Well, this is just a thought for your PR, not a suggestion for change. It would be great if the Mobile Wallet team more heavily considered this factor because we are inflating too much the client with logic. It's great that you unit tested, but this is not always the case in mobile PRs. My belief and experience with status-go is that the slowdown in iterations would only come in the beginning as people learn how to solve with this backend-first mentality.

cc'ing @shivekkhurana @smohamedjavid just to bump this point a bit more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's on the client for now, since this logic is directly related to the UI and how we show the typed data to the user. We are dealing with limited horizontal space on mobile, so we had to find solutions to remove nesting. On the desktop this might not be an issue, so the representation there might be different.

(:require [clojure.string :as string]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.number :as number]))

(declare flatten-data)

(defn- format-flattened-key
[k]
(cond
(keyword? k) (name k)
(number? k) (str k)
(string? k) k
:else "unsupported-key"))

(defn- flatten-map
[data path]
(reduce-kv (fn [acc k v]
(->> (format-flattened-key k)
(conj path)
(flatten-data v acc)))
[]
data))

(defn- flatten-vec
[data path]
(->> data
(map-indexed vector)
(reduce (fn [acc [idx v]]
(->> (str idx)
(conj path)
(flatten-data v acc)))
[])))

(defn flatten-data
"Recursively flatten a map or vector into a flat vector.

e.g. `[[[\"person\" \"first-name\"] \"Rich\"]
[[[\"person\" \"last-name\"] \"Hickey\"]]]`"
([value]
(flatten-data value [] []))
([value acc path]
(cond
(map? value) (into acc (flatten-map value path))
(vector? value) (into acc (flatten-vec value path))
:else (conj acc [path value]))))

(defn format-fields
"Format the fields into maps with `:label` & `:value`, where the label
is the flattened keys joined with a separator

e.g. `{:label \"person: first-name:\" :value \"Rich\"}`"
[data separator]
(mapv (fn [[kv v]]
{:label (-> separator
(string/join kv)
(str separator)
string/trim)
:value v})
data))

(defn flatten-typed-data
"Flatten typed data and prepare it for UI"
[typed-data]
(-> typed-data
(select-keys [:domain :message])
flatten-data
(format-fields ": ")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the format from: wallets: 1: copied from other products or is this a standard we are creating ourselves for Status? I find hard to imagine users can figure out the indexes and the flattening going on, but maybe not, things are crazy in terms of usability when signing.

Do you know if there's any interest in improving the designs or if any concerns were raised?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're doing small iterations on this. There is no standard around typed data, but there are definitely improvements to be made. Aside from the representation of arrays, which I agree that is not ideal, is the inclusion of custom types instead (or in addition) to key names, as it is now. The custom types should give more context around what the piece of data represents and the context it lives in. For now though, we decided move away from json but keep it simple at the same time.


(defn get-chain-id
"Returns the `:chain-id` from typed data if it's present and if the EIP712 domain defines it. Without
the `:chain-id` in the domain type, it will not be signed as part of the typed-data."
[typed-data]
(let [chain-id-type? (->> typed-data
:types
:EIP712Domain
(some #(= "chainId" (:name %))))
data-chain-id (-> typed-data
:domain
:chainId
number/parse-int)]
(when chain-id-type?
data-chain-id)))

(defn sign
[password address data chain-id-eip155 version]
(let [legacy? (= version :v1)
chain-id (networks/eip155->chain-id chain-id-eip155)]
(rpc/wallet-safe-sign-typed-data data
address
password
chain-id
legacy?)))

(defn typed-data-request?
[method]
(contains?
#{constants/wallet-connect-eth-sign-typed-v4-method
constants/wallet-connect-eth-sign-typed-method}
method))
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
(ns status-im.contexts.wallet.wallet-connect.utils.typed-data-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as sut]))

(deftest flatten-data-test
(testing "correctly returns the fallback when key is not string/keyword/number"
(is (match? [[["unsupported-key"] "value"]]
(sut/flatten-data {[1 2] "value"}))))

(testing "correctly flattens a simple map"
(is (match? [[["zero"] 0]
[["one"] 1]
[["2"] 2]]
(sut/flatten-data {:zero 0
"one" 1
2 2}))))

(testing "correctly flattens a simple vec"
(is (match? [[["0"] "zero"] [["1"] "one"]]
(sut/flatten-data ["zero" "one"]))))

(testing "correctly flattens a nested map"
(is (match? [[["nested" "child"] "child value"]
[["nested" "nested" "0"] "child one"]
[["nested" "nested" "1"] "child two"]
[["flat"] "child value"]]
(sut/flatten-data {:nested {:child "child value"
:nested ["child one" "child two"]}
:flat "child value"}))))

(testing "correctly flattens a nested vector"
(is (match? [[["0" "flat"] 1]
[["0" "nested-vector" "0"] 1]
[["0" "nested-vector" "1"] 2]
[["0" "nested-map" "one"] 1]]
(sut/flatten-data [{:flat 1
:nested-vector [1 2]
:nested-map {:one 1}}])))))

(deftest flatten-typed-data-test
(testing "successfully extracts, flattens and formats the typed data"
(is (match? [{:label "domain: chain-id:" :value 1}
{:label "message: to: address:" :value "0x"}
{:label "message: from: address:" :value "0x"}
{:label "message: amount:" :value "0x"}]
(sut/flatten-typed-data {:domain {:chain-id 1}
:types {:Tx [{:name "to"
:type "address"}]}
:message {:to {:address "0x"}
:from {:address "0x"}
:amount "0x"}})))))
14 changes: 14 additions & 0 deletions src/status_im/subs/wallet/dapps/requests.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[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.typed-data :as typed-data]
[utils.string]))

(rf/reg-sub
Expand All @@ -16,6 +17,14 @@
:<- [:wallet-connect/current-request]
:-> :display-data)

(rf/reg-sub
:wallet-connect/current-request-method
:<- [:wallet-connect/current-request]
(fn [request]
(-> request
:event
data-store/get-request-method)))

(rf/reg-sub
:wallet-connect/current-request-account-details
:<- [:wallet-connect/current-request-address]
Expand Down Expand Up @@ -45,3 +54,8 @@
:wallet-connect/current-request-network
:<- [:wallet-connect/chain-id]
networks/chain-id->network-details)

(rf/reg-sub
:wallet-connect/typed-data-request?
:<- [:wallet-connect/current-request-method]
typed-data/typed-data-request?)