Metamask address support (#20844)
* wip, small renamings * Added metamask regex * Added tests for metamask conversion * Add metamask address to shell scanner and wallet address scanner * unify address functions usage * address-related functions moved to a dedicated namespace * minor fixes * fix lint and tests * review notes fixes * return back function * lint fix * Support metamask addresses without suffix, prefixed `:ethereum`
This commit is contained in:
parent
96b88a427d
commit
bf7b86ccd5
|
@ -24,7 +24,7 @@
|
|||
|
||||
(def web-prefixes ["https://" "http://" "https://www." "http://www."])
|
||||
|
||||
(def web2-domain "status.app")
|
||||
(def status-web2-domain "status.app")
|
||||
|
||||
(def user-path "u#")
|
||||
(def user-with-data-path "u/")
|
||||
|
@ -32,13 +32,14 @@
|
|||
(def community-with-data-path "c/")
|
||||
(def channel-path "cc/")
|
||||
|
||||
(def web-urls (map #(str % web2-domain "/") web-prefixes))
|
||||
(def status-web-urls (map #(str % status-web2-domain "/") web-prefixes))
|
||||
|
||||
(defn path-urls
|
||||
(defn prepend-status-urls
|
||||
[path]
|
||||
(map #(str % path) web-urls))
|
||||
(map #(str % path) status-web-urls))
|
||||
|
||||
(def handled-schemes (set (into uri-schemes web-urls)))
|
||||
|
||||
(def handled-schemes (set (into uri-schemes status-web-urls)))
|
||||
|
||||
(def group-chat-extractor
|
||||
{[#"(.*)" :params] {"" :group-chat
|
||||
|
|
|
@ -260,9 +260,7 @@
|
|||
(def regx-community-universal-link #"((^https?://status.app/)|(^status-app://))c/([\x00-\x7F]+)$")
|
||||
(def regx-deep-link #"((^ethereum:.*)|(^status-app://[\x00-\x7F]+$))")
|
||||
(def regx-ens #"^(?=.{5,255}$)([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$")
|
||||
(def regx-multichain-address #"^(?:(?:eth:|arb1:|oeth:)(?=:|))*0x[0-9a-fA-F]{40}$")
|
||||
|
||||
(def regx-address-contains #"(?i)0x[a-fA-F0-9]{40}")
|
||||
(def regx-starts-with-uuid #"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
|
||||
(def regx-full-or-partial-address #"^0x[a-fA-F0-9]{1,40}$")
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[status-im.contexts.settings.wallet.saved-addresses.add-address-to-save.style :as style]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.common.validation :as validation]
|
||||
[utils.address :as utils-address]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
@ -31,7 +32,7 @@
|
|||
:own-account
|
||||
|
||||
(not
|
||||
(or (validation/eth-address? user-input)
|
||||
(or (utils-address/eip-3770-address? user-input)
|
||||
(validation/ens-name? user-input)))
|
||||
:invalid-address-or-ens)))
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
[status-im.common.scan-qr-code.view :as scan-qr-code]
|
||||
[status-im.common.validation.general :as validators]
|
||||
[status-im.contexts.communities.events]
|
||||
[status-im.contexts.wallet.common.validation :as wallet-validation]
|
||||
[status-im.contexts.wallet.wallet-connect.utils :as wc-utils]
|
||||
[status-im.feature-flags :as ff]
|
||||
[utils.address :as utils-address]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.ethereum.eip.eip681 :as eip681]
|
||||
[utils.i18n :as i18n]
|
||||
|
@ -19,19 +19,15 @@
|
|||
:theme :dark
|
||||
:text (i18n/label :t/invalid-qr)})
|
||||
|
||||
(defn- text-for-url-path?
|
||||
(defn- text-a-status-url-for-path?
|
||||
[text path]
|
||||
(some #(string/starts-with? text %) (router/path-urls path)))
|
||||
(some #(string/starts-with? text %) (router/prepend-status-urls path)))
|
||||
|
||||
(defn- extract-id
|
||||
[scanned-text]
|
||||
(let [index (string/index-of scanned-text "#")]
|
||||
(subs scanned-text (inc index))))
|
||||
|
||||
(defn eth-address?
|
||||
[scanned-text]
|
||||
(wallet-validation/eth-address? scanned-text))
|
||||
|
||||
(defn eip681-address?
|
||||
[scanned-text]
|
||||
(-> scanned-text
|
||||
|
@ -71,15 +67,15 @@
|
|||
[:wallet-connect/on-scan-connection scanned-text]
|
||||
300))
|
||||
|
||||
(defn on-qr-code-scanned
|
||||
(defn- on-qr-code-scanned
|
||||
[scanned-text]
|
||||
(cond
|
||||
(or
|
||||
(text-for-url-path? scanned-text router/community-with-data-path)
|
||||
(text-for-url-path? scanned-text router/channel-path))
|
||||
(text-a-status-url-for-path? scanned-text router/community-with-data-path)
|
||||
(text-a-status-url-for-path? scanned-text router/channel-path))
|
||||
(debounce/debounce-and-dispatch [:universal-links/handle-url scanned-text] 300)
|
||||
|
||||
(text-for-url-path? scanned-text router/user-with-data-path)
|
||||
(text-a-status-url-for-path? scanned-text router/user-with-data-path)
|
||||
(let [address (extract-id scanned-text)]
|
||||
(load-and-show-profile address))
|
||||
|
||||
|
@ -87,9 +83,9 @@
|
|||
(validators/valid-compressed-key? scanned-text))
|
||||
(load-and-show-profile scanned-text)
|
||||
|
||||
(eth-address? scanned-text)
|
||||
(do
|
||||
(debounce/debounce-and-dispatch [:generic-scanner/scan-success scanned-text] 300)
|
||||
(utils-address/supported-address? scanned-text)
|
||||
(when-let [address (utils-address/supported-address->status-address scanned-text)]
|
||||
(debounce/debounce-and-dispatch [:generic-scanner/scan-success address] 300)
|
||||
(debounce/debounce-and-dispatch [:navigate-change-tab :wallet-stack] 300))
|
||||
|
||||
(eip681-address? scanned-text)
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.common.floating-button-page.view :as floating-button-page]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.add-account.add-address-to-watch.style :as style]
|
||||
[status-im.contexts.wallet.common.validation :as validation]
|
||||
[status-im.subs.wallet.add-account.address-to-watch]
|
||||
[utils.address :as utils-address]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
@ -20,13 +20,9 @@
|
|||
(or (nil? user-input) (= user-input "")) nil
|
||||
(contains? known-addresses user-input) (i18n/label :t/address-already-in-use)
|
||||
(not
|
||||
(or (validation/eth-address? user-input)
|
||||
(or (utils-address/supported-address? user-input)
|
||||
(validation/ens-name? user-input))) (i18n/label :t/invalid-address)))
|
||||
|
||||
(defn- extract-address
|
||||
[scanned-text]
|
||||
(re-find constants/regx-address-contains scanned-text))
|
||||
|
||||
(defn- address-input
|
||||
[{:keys [input-value validation-msg validate clear-input]}]
|
||||
(let [scanned-address (rf/sub [:wallet/scanned-address])
|
||||
|
@ -141,10 +137,12 @@
|
|||
(= activity-state :scanning)
|
||||
(not validated-address))
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:navigate-to
|
||||
:screen/wallet.confirm-address-to-watch
|
||||
{:address (extract-address
|
||||
validated-address)}])
|
||||
(rf/dispatch
|
||||
[:navigate-to
|
||||
:screen/wallet.confirm-address-to-watch
|
||||
{:address
|
||||
(utils-address/extract-address-without-chains-info
|
||||
validated-address)}])
|
||||
(clear-input))
|
||||
:container-style {:z-index 2}}
|
||||
(i18n/label :t/continue)]}
|
||||
|
|
|
@ -1,28 +1,11 @@
|
|||
(ns status-im.contexts.wallet.common.scan-account.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[status-im.common.scan-qr-code.view :as scan-qr-code]
|
||||
[status-im.constants :as constants]
|
||||
[utils.address :as utils-address]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def ^:private supported-networks #{:eth :arb1 :oeth})
|
||||
|
||||
(defn- contains-supported-address?
|
||||
[s]
|
||||
(let [address? (boolean (re-find constants/regx-address-contains s))
|
||||
networks (when address?
|
||||
(as-> s $
|
||||
(string/split $ ":")
|
||||
(butlast $)))
|
||||
supported? (every? supported-networks (map keyword networks))]
|
||||
(and address? supported?)))
|
||||
|
||||
(defn- extract-address
|
||||
[scanned-text]
|
||||
(first (re-seq constants/regx-multichain-address scanned-text)))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [on-result]} (rf/sub [:get-screen-params])]
|
||||
|
@ -30,9 +13,9 @@
|
|||
{:title (i18n/label :t/scan-qr)
|
||||
:subtitle (i18n/label :t/scan-an-address-qr-code)
|
||||
:error-message (i18n/label :t/oops-this-qr-does-not-contain-an-address)
|
||||
:validate-fn #(contains-supported-address? %)
|
||||
:validate-fn #(utils-address/supported-address? %)
|
||||
:on-success-scan (fn [result]
|
||||
(let [address (extract-address result)]
|
||||
(let [address (utils-address/supported-address->status-address result)]
|
||||
(when on-result (on-result address))
|
||||
(debounce/debounce-and-dispatch
|
||||
[:wallet/scan-address-success address]
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
(:require [status-im.constants :as constants]))
|
||||
|
||||
(defn ens-name? [s] (boolean (re-find constants/regx-ens s)))
|
||||
(defn eth-address? [s] (re-find constants/regx-multichain-address s))
|
||||
(defn private-key?
|
||||
[s]
|
||||
(or (re-find constants/regx-private-key-hex s)
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.common.utils.networks :as network-utils]
|
||||
[status-im.contexts.wallet.common.validation :as validation]
|
||||
[status-im.contexts.wallet.item-types :as types]
|
||||
[status-im.contexts.wallet.send.select-address.style :as style]
|
||||
[status-im.contexts.wallet.send.select-address.tabs.view :as tabs]
|
||||
[status-im.feature-flags :as ff]
|
||||
[status-im.setup.hot-reload :as hot-reload]
|
||||
[utils.address :as utils-address]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
@ -33,9 +33,9 @@
|
|||
[address]
|
||||
(debounce/debounce-and-dispatch
|
||||
(cond
|
||||
(<= (count address) 0) [:wallet/address-validation-failed address]
|
||||
(validation/eth-address? address) [:wallet/address-validation-success address]
|
||||
:else [:wallet/address-validation-failed address])
|
||||
(<= (count address) 0) [:wallet/address-validation-failed address]
|
||||
(utils-address/eip-3770-address? address) [:wallet/address-validation-success address]
|
||||
:else [:wallet/address-validation-failed address])
|
||||
300))
|
||||
|
||||
(defn- address-input
|
||||
|
@ -58,7 +58,7 @@
|
|||
{:on-result on-result}]))
|
||||
:ens-regex constants/regx-ens
|
||||
:scanned-value (or (when recipient-plain-address? send-address) scanned-address)
|
||||
:address-regex constants/regx-multichain-address
|
||||
:address-regex utils-address/regx-eip-3770-address
|
||||
:on-detect-address (fn [address]
|
||||
(when (or (= current-screen-id :screen/wallet.select-address)
|
||||
(= current-screen-id :screen/wallet.scan-address))
|
||||
|
|
|
@ -4,7 +4,12 @@
|
|||
[native-module.core :as native-module]
|
||||
[utils.ethereum.eip.eip55 :as eip55]))
|
||||
|
||||
|
||||
(def hex-prefix "0x")
|
||||
;; EIP-3770 is a format used by Status and described here: https://eips.ethereum.org/EIPS/eip-3770
|
||||
(def regx-eip-3770-address #"^(?:(?:eth:|arb1:|oeth:)(?=:|))*0x[0-9a-fA-F]{40}$")
|
||||
(def regx-metamask-address #"^ethereum:(0x[0-9a-fA-F]{40})(?:@(0x1|0xa|0xa4b1))?$")
|
||||
(def regx-address-contains #"(?i)0x[a-fA-F0-9]{40}")
|
||||
|
||||
(defn normalized-hex
|
||||
[hex]
|
||||
|
@ -77,3 +82,49 @@
|
|||
[value]
|
||||
(when value
|
||||
(str (subs value 0 5) "..." (subs value (- (count value) 3) (count value)))))
|
||||
|
||||
(defn eip-155-suffix->eip-3770-prefix
|
||||
[eip-155-suffix]
|
||||
(case eip-155-suffix
|
||||
"0x1" "eth:"
|
||||
"0xa4b1" "arb1:"
|
||||
"0xa" "oeth:"
|
||||
nil))
|
||||
|
||||
(defn split-metamask-address
|
||||
[address]
|
||||
(re-find regx-metamask-address address))
|
||||
|
||||
(defn metamask-address?
|
||||
[address]
|
||||
(boolean (split-metamask-address address)))
|
||||
|
||||
(defn eip-3770-address?
|
||||
"Checks if address follows EIP-3770 format which is default for Status"
|
||||
[s]
|
||||
(re-find regx-eip-3770-address s))
|
||||
|
||||
(defn supported-address?
|
||||
[s]
|
||||
(boolean (or (eip-3770-address? s)
|
||||
(metamask-address? s))))
|
||||
|
||||
(defn metamask-address->status-address
|
||||
[metamask-address]
|
||||
(when-let [[_ address metamask-network-suffix] (split-metamask-address metamask-address)]
|
||||
(if-let [status-network-prefix (eip-155-suffix->eip-3770-prefix metamask-network-suffix)]
|
||||
(str status-network-prefix address)
|
||||
address)))
|
||||
|
||||
(defn supported-address->status-address
|
||||
[address]
|
||||
(cond
|
||||
(eip-3770-address? address)
|
||||
address
|
||||
|
||||
(metamask-address? address)
|
||||
(metamask-address->status-address address)))
|
||||
|
||||
(defn extract-address-without-chains-info
|
||||
[address]
|
||||
(re-find regx-address-contains address))
|
||||
|
|
|
@ -39,3 +39,50 @@
|
|||
|
||||
(testing "Ensure the function returns nil when given nil"
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url nil)))))
|
||||
|
||||
(def valid-metamask-addresses
|
||||
["ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0x1"
|
||||
"ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa4b1"
|
||||
"ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa"
|
||||
"ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2"])
|
||||
|
||||
(def invalid-metamask-addresses
|
||||
["ethe:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0x1"
|
||||
":0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa4b1"
|
||||
"0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa"
|
||||
"ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0x1d"
|
||||
"ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd20xa4b1"
|
||||
"ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2:0xa"])
|
||||
|
||||
(def metamask-to-status
|
||||
[{:metamask "ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0x1"
|
||||
:status "eth:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2"}
|
||||
{:metamask "ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa4b1"
|
||||
:status "arb1:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2"}
|
||||
{:metamask "ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa"
|
||||
:status "oeth:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2"}
|
||||
{:metamask "ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2"
|
||||
:status "0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2"}
|
||||
{:metamask "ethe:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0x1" :status nil}
|
||||
{:metamask ":0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa4b1" :status nil}
|
||||
{:metamask "0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0xa" :status nil}
|
||||
{:metamask "ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2@0x1d" :status nil}
|
||||
{:metamask "ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd20xa4b1" :status nil}
|
||||
{:metamask "ethereum:0x38cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2:0xa" :status nil}])
|
||||
|
||||
(deftest metamask-address?-test
|
||||
(testing "Check valid metamask addresses"
|
||||
(dorun
|
||||
(for [address valid-metamask-addresses]
|
||||
(is (utils.address/metamask-address? address)))))
|
||||
(testing "Check invalid metamask addresses"
|
||||
(dorun
|
||||
(for [address invalid-metamask-addresses]
|
||||
(is (not (utils.address/metamask-address? address)))))))
|
||||
|
||||
(deftest metamask-address->status-address-test
|
||||
(testing "Check metamask to status address conversion is valid"
|
||||
(dorun
|
||||
(for [{metamask-address :metamask
|
||||
status-address :status} metamask-to-status]
|
||||
(is (= status-address (utils.address/metamask-address->status-address metamask-address)))))))
|
||||
|
|
Loading…
Reference in New Issue