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.constants :as constants]
|
||||||
[status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
|
[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.transactions :as transactions]
|
||||||
|
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.security.core :as security]))
|
[utils.security.core :as security]))
|
||||||
|
|
||||||
|
@ -123,11 +124,11 @@
|
||||||
(rf/reg-fx
|
(rf/reg-fx
|
||||||
:effects.wallet-connect/sign-typed-data
|
:effects.wallet-connect/sign-typed-data
|
||||||
(fn [{:keys [password address data version chain-id on-success on-error]}]
|
(fn [{:keys [password address data version chain-id on-success on-error]}]
|
||||||
(-> (signing/eth-sign-typed-data (security/safe-unmask-data password)
|
(-> (typed-data/sign (security/safe-unmask-data password)
|
||||||
address
|
address
|
||||||
data
|
data
|
||||||
chain-id
|
chain-id
|
||||||
version)
|
version)
|
||||||
(promesa/then on-success)
|
(promesa/then on-success)
|
||||||
(promesa/catch on-error))))
|
(promesa/catch on-error))))
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||||
data-store]
|
data-store]
|
||||||
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
[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.transactions :as transactions]
|
||||||
|
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]
|
[utils.re-frame :as rf]
|
||||||
|
@ -135,16 +135,13 @@
|
||||||
(fn [{:keys [db]}]
|
(fn [{:keys [db]}]
|
||||||
(try
|
(try
|
||||||
(let [[address raw-data] (data-store/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)
|
|
||||||
session-chain-id (-> (data-store/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])
|
||||||
networks/eip155->chain-id)
|
networks/eip155->chain-id)
|
||||||
data-chain-id (-> parsed-raw-data
|
typed-data (-> raw-data
|
||||||
transforms/js->clj
|
transforms/js-parse
|
||||||
signing/typed-data-chain-id)
|
transforms/js->clj)
|
||||||
parsed-data (-> parsed-raw-data
|
data-chain-id (typed-data/get-chain-id typed-data)]
|
||||||
(transforms/js-dissoc :types :primaryType)
|
|
||||||
(transforms/js-stringify 2))]
|
|
||||||
(if (and data-chain-id
|
(if (and data-chain-id
|
||||||
(not= session-chain-id data-chain-id))
|
(not= session-chain-id data-chain-id))
|
||||||
{:fx [[:dispatch
|
{:fx [[:dispatch
|
||||||
|
@ -155,7 +152,7 @@
|
||||||
[:wallet-connect/current-request]
|
[:wallet-connect/current-request]
|
||||||
assoc
|
assoc
|
||||||
:address (string/lower-case address)
|
: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)
|
:raw-data raw-data)
|
||||||
:fx [[:dispatch [:wallet-connect/show-request-modal]]]}))
|
:fx [[:dispatch [:wallet-connect/show-request-modal]]]}))
|
||||||
(catch js/Error err
|
(catch js/Error err
|
||||||
|
|
|
@ -21,9 +21,19 @@
|
||||||
|
|
||||||
(def data-item
|
(def data-item
|
||||||
{:flex 1
|
{:flex 1
|
||||||
:background-color :transparent
|
:background-color :transparent})
|
||||||
:margin-bottom 8
|
|
||||||
:margin-top 2})
|
(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
|
(def data-item-container
|
||||||
{:padding 10
|
{:padding 10
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
(ns status-im.contexts.wallet.wallet-connect.modals.sign-message.view
|
(ns status-im.contexts.wallet.wallet-connect.modals.sign-message.view
|
||||||
(:require [quo.core :as quo]
|
(:require [quo.core :as quo]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
|
[react-native.gesture :as gesture]
|
||||||
[react-native.safe-area :as safe-area]
|
[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
|
[status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as
|
||||||
fees-data-item]
|
fees-data-item]
|
||||||
[status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer]
|
[status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer]
|
||||||
|
@ -12,6 +13,37 @@
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[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
|
(defn view
|
||||||
[]
|
[]
|
||||||
(let [bottom (safe-area/get-bottom)
|
(let [bottom (safe-area/get-bottom)
|
||||||
|
@ -29,7 +61,7 @@
|
||||||
{:label (i18n/label :t/wallet-connect-sign-message-header)
|
{:label (i18n/label :t/wallet-connect-sign-message-header)
|
||||||
:dapp dapp
|
:dapp dapp
|
||||||
:account account}]
|
:account account}]
|
||||||
[data-block/view]]
|
[display-data-view]]
|
||||||
[footer/view
|
[footer/view
|
||||||
{:warning-label (i18n/label :t/wallet-connect-sign-warning)
|
{:warning-label (i18n/label :t/wallet-connect-sign-warning)
|
||||||
:slide-button-text (i18n/label :t/slide-to-sign)}
|
:slide-button-text (i18n/label :t/slide-to-sign)}
|
||||||
|
|
|
@ -1,28 +1,12 @@
|
||||||
(ns status-im.contexts.wallet.wallet-connect.utils.signing
|
(ns status-im.contexts.wallet.wallet-connect.utils.signing
|
||||||
(:require [native-module.core :as native-module]
|
(:require
|
||||||
[promesa.core :as promesa]
|
[native-module.core :as native-module]
|
||||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
[promesa.core :as promesa]
|
||||||
data-store]
|
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||||
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
data-store]
|
||||||
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
|
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
|
||||||
[utils.hex :as hex]
|
[utils.hex :as hex]
|
||||||
[utils.number :as number]
|
[utils.transforms :as transforms]))
|
||||||
[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)))
|
|
||||||
|
|
||||||
(defn eth-sign
|
(defn eth-sign
|
||||||
[password address data]
|
[password address data]
|
||||||
|
@ -38,13 +22,3 @@
|
||||||
(-> (rpc/wallet-hash-message-eip-191 data)
|
(-> (rpc/wallet-hash-message-eip-191 data)
|
||||||
(promesa/then #(rpc/wallet-sign-message % address password))
|
(promesa/then #(rpc/wallet-sign-message % address password))
|
||||||
(promesa/then hex/prefix-hex)))
|
(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
|
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||||
data-store]
|
data-store]
|
||||||
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
||||||
|
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
|
||||||
[utils.string]))
|
[utils.string]))
|
||||||
|
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
|
@ -16,6 +17,14 @@
|
||||||
:<- [:wallet-connect/current-request]
|
:<- [:wallet-connect/current-request]
|
||||||
:-> :display-data)
|
:-> :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
|
(rf/reg-sub
|
||||||
:wallet-connect/current-request-account-details
|
:wallet-connect/current-request-account-details
|
||||||
:<- [:wallet-connect/current-request-address]
|
:<- [:wallet-connect/current-request-address]
|
||||||
|
@ -45,3 +54,8 @@
|
||||||
:wallet-connect/current-request-network
|
:wallet-connect/current-request-network
|
||||||
:<- [:wallet-connect/chain-id]
|
:<- [:wallet-connect/chain-id]
|
||||||
networks/chain-id->network-details)
|
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