[ISSUE #2391] Added support for ERC681

This commit is contained in:
Julien Eluard 2017-11-11 08:19:29 +01:00 committed by Oskar Thorén
parent 63848efc20
commit 7e6507be7c
26 changed files with 243 additions and 181 deletions

View File

@ -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)"

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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)]

View File

@ -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}))))

View File

@ -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

View File

@ -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))})))

View File

@ -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)))))

View File

@ -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]

View File

@ -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?))

View File

@ -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

View File

@ -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)))))))

View File

@ -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]

View File

@ -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))))))))

View 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)))))))

View File

@ -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)))

View File

@ -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

View File

@ -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}))))

View 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"}))))

View File

@ -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)))