Dapps Typed Data request improvements (#21333)
* feat: added type data flattening + tests * feat: added typed data flattened ui * ref: moved typed data utils into their own ns * ref: small changes & formatting * feat: show typed data view only for typed data requests * ref: moved typed-data? into sub * fix: tests and styling
This commit is contained in:
parent
0c3e836272
commit
97aff27980
|
@ -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]))
|
||||
|
||||
|
@ -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))))
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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)}
|
||||
|
|
|
@ -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]
|
||||
|
@ -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?)))
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
(ns status-im.contexts.wallet.wallet-connect.utils.typed-data
|
||||
(: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 ": ")))
|
||||
|
||||
(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))
|
|
@ -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"}})))))
|
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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?)
|
||||
|
|
Loading…
Reference in New Issue