[ISSUE #2391] Added support for ERC681
This commit is contained in:
parent
63848efc20
commit
7e6507be7c
|
@ -5,15 +5,15 @@
|
|||
[reagent.core :as r]
|
||||
[status-im.i18n :refer [message-status-label]]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
image
|
||||
icon
|
||||
animated-view
|
||||
touchable-without-feedback
|
||||
touchable-highlight
|
||||
autolink
|
||||
get-dimensions
|
||||
dismiss-keyboard!]]
|
||||
text
|
||||
image
|
||||
icon
|
||||
animated-view
|
||||
touchable-without-feedback
|
||||
touchable-highlight
|
||||
autolink
|
||||
get-dimensions
|
||||
dismiss-keyboard!]]
|
||||
[status-im.ui.components.animation :as anim]
|
||||
[status-im.ui.components.list-selection :refer [share share-or-open-map]]
|
||||
[status-im.chat.constants :as chat-consts]
|
||||
|
|
|
@ -336,6 +336,7 @@
|
|||
:wallet-choose-from-contacts "Choose From Contacts"
|
||||
:wallet-address-from-clipboard "Use Address From Clipboard"
|
||||
:wallet-invalid-address "Invalid address: \n {{data}}"
|
||||
:wallet-invalid-chain-id "Network does not match: \n {{data}}"
|
||||
:wallet-browse-photos "Browse Photos"
|
||||
: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)"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(ns status-im.ui.components.chat-icon.styles
|
||||
(:require [status-im.ui.components.styles :refer [color-white
|
||||
online-color]]
|
||||
online-color]]
|
||||
[status-im.utils.platform :as p]))
|
||||
|
||||
(defn default-chat-icon [color]
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
[status-im.ui.components.context-menu :as context-menu]
|
||||
[status-im.ui.components.drawer.styles :as st]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
text-input
|
||||
list-item
|
||||
list-view
|
||||
drawer-layout
|
||||
touchable-without-feedback
|
||||
touchable-highlight
|
||||
touchable-opacity
|
||||
dismiss-keyboard!]]
|
||||
text
|
||||
text-input
|
||||
list-item
|
||||
list-view
|
||||
drawer-layout
|
||||
touchable-without-feedback
|
||||
touchable-highlight
|
||||
touchable-opacity
|
||||
dismiss-keyboard!]]
|
||||
[status-im.ui.components.icons.vector-icons :as vi]
|
||||
[status-im.ui.components.status-view.view :as status-view]
|
||||
[status-im.i18n :as i18n]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
(ns status-im.ui.components.list-selection
|
||||
(:require [re-frame.core :refer [dispatch]]
|
||||
[status-im.ui.components.react :refer [copy-to-clipboard
|
||||
sharing
|
||||
linking]]
|
||||
sharing
|
||||
linking]]
|
||||
[status-im.utils.platform :refer [platform-specific ios?]]
|
||||
[status-im.i18n :refer [label]]))
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||
[reagent.core :as r]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
animated-view
|
||||
get-dimensions]]
|
||||
text
|
||||
animated-view
|
||||
get-dimensions]]
|
||||
[status-im.ui.components.sync-state.styles :as st]
|
||||
[status-im.ui.components.animation :as anim]
|
||||
[status-im.i18n :refer [label]]))
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
[status-im.ui.components.status-bar :refer [status-bar]]
|
||||
[status-im.i18n :refer [label]]
|
||||
[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]]))
|
||||
|
||||
(defview qr-code-view []
|
||||
(letsubs [{:keys [photo-path address name]} [:get-in [:qr-modal :contact]]
|
||||
{: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
|
||||
[status-bar {:type :modal}]
|
||||
[react/view styles/account-toolbar
|
||||
|
@ -35,7 +37,7 @@
|
|||
:height (.-height layout)}]))}
|
||||
(when (:width 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
|
||||
:size (- (min (:width dimensions)
|
||||
(:height dimensions))
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
[status-im.utils.handlers :as u :refer [register-handler]]
|
||||
[status-im.utils.utils :as utils]
|
||||
[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
|
||||
[db [_ _ identifier]]
|
||||
|
@ -35,7 +35,7 @@
|
|||
(defn handle-qr-request
|
||||
[db [_ context data]]
|
||||
(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]]
|
||||
(-> db
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns status-im.ui.screens.wallet.choose-recipient.events
|
||||
(:require [status-im.i18n :as i18n]
|
||||
[status-im.utils.eip.eip67 :as eip67]
|
||||
(:require [status-im.constants :as constants]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.eip.eip681 :as eip681]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
(handlers/register-handler-db
|
||||
|
@ -17,22 +18,25 @@
|
|||
amount (assoc :amount amount))))
|
||||
|
||||
(defn- extract-details
|
||||
"First try to parse as EIP67 URI, if not assume this is an address directly.
|
||||
Returns a map containing at least the `address` key"
|
||||
[s]
|
||||
(or (eip67/parse-uri s) {:address s}))
|
||||
"First try to parse as EIP681 URI, if not assume this is an address directly.
|
||||
Returns a map containing at least the `address` and `chain-id` keys"
|
||||
[s network]
|
||||
(or (eip681/parse-uri s) {:address s :chain-id network}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:choose-recipient
|
||||
(fn [{{:keys [web3] :as db} :db} [_ data name]]
|
||||
(let [{:keys [view-id]} db
|
||||
m (extract-details data)
|
||||
address (:address m)
|
||||
(fn [{{:keys [web3 network] :as db} :db} [_ data name]]
|
||||
(let [{:keys [view-id]} db
|
||||
current-network-id (get-in constants/default-networks [network :raw-config :NetworkId])
|
||||
{:keys [address chain-id] :as m} (extract-details data current-network-id)
|
||||
;; 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}
|
||||
valid-address? (update :db #(choose-address-and-name % address name (:value m)))
|
||||
(not valid-address?) (assoc :show-error (i18n/label :t/wallet-invalid-address {:data data}))))))
|
||||
(and valid-address? (= :choose-recipient view-id)) (assoc :dispatch [:navigate-back])
|
||||
(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
|
||||
:wallet-open-send-transaction
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
[react/touchable-highlight {:style (styles/recipient-touchable true)
|
||||
:on-press #(react/get-from-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/text {:style styles/recipient-button-text}
|
||||
(i18n/label :t/wallet-address-from-clipboard)]
|
||||
|
|
|
@ -29,12 +29,16 @@
|
|||
(let [amount-splited (string/split amount #"[.]")]
|
||||
(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)
|
||||
(let [normalized-amount (money/normalize amount)]
|
||||
(let [normalized-amount (money/normalize amount)
|
||||
value (money/bignumber normalized-amount)]
|
||||
(cond
|
||||
(not (money/valid? normalized-amount))
|
||||
(i18n/label :t/validation-amount-invalid-number)
|
||||
(not (money/valid? value))
|
||||
{:error (i18n/label :t/validation-amount-invalid-number)}
|
||||
|
||||
(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
|
||||
(: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 :wallet/request-transaction (allowed-keys
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
(ns status-im.ui.screens.wallet.request.events
|
||||
(:require
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.ui.screens.wallet.db :as wallet.db]
|
||||
[re-frame.core :as re-frame]))
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ui.screens.wallet.db :as wallet.db]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.money :as money]))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::wallet-send-chat-request
|
||||
|
@ -16,12 +16,13 @@
|
|||
(fn [{{:wallet/keys [request-transaction]} :db} [_ {:keys [whisper-identity]}]]
|
||||
{:dispatch-n [[:navigate-back]
|
||||
[: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]]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet-validate-request-amount
|
||||
(fn [{{:wallet/keys [request-transaction] :as db} :db} _]
|
||||
(let [amount (:amount request-transaction)
|
||||
error (wallet.db/get-amount-validation-error amount)]
|
||||
{:db (assoc-in db [:wallet/request-transaction :amount-error] error)})))
|
||||
:wallet.request/set-and-validate-amount
|
||||
(fn [{:keys [db]} [_ amount]]
|
||||
(let [{:keys [value error]} (wallet.db/parse-amount amount)]
|
||||
{:db (-> db
|
||||
(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]]
|
||||
(and
|
||||
(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.status-bar :as status-bar]
|
||||
[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.screens.wallet.components.views :as components]
|
||||
[status-im.ui.screens.wallet.request.styles :as styles]
|
||||
[status-im.ui.components.styles :as components.styles]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.eip.eip67 :as eip67]
|
||||
[status-im.ui.components.common.common :as common]))
|
||||
[status-im.utils.eip.eip681 :as eip681]
|
||||
[status-im.utils.money :as money]))
|
||||
|
||||
(defn toolbar-view []
|
||||
[toolbar/toolbar {:style wallet.styles/toolbar :hide-border? true}
|
||||
|
@ -30,9 +31,10 @@
|
|||
:params {:hide-actions? true}}]))
|
||||
|
||||
(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
|
||||
{: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}]))
|
||||
|
||||
(views/defview request-transaction []
|
||||
|
@ -56,9 +58,7 @@
|
|||
[components/amount-input
|
||||
{:error amount-error
|
||||
:input-options {:on-focus (fn [] (when @scroll (js/setTimeout #(.scrollToEnd @scroll) 100)))
|
||||
:on-change-text
|
||||
#(do (re-frame/dispatch [:set-in [:wallet/request-transaction :amount] %])
|
||||
(re-frame/dispatch [:wallet-validate-request-amount]))}}]
|
||||
:on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount %])}}]
|
||||
[react/view wallet.styles/choose-currency-container
|
||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||
[components/separator]
|
||||
|
@ -68,7 +68,7 @@
|
|||
[vi/icon :icons/share {:color :white :container-style styles/share-icon-container}]
|
||||
[components/button-text (i18n/label :t/share)]]]
|
||||
[react/view components.styles/flex]
|
||||
[react/touchable-highlight {:style wallet.styles/button :disabled (not request-enabled?) :on-press send-request}
|
||||
[react/touchable-highlight {:style wallet.styles/button :disabled (not request-enabled?) :on-press send-request}
|
||||
[react/view (wallet.styles/button-container request-enabled?)
|
||||
[components/button-text (i18n/label :t/send-request)]
|
||||
[vi/icon :icons/forward {:color :white :container-style wallet.styles/forward-icon-container}]]]]]))
|
|
@ -1,8 +1,9 @@
|
|||
(ns status-im.ui.screens.wallet.send.db
|
||||
(: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-name (spec/nilable string?))
|
||||
(spec/def ::amount-error (spec/nilable string?))
|
||||
|
|
|
@ -52,11 +52,11 @@
|
|||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/set-and-validate-amount
|
||||
:wallet.send/set-and-validate-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
|
||||
(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))})))
|
||||
|
||||
(def ^:private clear-send-properties {:id nil
|
||||
|
@ -174,7 +174,7 @@
|
|||
::send-transaction {:web3 web3
|
||||
:from (get-in accounts [current-account-id :address])
|
||||
:to to-address
|
||||
:value (money/to-wei (money/normalize amount))}}))))
|
||||
:value amount}}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/sign-transaction-modal
|
||||
|
|
|
@ -75,9 +75,8 @@
|
|||
(fn [[{:keys [value to] :as transaction} contacts balance]]
|
||||
(when transaction
|
||||
(let [contact (contacts (utils.hex/normalize-hex to))
|
||||
amount (str (.toFixed (money/wei->ether value)))
|
||||
sufficient-funds? (money/sufficient-funds? amount balance)]
|
||||
sufficient-funds? (money/sufficient-funds? value balance)]
|
||||
(cond-> (assoc transaction
|
||||
:amount amount
|
||||
:amount value
|
||||
:sufficient-funds? sufficient-funds?)
|
||||
contact (assoc :to-name (:name contact)))))))
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
(and
|
||||
(nil? amount-error)
|
||||
(not (nil? to-address)) (not= to-address "")
|
||||
(not (nil? amount)) (not= amount "")))
|
||||
(not (nil? amount))))
|
||||
|
||||
;; "Sign Later" and "Sign Transaction >" buttons
|
||||
(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)))
|
||||
:input-options {:auto-focus true
|
||||
:on-focus (fn [] (when @scroll (js/setTimeout #(.scrollToEnd @scroll) 100)))
|
||||
:default-value amount
|
||||
:on-change-text #(re-frame/dispatch [:wallet/set-and-validate-amount %])}}]
|
||||
:default-value (str (money/wei->ether amount))
|
||||
:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount %])}}]
|
||||
[react/view wallet.styles/choose-currency-container
|
||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||
[components/separator]
|
||||
|
@ -168,7 +168,7 @@
|
|||
[components/amount-input
|
||||
{:error (when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds))
|
||||
:disabled? true
|
||||
:input-options {:default-value amount}}]
|
||||
:input-options {:default-value (str (money/wei->ether amount))}}]
|
||||
[react/view wallet.styles/choose-currency-container
|
||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||
[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))))))))
|
||||
|
|
@ -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
|
||||
"A normalized string representation of an amount"
|
||||
[str]
|
||||
{:pre [(or (nil? str) (string? str))]}
|
||||
(when str
|
||||
(string/replace (string/trim str) #"," ".")))
|
||||
[s]
|
||||
{:pre [(or (nil? s) (string? s))]}
|
||||
(when s
|
||||
(string/replace (string/trim s) #"," ".")))
|
||||
|
||||
(defn bignumber [n]
|
||||
(when str
|
||||
(when n
|
||||
(try
|
||||
(dependencies/Web3.prototype.toBigNumber (str n))
|
||||
(catch :default err nil))))
|
||||
|
||||
(defn valid? [str]
|
||||
(when str
|
||||
(when-let [bn (bignumber (normalize str))]
|
||||
(.greaterThanOrEqualTo bn 0))))
|
||||
(defn valid? [bn]
|
||||
(when bn
|
||||
(.greaterThanOrEqualTo bn 0)))
|
||||
|
||||
(defn to-wei [str]
|
||||
(when str
|
||||
(defn str->wei [s]
|
||||
(when-let [ns (normalize s)]
|
||||
(try
|
||||
(dependencies/Web3.prototype.toWei (normalize str) "ether")
|
||||
(dependencies/Web3.prototype.toWei ns "ether")
|
||||
(catch :default err nil))))
|
||||
|
||||
(defn to-decimal [str]
|
||||
(when str
|
||||
(defn to-decimal [s]
|
||||
(when s
|
||||
(try
|
||||
(dependencies/Web3.prototype.toDecimal (normalize str))
|
||||
(dependencies/Web3.prototype.toDecimal (normalize s))
|
||||
(catch :default err nil))))
|
||||
|
||||
(def eth-units
|
||||
|
@ -64,10 +63,12 @@
|
|||
:teth (bignumber "1000000000000000000000000000000")})
|
||||
|
||||
(defn wei-> [unit n]
|
||||
(.dividedBy (bignumber n) (eth-units unit)))
|
||||
(when-let [bn (bignumber n)]
|
||||
(.dividedBy bn (eth-units unit))))
|
||||
|
||||
(defn to-fixed [bn]
|
||||
(.toFixed bn))
|
||||
(when bn
|
||||
(.toFixed bn)))
|
||||
|
||||
(defn wei->str [unit n]
|
||||
(str (to-fixed (wei-> unit n)) " " (string/upper-case (name unit))))
|
||||
|
@ -75,6 +76,10 @@
|
|||
(defn wei->ether [n]
|
||||
(wei-> :eth n))
|
||||
|
||||
(defn ether->wei [bn]
|
||||
(when bn
|
||||
(.times bn (bignumber 1e18))))
|
||||
|
||||
(defn fee-value [gas gas-price]
|
||||
(.times (bignumber gas) (bignumber gas-price)))
|
||||
|
||||
|
@ -82,12 +87,17 @@
|
|||
(.times (bignumber eth) (bignumber usd-price)))
|
||||
|
||||
(defn percent-change [from to]
|
||||
(-> (.dividedBy (bignumber from) (bignumber to))
|
||||
(.minus 1)
|
||||
(.times 100)))
|
||||
(let [bnf (bignumber from)
|
||||
bnt (bignumber to)]
|
||||
(when (and bnf bnt)
|
||||
(-> (.dividedBy bnf bnt)
|
||||
(.minus 1)
|
||||
(.times 100)))))
|
||||
|
||||
(defn with-precision [n decimals]
|
||||
(.round (bignumber n) decimals))
|
||||
(when-let [bn (bignumber n)]
|
||||
(.round bn decimals)))
|
||||
|
||||
(defn sufficient-funds? [amount-in-eth balance]
|
||||
(.greaterThanOrEqualTo balance (bignumber (to-wei amount-in-eth))))
|
||||
(defn sufficient-funds? [amount balance]
|
||||
(when amount
|
||||
(.greaterThanOrEqualTo balance amount)))
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[status-im.test.utils.utils]
|
||||
[status-im.test.utils.money]
|
||||
[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.random]
|
||||
[status-im.test.utils.gfycat.core]
|
||||
|
@ -42,7 +42,7 @@
|
|||
'status-im.test.utils.utils
|
||||
'status-im.test.utils.money
|
||||
'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.random
|
||||
'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}))))
|
|
@ -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?
|
||||
(is (not (true? (money/valid? nil))))
|
||||
(is (not (true? (money/valid? "a"))))
|
||||
(is (not (true? (money/valid? "-1"))))
|
||||
(is (not (true? (money/valid? "1a"))))
|
||||
(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"))))
|
||||
(is (true? (money/valid? (money/bignumber 0))))
|
||||
(is (true? (money/valid? (money/bignumber 1))))
|
||||
(is (not (true? (money/valid? (money/bignumber -1))))))
|
||||
|
||||
(deftest normalize
|
||||
(is (= nil (money/normalize nil)))
|
||||
|
|
Loading…
Reference in New Issue