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

Metamask address support #20844

Merged
merged 12 commits into from
Aug 29, 2024
11 changes: 6 additions & 5 deletions src/status_im/common/router.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@

(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/")
(def community-path "c#")
(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
Expand Down
2 changes: 0 additions & 2 deletions src/status_im/constants.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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}$")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand All @@ -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)))

Expand Down
24 changes: 10 additions & 14 deletions src/status_im/contexts/shell/qr_reader/view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -71,25 +67,25 @@
[: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))

(or (validators/valid-public-key? scanned-text)
(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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand All @@ -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])
Expand Down Expand Up @@ -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)]}
Expand Down
23 changes: 3 additions & 20 deletions src/status_im/contexts/wallet/common/scan_account/view.cljs
Original file line number Diff line number Diff line change
@@ -1,38 +1,21 @@
(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])]
[scan-qr-code/view
{: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]
Expand Down
1 change: 0 additions & 1 deletion src/status_im/contexts/wallet/common/validation.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions src/status_im/contexts/wallet/send/select_address/view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand All @@ -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]
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

:else [:wallet/address-validation-failed address])
300))

(defn- address-input
Expand All @@ -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))
Expand Down
51 changes: 51 additions & 0 deletions src/utils/address.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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))
47 changes: 47 additions & 0 deletions src/utils/address_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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)))))))