mirror of
https://github.com/status-im/status-react.git
synced 2025-01-11 03:26:31 +00:00
[ISSUE #2391] Added support for ERC681
This commit is contained in:
parent
63848efc20
commit
7e6507be7c
@ -336,6 +336,7 @@
|
|||||||
:wallet-choose-from-contacts "Choose From Contacts"
|
:wallet-choose-from-contacts "Choose From Contacts"
|
||||||
:wallet-address-from-clipboard "Use Address From Clipboard"
|
:wallet-address-from-clipboard "Use Address From Clipboard"
|
||||||
:wallet-invalid-address "Invalid address: \n {{data}}"
|
:wallet-invalid-address "Invalid address: \n {{data}}"
|
||||||
|
:wallet-invalid-chain-id "Network does not match: \n {{data}}"
|
||||||
:wallet-browse-photos "Browse Photos"
|
:wallet-browse-photos "Browse Photos"
|
||||||
:validation-amount-invalid-number "Amount is not a valid number"
|
:validation-amount-invalid-number "Amount is not a valid number"
|
||||||
:validation-amount-is-too-precise "Amount is too precise. The smallest unit you can send is 1 Wei (1x10^-18 ETH)"
|
:validation-amount-is-too-precise "Amount is too precise. The smallest unit you can send is 1 Wei (1x10^-18 ETH)"
|
||||||
|
@ -7,13 +7,15 @@
|
|||||||
[status-im.ui.components.status-bar :refer [status-bar]]
|
[status-im.ui.components.status-bar :refer [status-bar]]
|
||||||
[status-im.i18n :refer [label]]
|
[status-im.i18n :refer [label]]
|
||||||
[status-im.ui.screens.profile.qr-code.styles :as styles]
|
[status-im.ui.screens.profile.qr-code.styles :as styles]
|
||||||
[status-im.utils.eip.eip67 :as eip67])
|
[status-im.utils.money :as money]
|
||||||
|
[status-im.utils.eip.eip681 :as eip681])
|
||||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||||
|
|
||||||
(defview qr-code-view []
|
(defview qr-code-view []
|
||||||
(letsubs [{:keys [photo-path address name]} [:get-in [:qr-modal :contact]]
|
(letsubs [{:keys [photo-path address name]} [:get-in [:qr-modal :contact]]
|
||||||
{:keys [qr-source qr-value amount? dimensions]} [:get :qr-modal]
|
{:keys [qr-source qr-value amount? dimensions]} [:get :qr-modal]
|
||||||
{:keys [amount]} [:get :contacts/click-params]]
|
{:keys [amount]} [:get :contacts/click-params]
|
||||||
|
chain-id [:get-network-id]]
|
||||||
[react/view styles/wallet-qr-code
|
[react/view styles/wallet-qr-code
|
||||||
[status-bar {:type :modal}]
|
[status-bar {:type :modal}]
|
||||||
[react/view styles/account-toolbar
|
[react/view styles/account-toolbar
|
||||||
@ -35,7 +37,7 @@
|
|||||||
:height (.-height layout)}]))}
|
:height (.-height layout)}]))}
|
||||||
(when (:width dimensions)
|
(when (:width dimensions)
|
||||||
[react/view {:style (styles/qr-code-container dimensions)}
|
[react/view {:style (styles/qr-code-container dimensions)}
|
||||||
(when-let [value (eip67/generate-uri qr-value (when amount? {:value amount}))]
|
(when-let [value (eip681/generate-uri qr-value (merge {:chain-id chain-id} (when amount? {:value (money/str->wei amount)})))]
|
||||||
[qr-code {:value value
|
[qr-code {:value value
|
||||||
:size (- (min (:width dimensions)
|
:size (- (min (:width dimensions)
|
||||||
(:height dimensions))
|
(:height dimensions))
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
[status-im.utils.handlers :as u :refer [register-handler]]
|
[status-im.utils.handlers :as u :refer [register-handler]]
|
||||||
[status-im.utils.utils :as utils]
|
[status-im.utils.utils :as utils]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.utils.eip.eip67 :as eip67]))
|
[status-im.utils.eip.eip681 :as eip681]))
|
||||||
|
|
||||||
(defmethod nav/preload-data! :qr-scanner
|
(defmethod nav/preload-data! :qr-scanner
|
||||||
[db [_ _ identifier]]
|
[db [_ _ identifier]]
|
||||||
@ -35,7 +35,7 @@
|
|||||||
(defn handle-qr-request
|
(defn handle-qr-request
|
||||||
[db [_ context data]]
|
[db [_ context data]]
|
||||||
(when-let [handler (get-in db [:qr-codes context])]
|
(when-let [handler (get-in db [:qr-codes context])]
|
||||||
(re-frame/dispatch [handler context (:address (eip67/parse-uri data))])))
|
(re-frame/dispatch [handler context (:address (eip681/parse-uri data))])))
|
||||||
|
|
||||||
(defn clear-qr-request [db [_ context]]
|
(defn clear-qr-request [db [_ context]]
|
||||||
(-> db
|
(-> db
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
(ns status-im.ui.screens.wallet.choose-recipient.events
|
(ns status-im.ui.screens.wallet.choose-recipient.events
|
||||||
(:require [status-im.i18n :as i18n]
|
(:require [status-im.constants :as constants]
|
||||||
[status-im.utils.eip.eip67 :as eip67]
|
[status-im.i18n :as i18n]
|
||||||
|
[status-im.utils.eip.eip681 :as eip681]
|
||||||
[status-im.utils.handlers :as handlers]))
|
[status-im.utils.handlers :as handlers]))
|
||||||
|
|
||||||
(handlers/register-handler-db
|
(handlers/register-handler-db
|
||||||
@ -17,22 +18,25 @@
|
|||||||
amount (assoc :amount amount))))
|
amount (assoc :amount amount))))
|
||||||
|
|
||||||
(defn- extract-details
|
(defn- extract-details
|
||||||
"First try to parse as EIP67 URI, if not assume this is an address directly.
|
"First try to parse as EIP681 URI, if not assume this is an address directly.
|
||||||
Returns a map containing at least the `address` key"
|
Returns a map containing at least the `address` and `chain-id` keys"
|
||||||
[s]
|
[s network]
|
||||||
(or (eip67/parse-uri s) {:address s}))
|
(or (eip681/parse-uri s) {:address s :chain-id network}))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:choose-recipient
|
:choose-recipient
|
||||||
(fn [{{:keys [web3] :as db} :db} [_ data name]]
|
(fn [{{:keys [web3 network] :as db} :db} [_ data name]]
|
||||||
(let [{:keys [view-id]} db
|
(let [{:keys [view-id]} db
|
||||||
m (extract-details data)
|
current-network-id (get-in constants/default-networks [network :raw-config :NetworkId])
|
||||||
address (:address m)
|
{:keys [address chain-id] :as m} (extract-details data current-network-id)
|
||||||
;; isAddress works with or without address with leading '0x'
|
;; isAddress works with or without address with leading '0x'
|
||||||
valid-address? (.isAddress web3 address)]
|
valid-address? (.isAddress web3 address)
|
||||||
|
valid-network? (boolean (= current-network-id chain-id))]
|
||||||
(cond-> {:db db}
|
(cond-> {:db db}
|
||||||
valid-address? (update :db #(choose-address-and-name % address name (:value m)))
|
(and valid-address? (= :choose-recipient view-id)) (assoc :dispatch [:navigate-back])
|
||||||
(not valid-address?) (assoc :show-error (i18n/label :t/wallet-invalid-address {:data data}))))))
|
(and valid-network? valid-address?) (update :db #(choose-address-and-name % address name (eip681/parse-value m)))
|
||||||
|
(not valid-address?) (assoc :show-error (i18n/label :t/wallet-invalid-address {:data data}))
|
||||||
|
(and valid-address? (not valid-network?)) (assoc :show-error (i18n/label :t/wallet-invalid-chain-id {:data data}))))))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:wallet-open-send-transaction
|
:wallet-open-send-transaction
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
[react/touchable-highlight {:style (styles/recipient-touchable true)
|
[react/touchable-highlight {:style (styles/recipient-touchable true)
|
||||||
:on-press #(react/get-from-clipboard
|
:on-press #(react/get-from-clipboard
|
||||||
(fn [clipboard]
|
(fn [clipboard]
|
||||||
(re-frame/dispatch [:choose-recipient (string/trim clipboard) nil])))}
|
(re-frame/dispatch [:choose-recipient (string/trim-newline clipboard) nil])))}
|
||||||
[react/view {:style styles/recipient-button}
|
[react/view {:style styles/recipient-button}
|
||||||
[react/text {:style styles/recipient-button-text}
|
[react/text {:style styles/recipient-button-text}
|
||||||
(i18n/label :t/wallet-address-from-clipboard)]
|
(i18n/label :t/wallet-address-from-clipboard)]
|
||||||
|
@ -29,12 +29,16 @@
|
|||||||
(let [amount-splited (string/split amount #"[.]")]
|
(let [amount-splited (string/split amount #"[.]")]
|
||||||
(and (= (count amount-splited) 2) (> (count (last amount-splited)) 18))))
|
(and (= (count amount-splited) 2) (> (count (last amount-splited)) 18))))
|
||||||
|
|
||||||
(defn get-amount-validation-error [amount]
|
(defn parse-amount [amount]
|
||||||
(when-not (empty-amount? amount)
|
(when-not (empty-amount? amount)
|
||||||
(let [normalized-amount (money/normalize amount)]
|
(let [normalized-amount (money/normalize amount)
|
||||||
|
value (money/bignumber normalized-amount)]
|
||||||
(cond
|
(cond
|
||||||
(not (money/valid? normalized-amount))
|
(not (money/valid? value))
|
||||||
(i18n/label :t/validation-amount-invalid-number)
|
{:error (i18n/label :t/validation-amount-invalid-number)}
|
||||||
|
|
||||||
(too-precise-amount? normalized-amount)
|
(too-precise-amount? normalized-amount)
|
||||||
(i18n/label :t/validation-amount-is-too-precise)))))
|
{:error (i18n/label :t/validation-amount-is-too-precise)}
|
||||||
|
|
||||||
|
:else
|
||||||
|
{:value value}))))
|
@ -1,8 +1,9 @@
|
|||||||
(ns status-im.ui.screens.wallet.request.db
|
(ns status-im.ui.screens.wallet.request.db
|
||||||
(:require-macros [status-im.utils.db :refer [allowed-keys]])
|
(:require-macros [status-im.utils.db :refer [allowed-keys]])
|
||||||
(:require [cljs.spec.alpha :as spec]))
|
(:require [cljs.spec.alpha :as spec]
|
||||||
|
[status-im.utils.money :as money]))
|
||||||
|
|
||||||
(spec/def ::amount (spec/nilable string?))
|
(spec/def ::amount (spec/nilable money/valid?))
|
||||||
(spec/def ::amount-error (spec/nilable string?))
|
(spec/def ::amount-error (spec/nilable string?))
|
||||||
|
|
||||||
(spec/def :wallet/request-transaction (allowed-keys
|
(spec/def :wallet/request-transaction (allowed-keys
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
(ns status-im.ui.screens.wallet.request.events
|
(ns status-im.ui.screens.wallet.request.events
|
||||||
(:require
|
(:require [re-frame.core :as re-frame]
|
||||||
[status-im.utils.handlers :as handlers]
|
|
||||||
[status-im.ui.screens.wallet.db :as wallet.db]
|
[status-im.ui.screens.wallet.db :as wallet.db]
|
||||||
[re-frame.core :as re-frame]))
|
[status-im.utils.handlers :as handlers]
|
||||||
|
[status-im.utils.money :as money]))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
::wallet-send-chat-request
|
::wallet-send-chat-request
|
||||||
@ -16,12 +16,13 @@
|
|||||||
(fn [{{:wallet/keys [request-transaction]} :db} [_ {:keys [whisper-identity]}]]
|
(fn [{{:wallet/keys [request-transaction]} :db} [_ {:keys [whisper-identity]}]]
|
||||||
{:dispatch-n [[:navigate-back]
|
{:dispatch-n [[:navigate-back]
|
||||||
[:navigate-to-clean :chat-list]
|
[:navigate-to-clean :chat-list]
|
||||||
[:add-chat-loaded-event whisper-identity [::wallet-send-chat-request (:amount request-transaction)]]
|
[:add-chat-loaded-event whisper-identity [::wallet-send-chat-request (str (:amount request-transaction))]]
|
||||||
[:start-chat whisper-identity]]}))
|
[:start-chat whisper-identity]]}))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:wallet-validate-request-amount
|
:wallet.request/set-and-validate-amount
|
||||||
(fn [{{:wallet/keys [request-transaction] :as db} :db} _]
|
(fn [{:keys [db]} [_ amount]]
|
||||||
(let [amount (:amount request-transaction)
|
(let [{:keys [value error]} (wallet.db/parse-amount amount)]
|
||||||
error (wallet.db/get-amount-validation-error amount)]
|
{:db (-> db
|
||||||
{:db (assoc-in db [:wallet/request-transaction :amount-error] error)})))
|
(assoc-in [:wallet/request-transaction :amount] (money/ether->wei value))
|
||||||
|
(assoc-in [:wallet/request-transaction :amount-error] error))})))
|
||||||
|
@ -8,4 +8,4 @@
|
|||||||
(fn [[amount amount-error]]
|
(fn [[amount amount-error]]
|
||||||
(and
|
(and
|
||||||
(nil? amount-error)
|
(nil? amount-error)
|
||||||
(not (nil? amount)) (not= amount ""))))
|
(not (nil? amount)))))
|
@ -8,14 +8,15 @@
|
|||||||
[status-im.ui.components.toolbar.view :as toolbar]
|
[status-im.ui.components.toolbar.view :as toolbar]
|
||||||
[status-im.ui.components.status-bar :as status-bar]
|
[status-im.ui.components.status-bar :as status-bar]
|
||||||
[status-im.ui.screens.wallet.styles :as wallet.styles]
|
[status-im.ui.screens.wallet.styles :as wallet.styles]
|
||||||
|
[status-im.ui.components.common.common :as common]
|
||||||
[status-im.ui.components.icons.vector-icons :as vi]
|
[status-im.ui.components.icons.vector-icons :as vi]
|
||||||
[status-im.ui.screens.wallet.components.views :as components]
|
[status-im.ui.screens.wallet.components.views :as components]
|
||||||
[status-im.ui.screens.wallet.request.styles :as styles]
|
[status-im.ui.screens.wallet.request.styles :as styles]
|
||||||
[status-im.ui.components.styles :as components.styles]
|
[status-im.ui.components.styles :as components.styles]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.utils.platform :as platform]
|
[status-im.utils.platform :as platform]
|
||||||
[status-im.utils.eip.eip67 :as eip67]
|
[status-im.utils.eip.eip681 :as eip681]
|
||||||
[status-im.ui.components.common.common :as common]))
|
[status-im.utils.money :as money]))
|
||||||
|
|
||||||
(defn toolbar-view []
|
(defn toolbar-view []
|
||||||
[toolbar/toolbar {:style wallet.styles/toolbar :hide-border? true}
|
[toolbar/toolbar {:style wallet.styles/toolbar :hide-border? true}
|
||||||
@ -30,9 +31,10 @@
|
|||||||
:params {:hide-actions? true}}]))
|
:params {:hide-actions? true}}]))
|
||||||
|
|
||||||
(views/defview qr-code [amount]
|
(views/defview qr-code [amount]
|
||||||
(views/letsubs [account [:get-current-account]]
|
(views/letsubs [account [:get-current-account]
|
||||||
|
chain-id [:get-network-id]]
|
||||||
[components.qr-code/qr-code
|
[components.qr-code/qr-code
|
||||||
{:value (eip67/generate-uri (:address account) (when amount {:value amount}))
|
{:value (eip681/generate-uri (:address account) (merge {:chain-id chain-id} (when amount {:value amount})))
|
||||||
:size 256}]))
|
:size 256}]))
|
||||||
|
|
||||||
(views/defview request-transaction []
|
(views/defview request-transaction []
|
||||||
@ -56,9 +58,7 @@
|
|||||||
[components/amount-input
|
[components/amount-input
|
||||||
{:error amount-error
|
{:error amount-error
|
||||||
:input-options {:on-focus (fn [] (when @scroll (js/setTimeout #(.scrollToEnd @scroll) 100)))
|
:input-options {:on-focus (fn [] (when @scroll (js/setTimeout #(.scrollToEnd @scroll) 100)))
|
||||||
:on-change-text
|
:on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount %])}}]
|
||||||
#(do (re-frame/dispatch [:set-in [:wallet/request-transaction :amount] %])
|
|
||||||
(re-frame/dispatch [:wallet-validate-request-amount]))}}]
|
|
||||||
[react/view wallet.styles/choose-currency-container
|
[react/view wallet.styles/choose-currency-container
|
||||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||||
[components/separator]
|
[components/separator]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
(ns status-im.ui.screens.wallet.send.db
|
(ns status-im.ui.screens.wallet.send.db
|
||||||
(:require-macros [status-im.utils.db :refer [allowed-keys]])
|
(:require-macros [status-im.utils.db :refer [allowed-keys]])
|
||||||
(:require [cljs.spec.alpha :as spec]))
|
(:require [cljs.spec.alpha :as spec]
|
||||||
|
[status-im.utils.money :as money]))
|
||||||
|
|
||||||
(spec/def ::amount (spec/nilable string?))
|
(spec/def ::amount (spec/nilable money/valid?))
|
||||||
(spec/def ::to-address (spec/nilable string?))
|
(spec/def ::to-address (spec/nilable string?))
|
||||||
(spec/def ::to-name (spec/nilable string?))
|
(spec/def ::to-name (spec/nilable string?))
|
||||||
(spec/def ::amount-error (spec/nilable string?))
|
(spec/def ::amount-error (spec/nilable string?))
|
||||||
|
@ -52,11 +52,11 @@
|
|||||||
;;;; Handlers
|
;;;; Handlers
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:wallet/set-and-validate-amount
|
:wallet.send/set-and-validate-amount
|
||||||
(fn [{:keys [db]} [_ amount]]
|
(fn [{:keys [db]} [_ amount]]
|
||||||
(let [error (wallet.db/get-amount-validation-error amount)]
|
(let [{:keys [value error]} (wallet.db/parse-amount amount)]
|
||||||
{:db (-> db
|
{:db (-> db
|
||||||
(assoc-in [:wallet :send-transaction :amount] amount)
|
(assoc-in [:wallet :send-transaction :amount] (money/ether->wei value))
|
||||||
(assoc-in [:wallet :send-transaction :amount-error] error))})))
|
(assoc-in [:wallet :send-transaction :amount-error] error))})))
|
||||||
|
|
||||||
(def ^:private clear-send-properties {:id nil
|
(def ^:private clear-send-properties {:id nil
|
||||||
@ -174,7 +174,7 @@
|
|||||||
::send-transaction {:web3 web3
|
::send-transaction {:web3 web3
|
||||||
:from (get-in accounts [current-account-id :address])
|
:from (get-in accounts [current-account-id :address])
|
||||||
:to to-address
|
:to to-address
|
||||||
:value (money/to-wei (money/normalize amount))}}))))
|
:value amount}}))))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:wallet/sign-transaction-modal
|
:wallet/sign-transaction-modal
|
||||||
|
@ -75,9 +75,8 @@
|
|||||||
(fn [[{:keys [value to] :as transaction} contacts balance]]
|
(fn [[{:keys [value to] :as transaction} contacts balance]]
|
||||||
(when transaction
|
(when transaction
|
||||||
(let [contact (contacts (utils.hex/normalize-hex to))
|
(let [contact (contacts (utils.hex/normalize-hex to))
|
||||||
amount (str (.toFixed (money/wei->ether value)))
|
sufficient-funds? (money/sufficient-funds? value balance)]
|
||||||
sufficient-funds? (money/sufficient-funds? amount balance)]
|
|
||||||
(cond-> (assoc transaction
|
(cond-> (assoc transaction
|
||||||
:amount amount
|
:amount value
|
||||||
:sufficient-funds? sufficient-funds?)
|
:sufficient-funds? sufficient-funds?)
|
||||||
contact (assoc :to-name (:name contact)))))))
|
contact (assoc :to-name (:name contact)))))))
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
(and
|
(and
|
||||||
(nil? amount-error)
|
(nil? amount-error)
|
||||||
(not (nil? to-address)) (not= to-address "")
|
(not (nil? to-address)) (not= to-address "")
|
||||||
(not (nil? amount)) (not= amount "")))
|
(not (nil? amount))))
|
||||||
|
|
||||||
;; "Sign Later" and "Sign Transaction >" buttons
|
;; "Sign Later" and "Sign Transaction >" buttons
|
||||||
(defn- sign-buttons [amount-error to-address amount sufficient-funds? sign-later-handler]
|
(defn- sign-buttons [amount-error to-address amount sufficient-funds? sign-later-handler]
|
||||||
@ -126,8 +126,8 @@
|
|||||||
(when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds)))
|
(when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds)))
|
||||||
:input-options {:auto-focus true
|
:input-options {:auto-focus true
|
||||||
:on-focus (fn [] (when @scroll (js/setTimeout #(.scrollToEnd @scroll) 100)))
|
:on-focus (fn [] (when @scroll (js/setTimeout #(.scrollToEnd @scroll) 100)))
|
||||||
:default-value amount
|
:default-value (str (money/wei->ether amount))
|
||||||
:on-change-text #(re-frame/dispatch [:wallet/set-and-validate-amount %])}}]
|
:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount %])}}]
|
||||||
[react/view wallet.styles/choose-currency-container
|
[react/view wallet.styles/choose-currency-container
|
||||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||||
[components/separator]
|
[components/separator]
|
||||||
@ -168,7 +168,7 @@
|
|||||||
[components/amount-input
|
[components/amount-input
|
||||||
{:error (when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds))
|
{:error (when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds))
|
||||||
:disabled? true
|
:disabled? true
|
||||||
:input-options {:default-value amount}}]
|
:input-options {:default-value (str (money/wei->ether amount))}}]
|
||||||
[react/view wallet.styles/choose-currency-container
|
[react/view wallet.styles/choose-currency-container
|
||||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||||
[components/separator]
|
[components/separator]
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
(ns status-im.utils.eip.eip67
|
|
||||||
"Utility function related to [EIP67](https://github.com/ethereum/EIPs/issues/67)"
|
|
||||||
(:require [clojure.string :as string]))
|
|
||||||
|
|
||||||
(def scheme "ethereum")
|
|
||||||
(def scheme-separator ":")
|
|
||||||
(def parameters-separator "?")
|
|
||||||
(def parameter-separator "&")
|
|
||||||
(def key-value-separator "=")
|
|
||||||
|
|
||||||
(def key-value-format (str "([^" parameter-separator key-value-separator "]+)"))
|
|
||||||
(def parameters-pattern (re-pattern (str key-value-format key-value-separator key-value-format)))
|
|
||||||
|
|
||||||
(defn- parse-parameters [s]
|
|
||||||
(when s
|
|
||||||
(into {} (for [[_ k v] (re-seq parameters-pattern s)]
|
|
||||||
[(keyword k) v]))))
|
|
||||||
|
|
||||||
(defn parse-uri
|
|
||||||
"Parse a EIP 67 URI as a map of keyword / strings. Parsed map will contain at least the key `address`.
|
|
||||||
Invalid URI will be parsed as `nil`."
|
|
||||||
[s]
|
|
||||||
(when (and s (string/starts-with? s scheme))
|
|
||||||
(let [[address parameters] (string/split (string/replace s (str scheme scheme-separator) "") parameters-separator)]
|
|
||||||
(when-not (zero? (count address))
|
|
||||||
(merge
|
|
||||||
{:address address}
|
|
||||||
(parse-parameters parameters))))))
|
|
||||||
|
|
||||||
(defn- generate-parameter-string [m]
|
|
||||||
(string/join parameter-separator (for [[k v] m]
|
|
||||||
(str (name k) key-value-separator v))))
|
|
||||||
|
|
||||||
(defn generate-uri
|
|
||||||
"Generate a EIP 67 URI based on `address` and an optional map (keyword, string) of extra properties.
|
|
||||||
No validation of address format is performed."
|
|
||||||
([address] (generate-uri address nil))
|
|
||||||
([address m]
|
|
||||||
(when address
|
|
||||||
(let [parameters (into {} (filter second m))] ;; filter nil values
|
|
||||||
(str scheme scheme-separator address
|
|
||||||
(when-not (empty? parameters)
|
|
||||||
(str parameters-separator (generate-parameter-string parameters))))))))
|
|
||||||
|
|
66
src/status_im/utils/eip/eip681.cljs
Normal file
66
src/status_im/utils/eip/eip681.cljs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
(ns status-im.utils.eip.eip681
|
||||||
|
"Utility function related to [EIP681](https://github.com/ethereum/EIPs/issues/681)
|
||||||
|
|
||||||
|
This EIP standardize how ethereum payment request can be represented as URI (say to embed them in a QR code).
|
||||||
|
|
||||||
|
e.g. ethereum:0x1234@1/transfer?to=0x5678&value=1e18&gas=5000"
|
||||||
|
(:require [clojure.string :as string]
|
||||||
|
[status-im.constants :as constants]
|
||||||
|
[status-im.utils.money :as money]))
|
||||||
|
|
||||||
|
(def scheme "ethereum")
|
||||||
|
(def scheme-separator ":")
|
||||||
|
(def chain-id-separator "@")
|
||||||
|
(def function-name-separator "/")
|
||||||
|
(def query-separator "?")
|
||||||
|
(def parameter-separator "&")
|
||||||
|
(def key-value-separator "=")
|
||||||
|
|
||||||
|
(def uri-pattern (re-pattern (str scheme scheme-separator "([^" query-separator "]*)(?:\\" query-separator "(.*))?")))
|
||||||
|
(def authority-path-pattern (re-pattern (str "^([^" chain-id-separator "]*)(?:" chain-id-separator "(\\d))?(?:" function-name-separator "(\\w*))?")))
|
||||||
|
(def key-value-format (str "([^" parameter-separator key-value-separator "]+)"))
|
||||||
|
(def query-pattern (re-pattern (str key-value-format key-value-separator key-value-format)))
|
||||||
|
|
||||||
|
(defn- parse-query [s]
|
||||||
|
(when s
|
||||||
|
(into {} (for [[_ k v] (re-seq query-pattern s)]
|
||||||
|
[(keyword k) v]))))
|
||||||
|
|
||||||
|
(defn parse-value [{:keys [value function-name]}]
|
||||||
|
"Takes a map as returned by `parse-uri` and returns value as BigNumber"
|
||||||
|
(when (and value (not function-name)) ;; TODO(jeluard) Add ERC20 support
|
||||||
|
(let [eth? (string/ends-with? value "ETH")
|
||||||
|
n (money/bignumber (string/replace value "ETH" ""))]
|
||||||
|
(if eth? (.times n 1e18) n))))
|
||||||
|
|
||||||
|
(defn parse-uri
|
||||||
|
"Parse a EIP 681 URI as a map (keyword / strings). Parsed map will contain at least the key `address`.
|
||||||
|
Note that values are not decoded and you might need to rely on specific methods for some fields (parse-value, parse-number).
|
||||||
|
Invalid URI will be parsed as `nil`."
|
||||||
|
[s]
|
||||||
|
(when (string? s)
|
||||||
|
(let [[_ authority-path query] (re-find uri-pattern s)]
|
||||||
|
(when authority-path
|
||||||
|
(let [[_ address chain-id function-name] (re-find authority-path-pattern authority-path)]
|
||||||
|
(when-not (or (string/blank? address) function-name) ;; Native token support only TODO(jeluard) Add ERC20 support
|
||||||
|
(merge {:address address :chain-id (if chain-id (js/parseInt chain-id) constants/mainnet-id)}
|
||||||
|
(parse-query query))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- generate-query-string [m]
|
||||||
|
(string/join parameter-separator
|
||||||
|
(for [[k v] m]
|
||||||
|
(str (name k) key-value-separator v))))
|
||||||
|
|
||||||
|
(defn generate-uri
|
||||||
|
"Generate a EIP 681 URI based on `address` and a map (keywords / {bignumbers/strings} ) of extra properties.
|
||||||
|
No validation of address format is performed."
|
||||||
|
[address {:keys [function-name chain-id] :as m}]
|
||||||
|
(when (and address (not function-name)) ;; Native token support only TODO(jeluard) Add ERC20 support
|
||||||
|
(let [parameters (dissoc (into {} (filter second m)) :chain-id)] ;; filter nil values
|
||||||
|
(str scheme scheme-separator address
|
||||||
|
(when (and chain-id (not= chain-id constants/mainnet-id))
|
||||||
|
;; Add chain-id if specified and is not main-net
|
||||||
|
(str chain-id-separator chain-id))
|
||||||
|
(when-not (empty? parameters)
|
||||||
|
(str query-separator (generate-query-string parameters)))))))
|
@ -22,32 +22,31 @@
|
|||||||
|
|
||||||
(defn normalize
|
(defn normalize
|
||||||
"A normalized string representation of an amount"
|
"A normalized string representation of an amount"
|
||||||
[str]
|
[s]
|
||||||
{:pre [(or (nil? str) (string? str))]}
|
{:pre [(or (nil? s) (string? s))]}
|
||||||
(when str
|
(when s
|
||||||
(string/replace (string/trim str) #"," ".")))
|
(string/replace (string/trim s) #"," ".")))
|
||||||
|
|
||||||
(defn bignumber [n]
|
(defn bignumber [n]
|
||||||
(when str
|
(when n
|
||||||
(try
|
(try
|
||||||
(dependencies/Web3.prototype.toBigNumber (str n))
|
(dependencies/Web3.prototype.toBigNumber (str n))
|
||||||
(catch :default err nil))))
|
(catch :default err nil))))
|
||||||
|
|
||||||
(defn valid? [str]
|
(defn valid? [bn]
|
||||||
(when str
|
(when bn
|
||||||
(when-let [bn (bignumber (normalize str))]
|
(.greaterThanOrEqualTo bn 0)))
|
||||||
(.greaterThanOrEqualTo bn 0))))
|
|
||||||
|
|
||||||
(defn to-wei [str]
|
(defn str->wei [s]
|
||||||
(when str
|
(when-let [ns (normalize s)]
|
||||||
(try
|
(try
|
||||||
(dependencies/Web3.prototype.toWei (normalize str) "ether")
|
(dependencies/Web3.prototype.toWei ns "ether")
|
||||||
(catch :default err nil))))
|
(catch :default err nil))))
|
||||||
|
|
||||||
(defn to-decimal [str]
|
(defn to-decimal [s]
|
||||||
(when str
|
(when s
|
||||||
(try
|
(try
|
||||||
(dependencies/Web3.prototype.toDecimal (normalize str))
|
(dependencies/Web3.prototype.toDecimal (normalize s))
|
||||||
(catch :default err nil))))
|
(catch :default err nil))))
|
||||||
|
|
||||||
(def eth-units
|
(def eth-units
|
||||||
@ -64,10 +63,12 @@
|
|||||||
:teth (bignumber "1000000000000000000000000000000")})
|
:teth (bignumber "1000000000000000000000000000000")})
|
||||||
|
|
||||||
(defn wei-> [unit n]
|
(defn wei-> [unit n]
|
||||||
(.dividedBy (bignumber n) (eth-units unit)))
|
(when-let [bn (bignumber n)]
|
||||||
|
(.dividedBy bn (eth-units unit))))
|
||||||
|
|
||||||
(defn to-fixed [bn]
|
(defn to-fixed [bn]
|
||||||
(.toFixed bn))
|
(when bn
|
||||||
|
(.toFixed bn)))
|
||||||
|
|
||||||
(defn wei->str [unit n]
|
(defn wei->str [unit n]
|
||||||
(str (to-fixed (wei-> unit n)) " " (string/upper-case (name unit))))
|
(str (to-fixed (wei-> unit n)) " " (string/upper-case (name unit))))
|
||||||
@ -75,6 +76,10 @@
|
|||||||
(defn wei->ether [n]
|
(defn wei->ether [n]
|
||||||
(wei-> :eth n))
|
(wei-> :eth n))
|
||||||
|
|
||||||
|
(defn ether->wei [bn]
|
||||||
|
(when bn
|
||||||
|
(.times bn (bignumber 1e18))))
|
||||||
|
|
||||||
(defn fee-value [gas gas-price]
|
(defn fee-value [gas gas-price]
|
||||||
(.times (bignumber gas) (bignumber gas-price)))
|
(.times (bignumber gas) (bignumber gas-price)))
|
||||||
|
|
||||||
@ -82,12 +87,17 @@
|
|||||||
(.times (bignumber eth) (bignumber usd-price)))
|
(.times (bignumber eth) (bignumber usd-price)))
|
||||||
|
|
||||||
(defn percent-change [from to]
|
(defn percent-change [from to]
|
||||||
(-> (.dividedBy (bignumber from) (bignumber to))
|
(let [bnf (bignumber from)
|
||||||
|
bnt (bignumber to)]
|
||||||
|
(when (and bnf bnt)
|
||||||
|
(-> (.dividedBy bnf bnt)
|
||||||
(.minus 1)
|
(.minus 1)
|
||||||
(.times 100)))
|
(.times 100)))))
|
||||||
|
|
||||||
(defn with-precision [n decimals]
|
(defn with-precision [n decimals]
|
||||||
(.round (bignumber n) decimals))
|
(when-let [bn (bignumber n)]
|
||||||
|
(.round bn decimals)))
|
||||||
|
|
||||||
(defn sufficient-funds? [amount-in-eth balance]
|
(defn sufficient-funds? [amount balance]
|
||||||
(.greaterThanOrEqualTo balance (bignumber (to-wei amount-in-eth))))
|
(when amount
|
||||||
|
(.greaterThanOrEqualTo balance amount)))
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
[status-im.test.utils.utils]
|
[status-im.test.utils.utils]
|
||||||
[status-im.test.utils.money]
|
[status-im.test.utils.money]
|
||||||
[status-im.test.utils.clocks]
|
[status-im.test.utils.clocks]
|
||||||
[status-im.test.utils.eip.eip67]
|
[status-im.test.utils.eip.eip681]
|
||||||
[status-im.test.utils.erc20]
|
[status-im.test.utils.erc20]
|
||||||
[status-im.test.utils.random]
|
[status-im.test.utils.random]
|
||||||
[status-im.test.utils.gfycat.core]
|
[status-im.test.utils.gfycat.core]
|
||||||
@ -42,7 +42,7 @@
|
|||||||
'status-im.test.utils.utils
|
'status-im.test.utils.utils
|
||||||
'status-im.test.utils.money
|
'status-im.test.utils.money
|
||||||
'status-im.test.utils.clocks
|
'status-im.test.utils.clocks
|
||||||
'status-im.test.utils.eip.eip67
|
'status-im.test.utils.eip.eip681
|
||||||
'status-im.test.utils.erc20
|
'status-im.test.utils.erc20
|
||||||
'status-im.test.utils.random
|
'status-im.test.utils.random
|
||||||
'status-im.test.utils.gfycat.core
|
'status-im.test.utils.gfycat.core
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
(ns status-im.test.utils.eip.eip67
|
|
||||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
|
||||||
[status-im.utils.eip.eip67 :as eip67]))
|
|
||||||
|
|
||||||
(deftest parse-uri
|
|
||||||
(is (= nil (eip67/parse-uri nil)))
|
|
||||||
(is (= nil (eip67/parse-uri "random")))
|
|
||||||
(is (= nil (eip67/parse-uri "ethereum:")))
|
|
||||||
(is (= nil (eip67/parse-uri "ethereum:?value=1")))
|
|
||||||
(is (= nil (eip67/parse-uri "bitcoin:0x1234")))
|
|
||||||
(is (= {:address "0x1234"} (eip67/parse-uri "ethereum:0x1234")))
|
|
||||||
(is (= {:address "0x1234" :to "0x5678" :value "1"} (eip67/parse-uri "ethereum:0x1234?to=0x5678&value=1"))))
|
|
||||||
|
|
||||||
(deftest generate-uri
|
|
||||||
(is (= nil (eip67/generate-uri nil)))
|
|
||||||
(is (= "ethereum:0x1234" (eip67/generate-uri "0x1234")))
|
|
||||||
(is (= "ethereum:0x1234" (eip67/generate-uri "0x1234" nil)))
|
|
||||||
(is (= "ethereum:0x1234" (eip67/generate-uri "0x1234" {})))
|
|
||||||
(is (= "ethereum:0x1234" (eip67/generate-uri "0x1234" {:to nil})))
|
|
||||||
(is (= "ethereum:0x1234?to=0x5678" (eip67/generate-uri "0x1234" {:to "0x5678"})))
|
|
||||||
(is (= "ethereum:0x1234?to=0x5678&value=1" (eip67/generate-uri "0x1234" {:to "0x5678" :value 1}))))
|
|
44
test/cljs/status_im/test/utils/eip/eip681.cljs
Normal file
44
test/cljs/status_im/test/utils/eip/eip681.cljs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
(ns status-im.test.utils.eip.eip681
|
||||||
|
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||||
|
[status-im.utils.eip.eip681 :as eip681]
|
||||||
|
[status-im.utils.money :as money]))
|
||||||
|
|
||||||
|
(deftest parse-uri
|
||||||
|
(is (= nil (eip681/parse-uri nil)))
|
||||||
|
(is (= nil (eip681/parse-uri 5)))
|
||||||
|
(is (= nil (eip681/parse-uri "random")))
|
||||||
|
(is (= nil (eip681/parse-uri "ethereum:")))
|
||||||
|
(is (= nil (eip681/parse-uri "ethereum:?value=1")))
|
||||||
|
(is (= nil (eip681/parse-uri "bitcoin:0x1234")))
|
||||||
|
(is (= {:address "0x1234" :chain-id 1} (eip681/parse-uri "ethereum:0x1234")))
|
||||||
|
(is (= {:address "0x1234" :value "1" :chain-id 1} (eip681/parse-uri "ethereum:0x1234?value=1")))
|
||||||
|
(is (= {:address "0x1234" :value "-1e18" :chain-id 1} (eip681/parse-uri "ethereum:0x1234?value=-1e18")))
|
||||||
|
(is (= {:address "0x1234" :value "+1E18" :chain-id 1} (eip681/parse-uri "ethereum:0x1234?value=+1E18")))
|
||||||
|
(is (= {:address "0x1234" :value "1E18" :gas "100" :chain-id 1} (eip681/parse-uri "ethereum:0x1234?value=1E18&gas=100")))
|
||||||
|
(is (= {:address "0x1234" :value "NOT_NUMBER" :chain-id 1} (eip681/parse-uri "ethereum:0x1234?value=NOT_NUMBER")))
|
||||||
|
(is (= {:address "0x1234" :value "1ETH" :chain-id 1} (eip681/parse-uri "ethereum:0x1234?value=1ETH")))
|
||||||
|
(is (= {:address "0xadaf150b905cf5e6a778e553e15a139b6618bbb7" :value "1e18" :gas "5000" :chain-id 1} (eip681/parse-uri "ethereum:0xadaf150b905cf5e6a778e553e15a139b6618bbb7@1?value=1e18&gas=5000")))
|
||||||
|
(is (= {:address "0xadaf150b905cf5e6a778e553e15a139b6618bbb7" :value "1e18" :gas "5000" :chain-id 3} (eip681/parse-uri "ethereum:0xadaf150b905cf5e6a778e553e15a139b6618bbb7@3?value=1e18&gas=5000")))
|
||||||
|
(is (= nil (eip681/parse-uri "ethereum:0xadaf150b905cf5e6a778e553e15a139b6618bbb7@2/transfer?value=1e18&gas=5000"))))
|
||||||
|
|
||||||
|
(deftest generate-uri
|
||||||
|
(is (= nil (eip681/generate-uri nil nil)))
|
||||||
|
(is (= "ethereum:0x1234" (eip681/generate-uri "0x1234" nil)))
|
||||||
|
(is (= "ethereum:0x1234" (eip681/generate-uri "0x1234" {})))
|
||||||
|
(is (= "ethereum:0x1234" (eip681/generate-uri "0x1234" {:value nil})))
|
||||||
|
(is (= "ethereum:0xadaf150b905cf5e6a778e553e15a139b6618bbb7?value=1" (eip681/generate-uri "0xadaf150b905cf5e6a778e553e15a139b6618bbb7" {:value (money/bignumber 1)})))
|
||||||
|
(is (= "ethereum:0xadaf150b905cf5e6a778e553e15a139b6618bbb7?value=1000000000000000000" (eip681/generate-uri "0xadaf150b905cf5e6a778e553e15a139b6618bbb7" {:value (money/bignumber 1e18)})))
|
||||||
|
(is (= "ethereum:0xadaf150b905cf5e6a778e553e15a139b6618bbb7?value=1&gas=100" (eip681/generate-uri "0xadaf150b905cf5e6a778e553e15a139b6618bbb7" {:value (money/bignumber 1) :gas (money/bignumber 100) :chain-id 1})))
|
||||||
|
(is (= "ethereum:0xadaf150b905cf5e6a778e553e15a139b6618bbb7@3?value=1&gas=100" (eip681/generate-uri "0xadaf150b905cf5e6a778e553e15a139b6618bbb7" {:value (money/bignumber 1) :gas (money/bignumber 100) :chain-id 3})))
|
||||||
|
(is (= nil (eip681/generate-uri "0x1234" {:value (money/bignumber 1) :gas (money/bignumber 100) :chain-id 1 :function-name "transfer"}))))
|
||||||
|
|
||||||
|
(deftest parse-value
|
||||||
|
(is (= nil (eip681/parse-value nil)))
|
||||||
|
(is (= nil (eip681/parse-value 1)))
|
||||||
|
(is (= nil (eip681/parse-value {:value "NOT_NUMBER"})))
|
||||||
|
(is (= nil (eip681/parse-value {:value "1" :function-name "transfer"})))
|
||||||
|
(is (.equals (money/bignumber 1) (eip681/parse-value {:value "1"})))
|
||||||
|
(is (.equals (money/bignumber 1e18) (eip681/parse-value {:value "1ETH"})))
|
||||||
|
(is (.equals (money/bignumber -1e18) (eip681/parse-value {:value "-1e18"})))
|
||||||
|
(is (.equals (money/bignumber 1e18) (eip681/parse-value {:value "1E18"})))
|
||||||
|
(is (.equals (money/bignumber "111122223333441239") (eip681/parse-value {:value "111122223333441239"}))))
|
@ -12,15 +12,9 @@
|
|||||||
|
|
||||||
(deftest valid?
|
(deftest valid?
|
||||||
(is (not (true? (money/valid? nil))))
|
(is (not (true? (money/valid? nil))))
|
||||||
(is (not (true? (money/valid? "a"))))
|
(is (true? (money/valid? (money/bignumber 0))))
|
||||||
(is (not (true? (money/valid? "-1"))))
|
(is (true? (money/valid? (money/bignumber 1))))
|
||||||
(is (not (true? (money/valid? "1a"))))
|
(is (not (true? (money/valid? (money/bignumber -1))))))
|
||||||
(is (not (true? (money/valid? "0,,"))))
|
|
||||||
(is (true? (money/valid? "1")))
|
|
||||||
(is (true? (money/valid? "1.1")))
|
|
||||||
(is (true? (money/valid? "1,1")))
|
|
||||||
(is (true? (money/valid? "0.00000000000000000000001")))
|
|
||||||
(is (true? (money/valid? "0.0000000000000000000000000001"))))
|
|
||||||
|
|
||||||
(deftest normalize
|
(deftest normalize
|
||||||
(is (= nil (money/normalize nil)))
|
(is (= nil (money/normalize nil)))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user