[ISSUE #2642] Allow to send ERC20 tokens
This commit is contained in:
parent
38d7485f76
commit
7c4d2f2ece
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path opacity=".4" fill="white" d="M12,14.8284271 L8.46236609,11.2907932 C8.07770436,10.9061315 7.44077682,10.9023689 7.05025253,11.2928932 C6.65700558,11.6861402 6.65878803,12.3156423 7.04815252,12.7050068 L11.2949932,16.9518475 C11.4869793,17.1438336 11.741806,17.240936 11.9974742,17.2414902 C12.2565137,17.2439683 12.5106708,17.1461835 12.7050068,16.9518475 L16.9518475,12.7050068 C17.3365092,12.320345 17.3402718,11.6834175 16.9497475,11.2928932 C16.5565005,10.8996463 15.9269984,10.9014287 15.5376339,11.2907932 L12,14.8284271 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 631 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="" fill-rule="evenodd" d="M12,14.8284271 L8.46236609,11.2907932 C8.07770436,10.9061315 7.44077682,10.9023689 7.05025253,11.2928932 C6.65700558,11.6861402 6.65878803,12.3156423 7.04815252,12.7050068 L11.2949932,16.9518475 C11.4869793,17.1438336 11.741806,17.240936 11.9974742,17.2414902 C12.2565137,17.2439683 12.5106708,17.1461835 12.7050068,16.9518475 L16.9518475,12.7050068 C17.3365092,12.320345 17.3402718,11.6834175 16.9497475,11.2928932 C16.5565005,10.8996463 15.9269984,10.9014287 15.5376339,11.2907932 L12,14.8284271 Z" opacity=".4"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 646 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" x="12" y="12" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path opacity=".4" fill="white" d="M12,9 C11.7440777,9 11.4881554,9.09763107 11.2928932,9.29289322 L7.29289322,13.2928932 C6.90236893,13.6834175 6.90236893,14.3165825 7.29289322,14.7071068 C7.68341751,15.0976311 8.31658249,15.0976311 8.70710678,14.7071068 L12,11.4142136 L15.2928932,14.7071068 C15.6834175,15.0976311 16.3165825,15.0976311 16.7071068,14.7071068 C17.0976311,14.3165825 17.0976311,13.6834175 16.7071068,13.2928932 L12.7071068,9.29289322 C12.5118446,9.09763107 12.2559223,9 12,9 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 606 B |
|
@ -46,11 +46,11 @@
|
|||
(def mainnet-networks
|
||||
{"mainnet" {:id "mainnet",
|
||||
:name "Mainnet",
|
||||
:config {:NetworkId (ethereum/chain-id :mainnet)
|
||||
:config {:NetworkId (ethereum/chain-keyword->chain-id :mainnet)
|
||||
:DataDir "/ethereum/mainnet"}}
|
||||
"mainnet_rpc" {:id "mainnet_rpc",
|
||||
:name "Mainnet with upstream RPC",
|
||||
:config {:NetworkId (ethereum/chain-id :mainnet)
|
||||
:config {:NetworkId (ethereum/chain-keyword->chain-id :mainnet)
|
||||
:DataDir "/ethereum/mainnet_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://mainnet.infura.io/z6GCTmjdP3FETEJmMBI4"}}}})
|
||||
|
@ -58,21 +58,21 @@
|
|||
(def testnet-networks
|
||||
{"testnet" {:id "testnet",
|
||||
:name "Ropsten",
|
||||
:config {:NetworkId (ethereum/chain-id :ropsten)
|
||||
:config {:NetworkId (ethereum/chain-keyword->chain-id :testnet)
|
||||
:DataDir "/ethereum/testnet"}}
|
||||
"testnet_rpc" {:id "testnet_rpc",
|
||||
:name "Ropsten with upstream RPC",
|
||||
:config {:NetworkId (ethereum/chain-id :ropsten)
|
||||
:config {:NetworkId (ethereum/chain-keyword->chain-id :testnet)
|
||||
:DataDir "/ethereum/testnet_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://ropsten.infura.io/z6GCTmjdP3FETEJmMBI4"}}}
|
||||
"rinkeby" {:id "rinkeby",
|
||||
:name "Rinkeby",
|
||||
:config {:NetworkId (ethereum/chain-id :rinkeby)
|
||||
:config {:NetworkId (ethereum/chain-keyword->chain-id :rinkeby)
|
||||
:DataDir "/ethereum/rinkeby"}}
|
||||
"rinkeby_rpc" {:id "rinkeby_rpc",
|
||||
:name "Rinkeby with upstream RPC",
|
||||
:config {:NetworkId (ethereum/chain-id :rinkeby)
|
||||
:config {:NetworkId (ethereum/chain-keyword->chain-id :rinkeby)
|
||||
:DataDir "/ethereum/rinkeby_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://rinkeby.infura.io/z6GCTmjdP3FETEJmMBI4"}}}})
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
[status-im.data-store.realm.schemas.account.v16.core :as v16]
|
||||
[status-im.data-store.realm.schemas.account.v17.core :as v17]
|
||||
[status-im.data-store.realm.schemas.account.v18.core :as v18]
|
||||
[status-im.data-store.realm.schemas.account.v19.core :as v19]
|
||||
))
|
||||
[status-im.data-store.realm.schemas.account.v19.core :as v19]))
|
||||
|
||||
|
||||
;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas.
|
||||
|
||||
|
@ -79,5 +79,5 @@
|
|||
:migration v18/migration}
|
||||
{:schema v19/schema
|
||||
:schemaVersion 19
|
||||
:migration v19/migration}
|
||||
])
|
||||
:migration v19/migration}])
|
||||
|
||||
|
|
|
@ -344,8 +344,12 @@
|
|||
: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-invalid-chain-id "Network does not match: \n {{data}} but current chain is {{chain}}"
|
||||
:wallet-browse-photos "Browse Photos"
|
||||
:wallet-advanced "Advanced"
|
||||
:wallet-transaction-fee "Transaction Fee"
|
||||
:wallet-transaction-fee-details "Gas limit is the amount of gas to send with your transaction. Increasing this number will not get your transaction processed faster"
|
||||
:wallet-transaction-total-fee "Total Fee"
|
||||
: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)"
|
||||
|
||||
|
|
|
@ -70,5 +70,5 @@
|
|||
[vector-icons/icon :icons/network {:color :white}]]
|
||||
[react/text {:style (styles/network-text text-color)}
|
||||
(if (ethereum/testnet? network-id)
|
||||
(i18n/label :t/testnet-text {:testnet (get-in ethereum/chains [network-id :name] "Unknown")})
|
||||
(i18n/label :t/testnet-text {:testnet (get-in ethereum/chains [(ethereum/chain-id->chain-keyword network-id) :name] "Unknown")})
|
||||
(i18n/label :t/mainnet-text))]]]))
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
:icons/flash-active (slurp-svg "./resources/icons/flash_active.svg")
|
||||
:icons/flash-inactive (slurp-svg "./resources/icons/flash_inactive.svg")
|
||||
:icons/attach (slurp-svg "./resources/icons/attach.svg")
|
||||
:icons/back (slurp-svg "./resources/icons/back.svg")
|
||||
:icons/browse (slurp-svg "./resources/icons/browse.svg")
|
||||
:icons/close (slurp-svg "./resources/icons/close.svg")
|
||||
:icons/copy-from (slurp-svg "./resources/icons/copy_from.svg")
|
||||
|
@ -50,7 +49,6 @@
|
|||
:icons/dots-vertical (slurp-svg "./resources/icons/dots_vertical.svg")
|
||||
:icons/exclamation_mark (slurp-svg "./resources/icons/exclamation_mark.svg")
|
||||
:icons/filter (slurp-svg "./resources/icons/filter.svg")
|
||||
:icons/forward (slurp-svg "./resources/icons/forward.svg")
|
||||
:icons/fullscreen (slurp-svg "./resources/icons/fullscreen.svg")
|
||||
:icons/group-big (slurp-svg "./resources/icons/group_big.svg")
|
||||
:icons/group-chat (slurp-svg "./resources/icons/group_chat.svg")
|
||||
|
@ -64,8 +62,11 @@
|
|||
:icons/search (slurp-svg "./resources/icons/search.svg")
|
||||
:icons/smile (slurp-svg "./resources/icons/smile.svg")
|
||||
:icons/commands-list (slurp-svg "./resources/icons/commands_list.svg")
|
||||
:icons/back (slurp-svg "./resources/icons/back.svg")
|
||||
:icons/forward (slurp-svg "./resources/icons/forward.svg")
|
||||
:icons/dropdown-up (slurp-svg "./resources/icons/dropdown_up.svg")
|
||||
:icons/dropdown (slurp-svg "./resources/icons/dropdown.svg")
|
||||
:icons/up (slurp-svg "./resources/icons/up.svg")
|
||||
:icons/down (slurp-svg "./resources/icons/down.svg")
|
||||
:icons/grab (slurp-svg "./resources/icons/grab.svg")
|
||||
:icons/share (slurp-svg "./resources/icons/share.svg")
|
||||
:icons/tooltip-triangle (slurp-svg "./resources/icons/tooltip-triangle.svg")
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
(ns status-im.ui.components.image-button.view
|
||||
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
image
|
||||
touchable-highlight]]
|
||||
[status-im.ui.components.icons.vector-icons :as vi]
|
||||
[status-im.ui.components.styles :refer [icon-scan]]
|
||||
[status-im.i18n :refer [label]]
|
||||
[status-im.ui.components.image-button.styles :as st]))
|
||||
(:require [status-im.i18n :as i18n]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.image-button.styles :as styles]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.styles :as components.styles]))
|
||||
|
||||
(defn image-button [{:keys [value style handler]}]
|
||||
[view st/image-button
|
||||
[touchable-highlight {:on-press handler}
|
||||
[view st/image-button-content
|
||||
[vi/icon :icons/fullscreen {:color :blue :style icon-scan}]
|
||||
(when text
|
||||
[text {:style style} value])]]])
|
||||
(defn- image-button [{:keys [value style handler]}]
|
||||
[react/view styles/image-button
|
||||
[react/touchable-highlight {:on-press handler}
|
||||
[react/view styles/image-button-content
|
||||
[vector-icons/icon :icons/fullscreen {:color :blue :style components.styles/icon-scan}]
|
||||
(when value
|
||||
[react/text {:style style} value])]]])
|
||||
|
||||
(defn scan-button [{:keys [show-label? handler]}]
|
||||
[image-button {:value (if show-label?
|
||||
(label :t/scan-qr))
|
||||
:style st/scan-button-text
|
||||
[image-button {:value (when show-label?
|
||||
(i18n/label :t/scan-qr))
|
||||
:style styles/scan-button-text
|
||||
:handler handler}])
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
(defn item-icon
|
||||
[{:keys [icon style icon-opts]}]
|
||||
{:pre [(not (nil? icon))]}
|
||||
[react/view {:style style}
|
||||
[vector-icons/icon icon (merge icon-opts {:style styles/item-icon})]])
|
||||
|
||||
|
|
|
@ -55,7 +55,10 @@
|
|||
(def activity-indicator (get-class "ActivityIndicator"))
|
||||
|
||||
(def modal (get-class "Modal"))
|
||||
(def picker (get-class "Picker"))
|
||||
(def picker-class (get-class "Picker"))
|
||||
(def picker-item-class
|
||||
(when-let [picker (get-react-property "Picker")]
|
||||
(adapt-class (.-Item picker))))
|
||||
|
||||
(def pan-responder (.-PanResponder rn-dependencies/react-native))
|
||||
(def animated (.-Animated rn-dependencies/react-native))
|
||||
|
@ -111,10 +114,6 @@
|
|||
(merge {:underlay-color :transparent} props)
|
||||
content])
|
||||
|
||||
(def picker-item
|
||||
(when-let [picker (get-react-property "Picker")]
|
||||
(adapt-class (.-Item picker))))
|
||||
|
||||
(defn get-dimensions [name]
|
||||
(js->clj (.get dimensions name) :keywordize-keys true))
|
||||
|
||||
|
@ -126,6 +125,14 @@
|
|||
(defn list-item [component]
|
||||
(r/as-element component))
|
||||
|
||||
(defn picker
|
||||
([{:keys [style item-style selected on-change]} items]
|
||||
[picker-class {:selectedValue selected :style style :itemStyle item-style :onValueChange on-change}
|
||||
(for [{:keys [label value]} items]
|
||||
^{:key (str value)}
|
||||
[picker-item-class
|
||||
{:label (or label value) :value value}])]))
|
||||
|
||||
;; Image picker
|
||||
|
||||
(def image-picker-class rn-dependencies/image-crop-picker)
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
(:require [status-im.ui.components.styles :as common]
|
||||
[status-im.utils.platform :refer [platform-specific]]
|
||||
[status-im.utils.utils :as u]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
touchable-highlight]]))
|
||||
[status-im.ui.components.react :as react]))
|
||||
|
||||
(def sticky-button-style
|
||||
{:flex-direction :row
|
||||
|
@ -25,8 +23,8 @@
|
|||
(defn sticky-button
|
||||
([label on-press] (sticky-button label on-press false))
|
||||
([label on-press once?]
|
||||
[touchable-highlight {:on-press (if once? (u/wrap-call-once! on-press) on-press)}
|
||||
[view sticky-button-style
|
||||
[text {:style sticky-button-label-style
|
||||
:uppercase? (get-in platform-specific [:uppercase?])}
|
||||
[react/touchable-highlight {:on-press (if once? (u/wrap-call-once! on-press) on-press)}
|
||||
[react/view sticky-button-style
|
||||
[react/text {:style sticky-button-label-style
|
||||
:uppercase? (get-in platform-specific [:uppercase?])}
|
||||
label]]]))
|
|
@ -3,11 +3,11 @@
|
|||
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
|
||||
[reagent.core :as r]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
animated-text
|
||||
animated-view
|
||||
text-input
|
||||
touchable-opacity]]
|
||||
text
|
||||
animated-text
|
||||
animated-view
|
||||
text-input
|
||||
touchable-opacity]]
|
||||
[status-im.ui.components.text-field.styles :as st]
|
||||
[status-im.i18n :refer [label]]
|
||||
[status-im.ui.components.animation :as anim]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
(ns status-im.ui.screens.db
|
||||
(:require-macros [status-im.utils.db :refer [allowed-keys]])
|
||||
(:require [status-im.constants :as constants]
|
||||
(:require [cljs.spec.alpha :as spec]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.utils.platform :as platform]
|
||||
[cljs.spec.alpha :as spec]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
status-im.ui.screens.accounts.db
|
||||
status-im.ui.screens.contacts.db
|
||||
status-im.ui.screens.qr-scanner.db
|
||||
|
@ -14,6 +15,11 @@
|
|||
status-im.ui.screens.discover.db
|
||||
status-im.ui.screens.network-settings.db))
|
||||
|
||||
(def transaction-send-default
|
||||
{:symbol :ETH
|
||||
:gas ethereum/default-transaction-gas
|
||||
:gas-price ethereum/default-gas-price})
|
||||
|
||||
;; initial state of app-db
|
||||
(def app-db {:current-public-key ""
|
||||
:status-module-initialized? (or platform/ios? js/goog.DEBUG)
|
||||
|
@ -33,6 +39,7 @@
|
|||
:tags []
|
||||
:sync-state :done
|
||||
:wallet.transactions constants/default-wallet-transactions
|
||||
:wallet {:send-transaction transaction-send-default}
|
||||
:wallet-selected-asset {}
|
||||
:prices {}
|
||||
:notifications {}
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
(:require [status-im.ui.screens.navigation :as navigation]))
|
||||
|
||||
(defmethod navigation/preload-data! :qr-code-view
|
||||
[{:accounts/keys [current-account-id] :as db} [_ _ {:keys [contact qr-source qr-value amount?]}]]
|
||||
[{:accounts/keys [current-account-id] :as db} [_ _ {:keys [contact qr-source qr-value]}]]
|
||||
(update db :qr-modal #(merge % {:contact (or contact
|
||||
(get-in db [:accounts/accounts current-account-id]))
|
||||
:qr-source qr-source
|
||||
:qr-value qr-value
|
||||
:amount? amount?})))
|
||||
:qr-value qr-value})))
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
[status-im.ui.components.status-bar.view :refer [status-bar]]
|
||||
[status-im.i18n :refer [label]]
|
||||
[status-im.ui.screens.profile.qr-code.styles :as styles]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.ethereum.eip681 :as eip681])
|
||||
[status-im.utils.money :as money])
|
||||
(: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 [qr-source qr-value dimensions]} [:get :qr-modal]
|
||||
chain-id [:get-network-id]]
|
||||
[react/view styles/wallet-qr-code
|
||||
[status-bar {:type :modal}]
|
||||
|
@ -37,11 +35,10 @@
|
|||
:height (.-height layout)}]))}
|
||||
(when (:width dimensions)
|
||||
[react/view {:style (styles/qr-code-container dimensions)}
|
||||
(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))
|
||||
80)}])])]
|
||||
[qr-code {:value qr-value
|
||||
:size (- (min (:width dimensions)
|
||||
(:height dimensions))
|
||||
80)}]])]
|
||||
[react/view styles/footer
|
||||
(if (= :address qr-source)
|
||||
[react/view styles/wallet-info
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
[status-im.ui.screens.navigation :as nav]
|
||||
[status-im.utils.handlers :as u :refer [register-handler]]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.ethereum.eip681 :as eip681]))
|
||||
[status-im.i18n :as i18n]))
|
||||
|
||||
(defmethod nav/preload-data! :qr-scanner
|
||||
[db [_ _ identifier]]
|
||||
|
@ -32,10 +31,10 @@
|
|||
(fn [db [_ identifier]]
|
||||
(update db :qr-codes dissoc identifier)))
|
||||
|
||||
(defn handle-qr-request
|
||||
(defn- handle-qr-request
|
||||
[db [_ context data]]
|
||||
(when-let [handler (get-in db [:qr-codes context])]
|
||||
(re-frame/dispatch [handler context (:address (eip681/parse-uri data))])))
|
||||
(re-frame/dispatch [handler context data])))
|
||||
|
||||
(defn clear-qr-request [db [_ context]]
|
||||
(-> db
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
[status-im.ui.screens.wallet.choose-recipient.views :refer [choose-recipient]]
|
||||
[status-im.ui.screens.wallet.request.views :refer [request-transaction]]
|
||||
[status-im.ui.screens.wallet.wallet-list.views :refer [wallet-list-screen]]
|
||||
[status-im.ui.screens.wallet.send.views :as wallet.send]
|
||||
[status-im.ui.screens.wallet.settings.views :as wallet-settings]
|
||||
[status-im.ui.screens.wallet.transactions.views :as wallet-transactions]
|
||||
[status-im.ui.screens.wallet.send.transaction-sent.views :refer [transaction-sent transaction-sent-modal]]
|
||||
|
@ -131,5 +132,6 @@
|
|||
:wallet-settings-assets wallet-settings/manage-assets
|
||||
:wallet-send-transaction-modal send-transaction-modal
|
||||
:wallet-transaction-sent-modal transaction-sent-modal
|
||||
:wallet-transaction-fee wallet.send/transaction-fee
|
||||
(throw (str "Unknown modal view: " modal-view)))]
|
||||
[component])]])]])))))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns status-im.ui.screens.wallet.choose-recipient.events
|
||||
(:require [status-im.constants :as constants]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.eip681 :as eip681]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
|
@ -11,36 +12,37 @@
|
|||
toggled-state (if (= :on flashlight-state) :off :on)]
|
||||
(assoc-in db [:wallet :send-transaction :camera-flashlight] toggled-state))))
|
||||
|
||||
(defn choose-address-and-name [db address name amount]
|
||||
(defn- fill-request-details [db address name amount]
|
||||
(update-in
|
||||
db [:wallet :send-transaction]
|
||||
#(cond-> (assoc % :to-address address :to-name name)
|
||||
#(cond-> (assoc % :to address :to-name name)
|
||||
amount (assoc :amount amount))))
|
||||
|
||||
(defn- extract-details
|
||||
"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}))
|
||||
Returns a map containing at least the `address`, `symbol` and `chain-id` keys"
|
||||
[s chain-id]
|
||||
(or (let [m (eip681/parse-uri s)]
|
||||
(merge m (eip681/extract-request-details m)))
|
||||
(when (ethereum/address? s)
|
||||
{:address s :chain-id chain-id :symbol :ETH})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:choose-recipient
|
||||
:wallet/fill-request-from-url
|
||||
(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-network? (boolean (= current-network-id chain-id))]
|
||||
current-chain-id (get-in constants/default-networks [network :raw-config :NetworkId])
|
||||
{:keys [address chain-id value]} (extract-details data current-chain-id)
|
||||
valid-network? (boolean (= current-chain-id chain-id))]
|
||||
(cond-> {:db db}
|
||||
(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}))))))
|
||||
(and address (= :choose-recipient view-id)) (assoc :dispatch [:navigate-back])
|
||||
(and address valid-network?) (update :db #(fill-request-details % address name value))
|
||||
(not address) (assoc :show-error (i18n/label :t/wallet-invalid-address {:data data}))
|
||||
(and address (not valid-network?)) (assoc :show-error (i18n/label :t/wallet-invalid-chain-id {:data data :chain current-chain-id}))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet-open-send-transaction
|
||||
(fn [{db :db} [_ address name]]
|
||||
{:db (choose-address-and-name db address name nil)
|
||||
:wallet/fill-request-from-contact
|
||||
(fn [{db :db} [_ {:keys [address name]}]]
|
||||
{:db (fill-request-details db address name nil)
|
||||
:dispatch-n [[:navigate-back]
|
||||
[:navigate-back]]}))
|
||||
|
|
|
@ -65,9 +65,9 @@
|
|||
{:flex 1})
|
||||
|
||||
(def preview
|
||||
{:flex 1
|
||||
:justify-content :flex-end
|
||||
:align-items :center})
|
||||
{:flex 1
|
||||
:justifyContent :flex-end
|
||||
:alignItems :center})
|
||||
|
||||
(def corner-dimensions
|
||||
{:position :absolute
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
(defn choose-from-contacts []
|
||||
(re-frame/dispatch [:navigate-to-modal
|
||||
:contact-list-modal
|
||||
{:handler #(re-frame/dispatch [:wallet-open-send-transaction (:address %1) (:name %1)])
|
||||
{:handler #(re-frame/dispatch [:wallet/fill-request-from-contact %])
|
||||
:action :send
|
||||
:params {:hide-actions? true}}]))
|
||||
|
||||
|
@ -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-newline clipboard) nil])))}
|
||||
(re-frame/dispatch [:wallet/fill-request-from-url (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)]
|
||||
|
@ -89,6 +89,6 @@
|
|||
:aspect :fill
|
||||
:captureAudio false
|
||||
:torchMode (camera/set-torch camera-flashlight)
|
||||
:onBarCodeRead #(re-frame/dispatch [:choose-recipient (camera/get-qr-code-data %) nil])}])
|
||||
:onBarCodeRead #(re-frame/dispatch [:wallet/fill-request-from-url (camera/get-qr-code-data %) nil])}])
|
||||
[viewfinder camera-dimensions]]
|
||||
[recipient-buttons]]))
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
(ns status-im.ui.screens.wallet.components.views
|
||||
(:require-macros [status-im.utils.views :as views])
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.styles :as components.styles]
|
||||
[status-im.ui.screens.wallet.components.styles :as styles]
|
||||
[status-im.i18n :as i18n]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.tokens :as tokens]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.ui.screens.wallet.components.animations :as animations]))
|
||||
[status-im.ui.screens.wallet.components.animations :as animations]
|
||||
[status-im.ui.screens.wallet.utils :as wallet.utils]))
|
||||
|
||||
(views/defview tooltip [label & [style]]
|
||||
(views/letsubs [bottom-value (animation/create-value 16)
|
||||
|
@ -48,68 +52,78 @@
|
|||
(when-not (nil? error)
|
||||
[tooltip error])]]))))
|
||||
|
||||
|
||||
;;TODO (andrey) this should be choose component with the list of currencies
|
||||
(defn choose-currency [& [style]]
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/currency)]
|
||||
[react/view (merge styles/currency-container
|
||||
style)
|
||||
[react/text {:style styles/wallet-name} "ETH"]]])
|
||||
|
||||
(defn choose-recipient-disabled [{:keys [address name]}]
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/recipient)]
|
||||
[react/view (merge styles/recipient-container
|
||||
styles/container-disabled)
|
||||
(when name
|
||||
[react/view styles/recipient-name-container
|
||||
[react/text {:style (styles/participant true)
|
||||
:number-of-lines 1}
|
||||
name]])
|
||||
[react/view components.styles/flex
|
||||
[react/text {:style (styles/participant (not name))
|
||||
:number-of-lines 1
|
||||
:ellipsizeMode :middle}
|
||||
address]]]])
|
||||
|
||||
(defn choose-recipient [{:keys [address name on-press style]}]
|
||||
(let [address? (and (not (nil? address)) (not= address ""))]
|
||||
[react/touchable-highlight {:on-press on-press}
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/recipient)]
|
||||
[react/view (merge styles/recipient-container
|
||||
style)
|
||||
(when name
|
||||
[react/view styles/recipient-name-container
|
||||
[react/text {:style (styles/participant true)
|
||||
:number-of-lines 1}
|
||||
name]])
|
||||
[react/view components.styles/flex
|
||||
[react/text {:style (styles/participant (and (not name) address?))
|
||||
:number-of-lines 1
|
||||
:ellipsizeMode :middle}
|
||||
(if address? address "Choose recipient...")]]
|
||||
[vector-icons/icon :icons/forward {:color :white}]]]]))
|
||||
|
||||
;;TODO (andrey) this should be choose component with the list of wallets
|
||||
(views/defview choose-wallet [& [style]]
|
||||
(views/letsubs [balance [:balance]]
|
||||
(views/defview view-currency [style]
|
||||
(views/letsubs [visible-tokens [:wallet.settings/visible-tokens]
|
||||
symbol [:wallet.send/symbol]]
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/wallet)]
|
||||
[react/view (merge styles/wallet-container
|
||||
[react/text {:style styles/label} (i18n/label :t/currency)]
|
||||
[react/view (merge styles/currency-container
|
||||
style)
|
||||
[react/text {:style styles/wallet-name} (i18n/label :t/main-wallet)]
|
||||
(if balance
|
||||
[react/view {:style styles/wallet-value-container}
|
||||
[react/text {:style (merge styles/wallet-value styles/wallet-value-amount)
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail}
|
||||
(str (money/wei->ether (:ETH balance)))] ;; TODO(jeluard) update based on currency selected
|
||||
[react/text {:style styles/wallet-value}
|
||||
(i18n/label :t/eth)]]
|
||||
[react/text {:style styles/wallet-value}
|
||||
"..."])]]))
|
||||
[react/text
|
||||
(name symbol)]]]))
|
||||
|
||||
(views/defview choose-currency [style]
|
||||
(views/letsubs [visible-tokens [:wallet.settings/visible-tokens]
|
||||
symbol [:wallet.send/symbol]]
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/currency)]
|
||||
[react/view (merge styles/currency-container
|
||||
style)
|
||||
[react/picker {:selected (name symbol)
|
||||
:style {:color "white"}
|
||||
:item-style styles/wallet-name
|
||||
:on-change #(re-frame/dispatch [:wallet.send/set-symbol (keyword %)])}
|
||||
(map (fn [s] {:value (name s) :color "white"}) (conj visible-tokens (:symbol tokens/ethereum)))]]]))
|
||||
|
||||
(defn choose-recipient-content [{:keys [address name on-press style]}]
|
||||
(let [address? (and (not (nil? address)) (not= address ""))]
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/recipient)]
|
||||
[react/view (merge styles/recipient-container
|
||||
(when-not on-press styles/container-disabled)
|
||||
style)
|
||||
(when name
|
||||
[react/view styles/recipient-name-container
|
||||
[react/text {:style (styles/participant true)
|
||||
:number-of-lines 1}
|
||||
name]])
|
||||
[react/view components.styles/flex
|
||||
[react/text {:style (styles/participant (and (not name) address?))
|
||||
:number-of-lines 1
|
||||
:ellipsizeMode :middle}
|
||||
(if address? address "Choose recipient...")]]
|
||||
(when on-press
|
||||
[vector-icons/icon :icons/forward {:color :white}])]]))
|
||||
|
||||
(defn choose-recipient [{:keys [on-press] :as m}]
|
||||
(if on-press
|
||||
[react/touchable-highlight {:on-press on-press}
|
||||
[react/view ;; TODO(jeluard) remove extra view when migrating to latest RN
|
||||
[choose-recipient-content m]]]
|
||||
[react/view
|
||||
[choose-recipient-content m]]))
|
||||
|
||||
(views/defview choose-wallet [& [style]]
|
||||
(views/letsubs [network [:network]
|
||||
balance [:balance]
|
||||
symbol [:wallet.send/symbol]]
|
||||
(let [amount (get balance symbol)
|
||||
decimals (:decimals (tokens/asset-for (ethereum/network->chain-keyword network) symbol))]
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/wallet)]
|
||||
[react/view (merge styles/wallet-container
|
||||
style)
|
||||
[react/text {:style styles/wallet-name} (i18n/label :t/main-wallet)]
|
||||
(if amount
|
||||
[react/view {:style styles/wallet-value-container}
|
||||
[react/text {:style (merge styles/wallet-value styles/wallet-value-amount)
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail}
|
||||
(wallet.utils/format-amount amount decimals)]
|
||||
[react/text {:style styles/wallet-value}
|
||||
(name symbol)]]
|
||||
[react/text {:style styles/wallet-value}
|
||||
"..."])]])))
|
||||
|
||||
(defn separator []
|
||||
[react/view styles/separator])
|
||||
|
|
|
@ -56,9 +56,9 @@
|
|||
|
||||
(reg-fx
|
||||
:get-tokens-balance
|
||||
(fn [{:keys [web3 symbols chain-id account-id success-event error-event]}]
|
||||
(fn [{:keys [web3 symbols chain account-id success-event error-event]}]
|
||||
(doseq [symbol symbols]
|
||||
(let [contract (:address (tokens/token-for chain-id symbol))]
|
||||
(let [contract (:address (tokens/symbol->token chain symbol))]
|
||||
(get-token-balance {:web3 web3
|
||||
:contract contract
|
||||
:account-id account-id
|
||||
|
@ -94,7 +94,7 @@
|
|||
:get-tokens-balance {:web3 web3
|
||||
:account-id current-account-id
|
||||
:symbols symbols
|
||||
:chain-id (ethereum/network->chain-id network)
|
||||
:chain (ethereum/network->chain-keyword network)
|
||||
:success-event :update-token-balance-success
|
||||
:error-event :update-token-balance-fail}
|
||||
:get-prices {:from "ETH"
|
||||
|
|
|
@ -16,25 +16,18 @@
|
|||
[status-im.utils.utils :as utils]
|
||||
[status-im.ui.screens.wallet.main.styles :as styles]
|
||||
[status-im.ui.screens.wallet.styles :as wallet.styles]
|
||||
[status-im.ui.screens.wallet.utils :as wallet.utils]
|
||||
[status-im.ui.components.styles :as components.styles]
|
||||
[status-im.ui.screens.wallet.components.views :as components]
|
||||
[status-im.ui.components.button.styles :as button.styles]
|
||||
[status-im.ui.screens.wallet.views :as wallet.views]))
|
||||
|
||||
(defn- show-not-implemented! []
|
||||
(utils/show-popup "TODO" "Not implemented yet!"))
|
||||
|
||||
(defn toolbar-title []
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :wallet-list])}
|
||||
[react/view {:style styles/toolbar-title-container}
|
||||
[react/text {:style styles/toolbar-title-text
|
||||
:font :toolbar-title
|
||||
:number-of-lines 1}
|
||||
(i18n/label :t/main-wallet)]
|
||||
[vi/icon
|
||||
:icons/dropdown
|
||||
{:container-style styles/toolbar-title-icon
|
||||
:color :white}]]])
|
||||
[react/view {:style styles/toolbar-title-container}
|
||||
[react/text {:style styles/toolbar-title-text
|
||||
:font :toolbar-title
|
||||
:number-of-lines 1}
|
||||
(i18n/label :t/main-wallet)]])
|
||||
|
||||
(def transaction-history-action
|
||||
{:icon :icons/transaction-history
|
||||
|
@ -62,10 +55,6 @@
|
|||
[react/view {:style styles/total-balance}
|
||||
[react/text {:style styles/total-balance-value} usd-value]
|
||||
[react/text {:style styles/total-balance-currency} (i18n/label :t/usd-currency)]]
|
||||
[react/view {:style styles/value-variation}
|
||||
[react/text {:style styles/value-variation-title}
|
||||
(i18n/label :t/wallet-total-value)]
|
||||
[components/change-display change]]
|
||||
[react/view {:style (merge button.styles/buttons-container styles/buttons)}
|
||||
[btn/button {:disabled? syncing?
|
||||
:on-press #(re-frame/dispatch [:navigate-to :wallet-send-transaction])
|
||||
|
@ -78,43 +67,27 @@
|
|||
[btn/button {:disabled? true :style (button.styles/button-bar :last) :text-style styles/main-button-text}
|
||||
(i18n/label :t/wallet-exchange)]]]])
|
||||
|
||||
(defn add-asset []
|
||||
[list/touchable-item show-not-implemented!
|
||||
[react/view
|
||||
[list/item
|
||||
[list/item-icon {:icon :icons/add :style styles/add-asset-icon :icon-opts {:color :blue}}]
|
||||
[react/view {:style styles/asset-item-value-container}
|
||||
[react/text {:style styles/add-asset-text}
|
||||
(i18n/label :t/wallet-add-asset)]]]]])
|
||||
|
||||
(defn render-asset [{:keys [name symbol icon decimals amount] :as asset}]
|
||||
(if name ;; If no 'name' then this the dummy value used to render `add-asset`
|
||||
[list/touchable-item #(re-frame/dispatch [:navigate-to-asset asset])
|
||||
[react/view
|
||||
[list/item
|
||||
[list/item-image icon]
|
||||
[react/view {:style styles/asset-item-value-container}
|
||||
[react/text {:style styles/asset-item-value
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail}
|
||||
(money/to-fixed (money/token->unit (or amount (money/bignumber 0)) decimals))]
|
||||
[react/text {:style styles/asset-item-currency
|
||||
:uppercase? true
|
||||
:number-of-lines 1}
|
||||
(clojure.core/name symbol)]]
|
||||
[list/item-icon {:icon :icons/forward}]]]]
|
||||
[add-asset]))
|
||||
|
||||
(defn tokens-for [network]
|
||||
(get tokens/all (ethereum/network->chain-id network)))
|
||||
(defn- render-asset [{:keys [name symbol icon decimals amount] :as asset}]
|
||||
[react/view
|
||||
[list/item
|
||||
[list/item-image icon]
|
||||
[react/view {:style styles/asset-item-value-container}
|
||||
[react/text {:style styles/asset-item-value
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail}
|
||||
(wallet.utils/format-amount amount decimals)]
|
||||
[react/text {:style styles/asset-item-currency
|
||||
:uppercase? true
|
||||
:number-of-lines 1}
|
||||
(clojure.core/name symbol)]]]])
|
||||
|
||||
(defn asset-section [network balance visible-tokens prices-loading? balance-loading?]
|
||||
(let [tokens (filter #(contains? visible-tokens (:symbol %)) (tokens-for network))
|
||||
(let [tokens (filter #(contains? visible-tokens (:symbol %)) (tokens/tokens-for (ethereum/network->chain-keyword network)))
|
||||
assets (map #(assoc % :amount (get balance (:symbol %))) (concat [tokens/ethereum] (when config/erc20-enabled? tokens)))]
|
||||
[react/view {:style styles/asset-section}
|
||||
[react/text {:style styles/asset-section-title} (i18n/label :t/wallet-assets)]
|
||||
[list/flat-list
|
||||
{:data assets ;; TODO(jeluard) Reenable once we `add-an-asset` story is flecthed out ;; (concat assets [{}]) ;; Extra map triggers rendering for add-asset
|
||||
{:data assets
|
||||
:render-fn render-asset
|
||||
:on-refresh #(re-frame/dispatch [:update-wallet (when config/erc20-enabled? (map :symbol tokens))])
|
||||
:refreshing (boolean (or prices-loading? balance-loading?))}]]))
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
(ns status-im.ui.screens.wallet.navigation
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ui.screens.db :as db]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.ui.screens.wallet.main.views :as main]))
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.tokens :as tokens]))
|
||||
|
||||
(defmethod navigation/preload-data! :wallet
|
||||
[db _]
|
||||
(re-frame/dispatch [:update-wallet (map :symbol (main/tokens-for (:network db)))])
|
||||
(re-frame/dispatch [:update-wallet (map :symbol (tokens/tokens-for (ethereum/network->chain-keyword (:network db))))])
|
||||
(assoc-in db [:wallet :current-tab] 0))
|
||||
|
||||
(defmethod navigation/preload-data! :transactions-history
|
||||
|
@ -17,8 +19,11 @@
|
|||
[db _]
|
||||
(dissoc db :wallet/request-transaction))
|
||||
|
||||
(defn- dissoc-transaction-details [m]
|
||||
(apply dissoc m (apply disj (set (keys m)) (keys db/transaction-send-default))))
|
||||
|
||||
(defmethod navigation/preload-data! :wallet-send-transaction
|
||||
[db [event]]
|
||||
(if (= event :navigate-back)
|
||||
db
|
||||
(update db :wallet dissoc :send-transaction)))
|
||||
(update-in db [:wallet :send-transaction] dissoc-transaction-details)))
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[status-im.ui.components.styles :as components.styles]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.eip681 :as eip681]
|
||||
[status-im.utils.money :as money]))
|
||||
|
||||
|
@ -34,7 +35,7 @@
|
|||
(views/letsubs [account [:get-current-account]
|
||||
chain-id [:get-network-id]]
|
||||
[components.qr-code/qr-code
|
||||
{:value (eip681/generate-uri (:address account) (merge {:chain-id chain-id} (when amount {:value amount})))
|
||||
{:value (eip681/generate-uri (ethereum/normalized-address (:address account)) (merge {:chain-id chain-id} (when amount {:value amount})))
|
||||
:size 256}]))
|
||||
|
||||
(views/defview request-transaction []
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
[status-im.utils.money :as money]))
|
||||
|
||||
(spec/def ::amount (spec/nilable money/valid?))
|
||||
(spec/def ::to-address (spec/nilable string?))
|
||||
(spec/def ::to (spec/nilable string?))
|
||||
(spec/def ::to-name (spec/nilable string?))
|
||||
(spec/def ::amount-error (spec/nilable string?))
|
||||
(spec/def ::password (spec/nilable string?))
|
||||
|
@ -20,9 +20,14 @@
|
|||
(spec/def ::camera-permitted? boolean?)
|
||||
(spec/def ::in-progress? boolean?)
|
||||
(spec/def ::from-chat? (spec/nilable boolean?))
|
||||
(spec/def ::symbol (spec/nilable keyword?))
|
||||
(spec/def ::gas (spec/nilable money/valid?))
|
||||
(spec/def ::gas-price (spec/nilable money/valid?))
|
||||
(spec/def ::advanced? boolean?)
|
||||
|
||||
(spec/def :wallet/send-transaction (allowed-keys
|
||||
:opt-un [::amount ::to-address ::to-name ::amount-error ::password
|
||||
:opt-un [::amount ::to ::to-name ::amount-error ::password
|
||||
::waiting-signal? ::signing? ::id ::later?
|
||||
::camera-dimensions ::camera-flashlight ::in-progress?
|
||||
::wrong-password? ::camera-permitted? ::from-chat?]))
|
||||
::wrong-password? ::camera-permitted? ::from-chat? ::symbol ::advanced?
|
||||
::gas ::gas-price]))
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.ui.screens.wallet.db :as wallet.db]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.erc20 :as erc20]
|
||||
[status-im.utils.ethereum.tokens :as tokens]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.hex :as utils.hex]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.constants :as constants]))
|
||||
|
||||
;;;; FX
|
||||
|
@ -18,13 +21,21 @@
|
|||
(fn [{:keys [password id on-completed]}]
|
||||
(status/complete-transactions (list id) password on-completed)))
|
||||
|
||||
(defn- send-ethers [{:keys [web3 from to value gas gas-price]}]
|
||||
(.sendTransaction (.-eth web3)
|
||||
(clj->js {:from from :to to :value value :gas gas :gasPrice gas-price})
|
||||
#()))
|
||||
|
||||
(defn- send-tokens [{:keys [web3 from to value gas gas-price symbol network]}]
|
||||
(let [contract (:address (tokens/symbol->token (ethereum/network->chain-keyword network) symbol))]
|
||||
(erc20/transfer web3 contract from to value {:gas gas :gasPrice gas-price} #())))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::send-transaction
|
||||
(fn [{:keys [web3] :as params}]
|
||||
(when web3
|
||||
(.sendTransaction (.-eth web3)
|
||||
(clj->js (select-keys params [:from :to :value]))
|
||||
#()))))
|
||||
(fn [{:keys [symbol] :as params}]
|
||||
(case symbol
|
||||
:ETH (send-ethers params)
|
||||
(send-tokens params))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::show-transaction-moved
|
||||
|
@ -59,6 +70,23 @@
|
|||
(assoc-in [:wallet :send-transaction :amount] (money/ether->wei value))
|
||||
(assoc-in [:wallet :send-transaction :amount-error] error))})))
|
||||
|
||||
(defn- estimated-gas [symbol]
|
||||
(if (tokens/ethereum? symbol)
|
||||
ethereum/default-transaction-gas
|
||||
;; TODO(jeluard) Rely on estimateGas call
|
||||
(.times ethereum/default-transaction-gas 5)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet.send/set-symbol
|
||||
(fn [{:keys [db]} [_ symbol]]
|
||||
{:db (-> (assoc-in db [:wallet :send-transaction :symbol] symbol)
|
||||
(assoc-in [:wallet :send-transaction :gas] (estimated-gas symbol)))}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet.send/toggle-advanced
|
||||
(fn [{:keys [db]} [_ advanced?]]
|
||||
{:db (assoc-in db [:wallet :send-transaction :advanced?] advanced?)}))
|
||||
|
||||
(def ^:private clear-send-properties {:id nil
|
||||
:signing? false
|
||||
:wrong-password? false
|
||||
|
@ -76,11 +104,12 @@
|
|||
[(re-frame/inject-cofx :now)]
|
||||
(fn [{:keys [db now]} [_ {:keys [id message_id args] :as transaction}]]
|
||||
(if (transaction-valid? transaction)
|
||||
(let [{:keys [from to value data gas gasPrice]} args
|
||||
(let [{:keys [from to value symbol data gas gasPrice]} args
|
||||
;;TODO (andrey) revisit this map later (this map from old transactions, idk if we need all these fields)
|
||||
transaction {:id id
|
||||
:from from
|
||||
:to to
|
||||
:symbol symbol
|
||||
:value (money/bignumber (or value 0))
|
||||
:data data
|
||||
:gas (money/to-decimal gas)
|
||||
|
@ -160,8 +189,9 @@
|
|||
:wallet/sign-transaction
|
||||
(fn [{{:keys [web3]
|
||||
:accounts/keys [accounts current-account-id] :as db} :db} [_ later?]]
|
||||
(let [db' (assoc-in db [:wallet :send-transaction :wrong-password?] false)
|
||||
{:keys [amount id password to-address]} (get-in db [:wallet :send-transaction])]
|
||||
(let [db' (assoc-in db [:wallet :send-transaction :wrong-password?] false)
|
||||
network (:network db)
|
||||
{:keys [amount id password to symbol gas gas-price] :as m} (get-in db [:wallet :send-transaction])]
|
||||
(if id
|
||||
{::accept-transaction {:id id
|
||||
:password password
|
||||
|
@ -171,10 +201,14 @@
|
|||
:waiting-signal? true
|
||||
:later? later?
|
||||
:in-progress? true)
|
||||
::send-transaction {:web3 web3
|
||||
:from (get-in accounts [current-account-id :address])
|
||||
:to to-address
|
||||
:value amount}}))))
|
||||
::send-transaction {:web3 web3
|
||||
:from (get-in accounts [current-account-id :address])
|
||||
:to to
|
||||
:value amount
|
||||
:gas gas
|
||||
:gas-price gas-price
|
||||
:symbol symbol
|
||||
:network network}}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/sign-transaction-modal
|
||||
|
@ -226,3 +260,13 @@
|
|||
:wallet.send/set-signing?
|
||||
(fn [{:keys [db]} [_ signing?]]
|
||||
{:db (assoc-in db [:wallet :send-transaction :signing?] signing?)}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet.send/set-gas
|
||||
(fn [{:keys [db]} [_ gas]]
|
||||
{:db (assoc-in db [:wallet :send-transaction :gas] (money/bignumber gas))}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet.send/set-gas-price
|
||||
(fn [{:keys [db]} [_ gas-price]]
|
||||
{:db (assoc-in db [:wallet :send-transaction :gas-price] (money/bignumber gas-price))}))
|
|
@ -1,6 +1,7 @@
|
|||
(ns status-im.ui.screens.wallet.send.styles
|
||||
(:require-macros [status-im.utils.styles :refer [defnstyle defstyle]])
|
||||
(:require [status-im.ui.components.styles :as styles]))
|
||||
(:require [status-im.ui.components.styles :as styles]
|
||||
[status-im.ui.screens.wallet.components.styles :as wallet.components.styles]))
|
||||
|
||||
(def toolbar
|
||||
{:background-color styles/color-blue5
|
||||
|
@ -76,3 +77,70 @@
|
|||
{:text-align :center
|
||||
:margin-top 22
|
||||
:margin-horizontal 92})
|
||||
|
||||
(def advanced-button
|
||||
{:flex-direction :row
|
||||
:background-color styles/color-blue6
|
||||
:border-radius 50
|
||||
:padding 8})
|
||||
|
||||
(def advanced-button-wrapper
|
||||
{:align-items :center})
|
||||
|
||||
(def advanced-wrapper
|
||||
{:margin-horizontal 15})
|
||||
|
||||
(def advanced-options-wrapper
|
||||
{:height 52
|
||||
:background-color styles/color-white-transparent-4
|
||||
:border-radius 4
|
||||
:margin-top 16
|
||||
:margin-bottom 16
|
||||
:align-items :center
|
||||
:flex-direction :row})
|
||||
|
||||
(def advanced-options-text-wrapper
|
||||
{:flex 1
|
||||
:flex-direction :row
|
||||
:justify-content :space-between
|
||||
:margin-horizontal 15})
|
||||
|
||||
(def advanced-label
|
||||
{:text-align-vertical :center
|
||||
:margin-left 4})
|
||||
|
||||
(def advanced-fees-text
|
||||
{:color styles/color-white})
|
||||
|
||||
(def advanced-fees-details-text
|
||||
{:color styles/color-white-transparent})
|
||||
|
||||
(def transaction-fee-block-wrapper
|
||||
{:flex-direction :row
|
||||
:margin-top 15})
|
||||
|
||||
(def transaction-fee-column-wrapper
|
||||
{:flex 0.5
|
||||
:margin-horizontal 15})
|
||||
|
||||
(def transaction-fee-bubble
|
||||
(merge advanced-options-wrapper
|
||||
{:flex-direction :row
|
||||
:justify-content :space-between
|
||||
:padding-horizontal 15}))
|
||||
|
||||
(def transaction-fee-bubble-read-only
|
||||
(merge transaction-fee-bubble
|
||||
{:background-color styles/color-blue6}))
|
||||
|
||||
(def transaction-fee-info
|
||||
{:margin 15})
|
||||
|
||||
(def transaction-fee-input
|
||||
{:flex 1
|
||||
:keyboard-type :numeric
|
||||
:auto-capitalize "none"
|
||||
:placeholder "0.000"
|
||||
:placeholder-text-color styles/color-white-transparent
|
||||
:selection-color :white
|
||||
:style wallet.components.styles/text-input})
|
|
@ -16,6 +16,16 @@
|
|||
(fn [wallet]
|
||||
(:send-transaction wallet)))
|
||||
|
||||
(re-frame/reg-sub :wallet.send/symbol
|
||||
:<- [::send-transaction]
|
||||
(fn [send-transaction]
|
||||
(:symbol send-transaction)))
|
||||
|
||||
(re-frame/reg-sub :wallet.send/advanced?
|
||||
:<- [::send-transaction]
|
||||
(fn [send-transaction]
|
||||
(:advanced? send-transaction)))
|
||||
|
||||
(re-frame/reg-sub :wallet.send/camera-dimensions
|
||||
:<- [::send-transaction]
|
||||
(fn [send-transaction]
|
||||
|
@ -55,29 +65,27 @@
|
|||
(merge send-transaction
|
||||
unsigned-transaction))))
|
||||
|
||||
(defn sign-enabled? [amount-error to-address amount]
|
||||
(defn sign-enabled? [amount-error to amount]
|
||||
(and
|
||||
(nil? amount-error)
|
||||
(not (nil? to-address)) (not= to-address "")
|
||||
(not (nil? to)) (not= to "")
|
||||
(not (nil? amount)) (not= amount "")))
|
||||
|
||||
(re-frame/reg-sub :wallet.send/transaction
|
||||
:<- [::send-transaction]
|
||||
:<- [:balance]
|
||||
(fn [[{:keys [amount to] :as transaction} balance]]
|
||||
(fn [[{:keys [amount to symbol] :as transaction} balance]]
|
||||
(assoc transaction :sufficient-funds? (or (nil? amount)
|
||||
;; TODO(jeluard) Modify to consider tokens
|
||||
(money/sufficient-funds? amount (get balance :ETH))))))
|
||||
(money/sufficient-funds? amount (get balance symbol))))))
|
||||
|
||||
(re-frame/reg-sub :wallet.send/unsigned-transaction
|
||||
:<- [::unsigned-transaction]
|
||||
:<- [:contacts/by-address]
|
||||
:<- [:balance]
|
||||
(fn [[{:keys [value to] :as transaction} contacts balance]]
|
||||
(fn [[{:keys [value to symbol] :as transaction} contacts balance]]
|
||||
(when transaction
|
||||
(let [contact (contacts (utils.hex/normalize-hex to))
|
||||
;; TODO(jeluard) Modify to consider tokens
|
||||
sufficient-funds? (money/sufficient-funds? value (get balance :ETH))]
|
||||
sufficient-funds? (money/sufficient-funds? value (get balance symbol))]
|
||||
(cond-> (assoc transaction
|
||||
:amount value
|
||||
:sufficient-funds? sufficient-funds?)
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
(ns status-im.ui.screens.wallet.send.views
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[status-im.ui.components.camera :as camera]
|
||||
[status-im.ui.components.common.common :as common]
|
||||
[status-im.ui.components.styles :as styles]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.status-bar.view :as status-bar]
|
||||
[status-im.ui.components.styles :as components.styles]
|
||||
[status-im.ui.components.toolbar.actions :as act]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.screens.wallet.components.styles :as wallet.components.styles]
|
||||
[status-im.ui.screens.wallet.components.views :as components]
|
||||
[status-im.ui.screens.wallet.send.animations :as send.animations]
|
||||
[status-im.ui.screens.wallet.send.styles :as send.styles]
|
||||
|
@ -19,13 +21,6 @@
|
|||
[status-im.utils.utils :as utils])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
(defn toolbar-view [signing?]
|
||||
[toolbar/toolbar {:style wallet.styles/toolbar}
|
||||
[toolbar/nav-button (act/back-white (if signing?
|
||||
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back])
|
||||
act/default-handler))]
|
||||
[toolbar/content-title {:color :white} (i18n/label :t/send-transaction)]])
|
||||
|
||||
(defn sign-later-popup
|
||||
[from-chat?]
|
||||
(utils/show-question
|
||||
|
@ -52,7 +47,7 @@
|
|||
{:auto-focus true
|
||||
:secure-text-entry true
|
||||
:placeholder (i18n/label :t/enter-password)
|
||||
:placeholder-text-color "#939ba1"
|
||||
:placeholder-text-color components.styles/color-gray4
|
||||
:on-change-text #(re-frame/dispatch [:wallet.send/set-password %])
|
||||
:style send.styles/password}]]]
|
||||
(when wrong-password?
|
||||
|
@ -72,15 +67,15 @@
|
|||
[components/button-text (i18n/label :t/transactions-sign-transaction)]
|
||||
[vector-icons/icon :icons/forward {:color :white :container-style wallet.styles/forward-icon-container}]]]]))
|
||||
|
||||
(defn sign-enabled? [amount-error to-address amount]
|
||||
(defn- sign-enabled? [amount-error to amount]
|
||||
(and
|
||||
(nil? amount-error)
|
||||
(not (nil? to-address)) (not= to-address "")
|
||||
(not (nil? to)) (not= to "")
|
||||
(not (nil? amount))))
|
||||
|
||||
;; "Sign Later" and "Sign Transaction >" buttons
|
||||
(defn- sign-buttons [amount-error to-address amount sufficient-funds? sign-later-handler]
|
||||
(let [sign-enabled? (sign-enabled? amount-error to-address amount)
|
||||
(defn- sign-buttons [amount-error to amount sufficient-funds? sign-later-handler]
|
||||
(let [sign-enabled? (sign-enabled? amount-error to amount)
|
||||
immediate-sign-enabled? (and sign-enabled? sufficient-funds?)]
|
||||
[react/view wallet.styles/buttons-container
|
||||
(when sign-enabled?
|
||||
|
@ -94,7 +89,7 @@
|
|||
[components/button-text (i18n/label :t/transactions-sign-transaction)]
|
||||
[vector-icons/icon :icons/forward {:color :white :container-style wallet.styles/forward-icon-container}]]]]))
|
||||
|
||||
(defn request-camera-permissions []
|
||||
(defn- request-camera-permissions []
|
||||
(when platform/android?
|
||||
(re-frame/dispatch [:request-permissions [:camera]]))
|
||||
(camera/request-access
|
||||
|
@ -102,87 +97,163 @@
|
|||
(re-frame/dispatch [:set-in [:wallet :send-transaction :camera-permitted?] permitted?])
|
||||
(re-frame/dispatch [:navigate-to :choose-recipient]))))
|
||||
|
||||
(defview send-transaction []
|
||||
(letsubs [transaction [:wallet.send/transaction]
|
||||
scroll (atom nil)]
|
||||
(let [{:keys [amount amount-error signing? to-address to-name in-progress? sufficient-funds?]} transaction]
|
||||
[react/keyboard-avoiding-view wallet.styles/wallet-modal-container
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar {:type :wallet}]
|
||||
[toolbar-view signing?]
|
||||
[common/network-info {:text-color :white}]
|
||||
[react/scroll-view {:keyboardShouldPersistTaps :always
|
||||
:ref #(reset! scroll %)}
|
||||
[react/view components.styles/flex
|
||||
[react/view wallet.styles/choose-participant-container
|
||||
[components/choose-recipient {:address to-address
|
||||
:name to-name
|
||||
:on-press request-camera-permissions}]]
|
||||
[react/view wallet.styles/choose-wallet-container
|
||||
[components/choose-wallet]]
|
||||
[react/view wallet.styles/amount-container
|
||||
[components/amount-input
|
||||
{:error (or amount-error
|
||||
(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 (str (money/to-fixed (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]
|
||||
(if signing?
|
||||
[signing-buttons
|
||||
#(re-frame/dispatch [:wallet/discard-transaction])
|
||||
#(re-frame/dispatch [:wallet/sign-transaction])
|
||||
in-progress?]
|
||||
[sign-buttons amount-error to-address amount sufficient-funds? #(sign-later-popup false)])
|
||||
(when signing?
|
||||
[sign-panel])]
|
||||
(when in-progress? [react/view send.styles/processing-view])])))
|
||||
|
||||
(defn toolbar-modal [from-chat?]
|
||||
(defn- toolbar-modal [from-chat?]
|
||||
[toolbar/toolbar {:style wallet.styles/toolbar}
|
||||
[toolbar/nav-button (act/close-white (if from-chat?
|
||||
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back])
|
||||
act/default-handler))]
|
||||
[toolbar/content-title {:color :white} (i18n/label :t/send-transaction)]])
|
||||
|
||||
(defn- toolbar-view [signing?]
|
||||
[toolbar/toolbar {:style wallet.styles/toolbar}
|
||||
[toolbar/nav-button (act/back-white (if signing?
|
||||
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back])
|
||||
act/default-handler))]
|
||||
[toolbar/content-title {:color :white} (i18n/label :t/send-transaction)]])
|
||||
|
||||
(defn- transaction-fee-toolbar []
|
||||
[toolbar/toolbar {:style wallet.styles/toolbar}
|
||||
[toolbar/nav-button (act/close-white #(re-frame/dispatch [:wallet/discard-transaction-navigate-back]))]
|
||||
[toolbar/content-title {:color :white} (i18n/label :t/wallet-transaction-fee)]])
|
||||
|
||||
(defn- max-fee [gas gas-price]
|
||||
(when (and gas gas-price)
|
||||
(money/wei->ether (.times gas gas-price))))
|
||||
|
||||
(defview transaction-fee []
|
||||
(letsubs [{:keys [amount gas gas-price symbol]} [:wallet.send/transaction]]
|
||||
[react/keyboard-avoiding-view wallet.styles/wallet-modal-container
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar {:type :modal-wallet}]
|
||||
[transaction-fee-toolbar]
|
||||
[react/view send.styles/transaction-fee-block-wrapper
|
||||
[react/view send.styles/transaction-fee-column-wrapper
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(i18n/label :t/gas-limit)]
|
||||
[react/view send.styles/advanced-options-wrapper
|
||||
[react/text-input (merge send.styles/transaction-fee-input
|
||||
{:on-change-text #(re-frame/dispatch [:wallet.send/set-gas %])
|
||||
:default-value (str (money/to-fixed gas))})]]]
|
||||
|
||||
[react/view send.styles/transaction-fee-column-wrapper
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(i18n/label :t/gas-price)]
|
||||
[react/view send.styles/transaction-fee-bubble
|
||||
[react/text-input (merge send.styles/transaction-fee-input
|
||||
{:on-change-text #(re-frame/dispatch [:wallet.send/set-gas-price (money/->wei :gwei %)])
|
||||
:default-value (str (money/to-fixed (money/wei-> :gwei gas-price)))})]
|
||||
[react/text {:style send.styles/advanced-fees-details-text}
|
||||
"Gwei"]]]]
|
||||
[react/view send.styles/transaction-fee-info
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(i18n/label :t/wallet-transaction-fee-details)]]
|
||||
[components/separator]
|
||||
[react/view send.styles/transaction-fee-block-wrapper
|
||||
[react/view send.styles/transaction-fee-column-wrapper
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(i18n/label :t/amount)]
|
||||
[react/view send.styles/transaction-fee-bubble-read-only
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(str (money/to-fixed (money/wei->ether amount)))]
|
||||
[react/text {:style send.styles/advanced-fees-details-text}
|
||||
(name symbol)]]]
|
||||
[react/view send.styles/transaction-fee-column-wrapper
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(i18n/label :t/wallet-transaction-total-fee)]
|
||||
[react/view send.styles/transaction-fee-bubble-read-only
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(str (money/to-fixed (max-fee gas gas-price)))]
|
||||
[react/text {:style send.styles/advanced-fees-details-text}
|
||||
"ETH"]]]]]]))
|
||||
|
||||
(defn- advanced-options-wrapper [on-press content]
|
||||
(if on-press
|
||||
[react/touchable-highlight {:on-press on-press}
|
||||
[react/view
|
||||
content]]
|
||||
[react/view
|
||||
content]))
|
||||
|
||||
(defn- advanced-options [{:keys [gas gas-price]} on-press]
|
||||
[react/touchable-highlight {:on-press on-press}
|
||||
[react/view
|
||||
[react/text {:style wallet.components.styles/label}
|
||||
(i18n/label :t/wallet-transaction-fee)]
|
||||
[advanced-options-wrapper on-press
|
||||
[react/view send.styles/advanced-options-wrapper
|
||||
[react/view send.styles/advanced-options-text-wrapper
|
||||
[react/text {:style send.styles/advanced-fees-text}
|
||||
(str (money/to-fixed (max-fee gas gas-price)) " ETH")]
|
||||
[react/text {:style send.styles/advanced-fees-details-text}
|
||||
(str (money/to-fixed gas) " * " (money/to-fixed (money/wei-> :gwei gas-price)) "GWEI")]]
|
||||
(when on-press
|
||||
[vector-icons/icon :icons/forward {:color :white}])]]]])
|
||||
|
||||
(defn- send-transaction-panel [{:keys [modal? transaction scroll advanced?] :as transaction}]
|
||||
(let [{:keys [amount amount-error signing? to to-name sufficient-funds? in-progress? from-chat?]} transaction]
|
||||
[react/keyboard-avoiding-view wallet.styles/wallet-modal-container
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar {:type (if modal? :modal-wallet :wallet)}]
|
||||
(if modal? [toolbar-modal from-chat?] [toolbar-view signing?])
|
||||
[common/network-info {:text-color :white}]
|
||||
[react/scroll-view (merge {:keyboardShouldPersistTaps :always} (when-not modal? {:ref #(reset! scroll %)}))
|
||||
[react/view components.styles/flex
|
||||
[react/view wallet.styles/choose-participant-container
|
||||
[components/choose-recipient (merge {:address to
|
||||
:name to-name}
|
||||
(when-not modal?
|
||||
{:on-press request-camera-permissions}))]]
|
||||
[react/view wallet.styles/choose-wallet-container
|
||||
[components/choose-wallet]]
|
||||
[react/view wallet.styles/amount-container
|
||||
[components/amount-input
|
||||
(merge
|
||||
{:error (or amount-error
|
||||
(when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds)))
|
||||
:input-options {:default-value (str (money/wei->ether amount))
|
||||
:on-focus (fn [] (when @scroll (js/setTimeout #(.scrollToEnd @scroll) 100)))
|
||||
:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount %])}}
|
||||
(when modal?
|
||||
{:disabled? true}))]
|
||||
(if modal?
|
||||
[react/view wallet.styles/choose-currency-container
|
||||
[components/view-currency wallet.styles/choose-currency]]
|
||||
[react/view wallet.styles/choose-currency-container
|
||||
[components/choose-currency wallet.styles/choose-currency]])]
|
||||
[react/view {:style send.styles/advanced-wrapper}
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:wallet.send/toggle-advanced (not advanced?)])}
|
||||
[react/view {:style send.styles/advanced-button-wrapper}
|
||||
[react/view {:style send.styles/advanced-button}
|
||||
[react/text {:style (merge wallet.components.styles/label send.styles/advanced-label)}
|
||||
(i18n/label :t/wallet-advanced)]
|
||||
[vector-icons/icon (if advanced? :icons/up :icons/down) {:color :white}]]]]
|
||||
(when advanced?
|
||||
[advanced-options transaction (when-not modal? #(re-frame/dispatch [:navigate-to-modal :wallet-transaction-fee]))])]]]
|
||||
[components/separator]
|
||||
(if signing?
|
||||
[signing-buttons
|
||||
#(re-frame/dispatch (if modal? [:wallet/cancel-signing-modal] [:wallet/discard-transaction]))
|
||||
#(re-frame/dispatch (if modal? [:wallet/sign-transaction-modal] [:wallet/sign-transaction]))
|
||||
in-progress?]
|
||||
[sign-buttons amount-error to amount sufficient-funds? (if modal? (if from-chat?
|
||||
#(sign-later-popup true)
|
||||
#(re-frame/dispatch [:navigate-back]))
|
||||
#(sign-later-popup false))])
|
||||
(when signing?
|
||||
[sign-panel])
|
||||
(when in-progress? [react/view send.styles/processing-view])]]))
|
||||
|
||||
(defview send-transaction []
|
||||
(letsubs [transaction [:wallet.send/transaction]
|
||||
advanced? [:wallet.send/advanced?]
|
||||
scroll (atom nil)]
|
||||
[send-transaction-panel {:modal? false :transaction transaction :scroll scroll :advanced? advanced?}]))
|
||||
|
||||
(defview send-transaction-modal []
|
||||
(letsubs [transaction [:wallet.send/unsigned-transaction]]
|
||||
(letsubs [transaction [:wallet.send/unsigned-transaction]
|
||||
advanced? [:wallet.send/advanced?]]
|
||||
(if transaction
|
||||
(let [{:keys [amount amount-error signing? to to-name sufficient-funds? in-progress? from-chat?]} transaction]
|
||||
[react/keyboard-avoiding-view wallet.styles/wallet-modal-container
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar {:type :modal-wallet}]
|
||||
[toolbar-modal from-chat?]
|
||||
[common/network-info {:text-color :white}]
|
||||
[react/scroll-view {:keyboardShouldPersistTaps :always}
|
||||
[react/view components.styles/flex
|
||||
[react/view wallet.styles/choose-participant-container
|
||||
[components/choose-recipient-disabled {:address to
|
||||
:name to-name}]]
|
||||
[react/view wallet.styles/choose-wallet-container
|
||||
[components/choose-wallet]]
|
||||
[react/view wallet.styles/amount-container
|
||||
[components/amount-input
|
||||
{:error (when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds))
|
||||
:disabled? true
|
||||
: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]
|
||||
(if signing?
|
||||
[signing-buttons
|
||||
#(re-frame/dispatch [:wallet/cancel-signing-modal])
|
||||
#(re-frame/dispatch [:wallet/sign-transaction-modal])
|
||||
in-progress?]
|
||||
[sign-buttons amount-error to amount sufficient-funds? (if from-chat?
|
||||
#(sign-later-popup true)
|
||||
#(re-frame/dispatch [:navigate-back]))])
|
||||
(when signing?
|
||||
[sign-panel])
|
||||
(when in-progress? [react/view send.styles/processing-view])]])
|
||||
[send-transaction-panel {:modal? true :transaction transaction :advanced? advanced?}]
|
||||
[react/view wallet.styles/wallet-modal-container
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar {:type :modal-wallet}]
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
(handlers/register-handler-fx
|
||||
:wallet.settings/toggle-visible-token
|
||||
(fn [{{:keys [network] :accounts/keys [current-account-id] :as db} :db} [_ symbol checked?]]
|
||||
(let [chain-id (ethereum/network->chain-id network)
|
||||
(let [chain (ethereum/network->chain-keyword network)
|
||||
path [:accounts/accounts current-account-id :settings]
|
||||
settings (get-in db path)
|
||||
new-settings (update-in settings [:wallet :visible-tokens chain-id] #(toggle-checked % symbol checked?))]
|
||||
new-settings (update-in settings [:wallet :visible-tokens chain] #(toggle-checked % symbol checked?))]
|
||||
(-> db
|
||||
(assoc-in path new-settings)
|
||||
(accounts/update-wallet-settings new-settings)))))
|
|
@ -7,5 +7,5 @@
|
|||
:<- [:network]
|
||||
:<- [:get-current-account]
|
||||
(fn [[network current-account]]
|
||||
(let [chain-id (ethereum/network->chain-id network)]
|
||||
(get-in current-account [:settings :wallet :visible-tokens chain-id]))))
|
||||
(let [chain (ethereum/network->chain-keyword network)]
|
||||
(get-in current-account [:settings :wallet :visible-tokens chain]))))
|
|
@ -21,11 +21,10 @@
|
|||
(defview manage-assets []
|
||||
(letsubs [network [:network]
|
||||
visible-tokens [:wallet.settings/visible-tokens]]
|
||||
(let [chain-id (ethereum/network->chain-id network)]
|
||||
[react/view components.styles/flex
|
||||
[toolbar/toolbar {}
|
||||
[toolbar/nav-clear-text (i18n/label :t/done)]
|
||||
[toolbar/content-title (i18n/label :t/wallet-assets)]]
|
||||
[react/view {:style components.styles/flex}
|
||||
[list/flat-list {:data (tokens/tokens-for chain-id)
|
||||
:render-fn #(render-token % visible-tokens)}]]])))
|
||||
[react/view components.styles/flex
|
||||
[toolbar/toolbar {}
|
||||
[toolbar/nav-clear-text (i18n/label :t/done)]
|
||||
[toolbar/content-title (i18n/label :t/wallet-assets)]]
|
||||
[react/view {:style components.styles/flex}
|
||||
[list/flat-list {:data (tokens/tokens-for (ethereum/network->chain-keyword network))
|
||||
:render-fn #(render-token % visible-tokens)}]]]))
|
||||
|
|
|
@ -47,11 +47,10 @@
|
|||
(fn [transactions]
|
||||
(group-by :type (vals transactions))))
|
||||
|
||||
(defn format-unsigned-transaction [{:keys [id] :as transaction}]
|
||||
(defn- format-unsigned-transaction [{:keys [id] :as transaction}]
|
||||
(assoc transaction
|
||||
:type :unsigned
|
||||
:confirmations 0
|
||||
:symbol "ETH"
|
||||
;; TODO (andrey) revisit this, we shouldn't set not hash value to the hash field
|
||||
:hash id))
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
(ns status-im.ui.screens.wallet.utils
|
||||
(:require [status-im.utils.money :as money]))
|
||||
|
||||
(defn format-amount [amount decimals]
|
||||
(-> amount
|
||||
(or (money/bignumber 0))
|
||||
(money/token->unit decimals)
|
||||
money/to-fixed))
|
|
@ -1,10 +1,8 @@
|
|||
(ns status-im.utils.db
|
||||
(:require [clojure.string :as string]
|
||||
[cljs.spec.alpha :as spec]
|
||||
[status-im.js-dependencies :as dependencies]))
|
||||
|
||||
(defn address? [s]
|
||||
(.isAddress dependencies/Web3.prototype s))
|
||||
[status-im.js-dependencies :as dependencies]
|
||||
[status-im.utils.ethereum.core :as ethereum]))
|
||||
|
||||
(defn hex-string? [s]
|
||||
(let [s' (if (string/starts-with? s "0x")
|
||||
|
@ -20,8 +18,8 @@
|
|||
(and (= 128 length) (not (string/includes? identity "0x")))
|
||||
(and (= 130 length) (string/starts-with? identity "0x"))
|
||||
(and (= 132 length) (string/starts-with? identity "0x04"))
|
||||
(address? identity)))))
|
||||
(ethereum/address? identity)))))
|
||||
|
||||
(spec/def :global/not-empty-string (spec/and string? not-empty))
|
||||
(spec/def :global/public-key (spec/and :global/not-empty-string valid-length?))
|
||||
(spec/def :global/address address?)
|
||||
(spec/def :global/address ethereum/address?)
|
|
@ -7,14 +7,17 @@
|
|||
|
||||
(def chains
|
||||
{:mainnet {:id 1 :name "Mainnet"}
|
||||
:ropsten {:id 3 :name "Ropsten"}
|
||||
:testnet {:id 3 :name "Ropsten"}
|
||||
:rinkeby {:id 4 :name "Rinkeby"}})
|
||||
|
||||
(defn chain-id [k]
|
||||
(defn chain-id->chain-keyword [i]
|
||||
(some #(when (= i (:id (val %))) (key %)) chains))
|
||||
|
||||
(defn chain-keyword->chain-id [k]
|
||||
(get-in chains [k :id]))
|
||||
|
||||
(defn testnet? [id]
|
||||
(contains? #{(chain-id :ropsten) (chain-id :rinkeby)} id))
|
||||
(contains? #{(chain-keyword->chain-id :testnet) (chain-keyword->chain-id :rinkeby)} id))
|
||||
|
||||
(defn network-with-upstream-rpc? [networks network]
|
||||
(get-in networks [network :raw-config :UpstreamConfig :Enabled]))
|
||||
|
@ -27,7 +30,11 @@
|
|||
address
|
||||
(str hex-prefix address))))
|
||||
|
||||
(defn network->chain-id [network]
|
||||
(defn address? [s]
|
||||
(when s
|
||||
(.isAddress dependencies/Web3.prototype s)))
|
||||
|
||||
(defn network->chain-keyword [network]
|
||||
(when network
|
||||
(keyword (string/replace network "_rpc" ""))))
|
||||
|
||||
|
@ -47,7 +54,7 @@
|
|||
(.toHex dependencies/Web3.prototype i))
|
||||
|
||||
(defn hex->bignumber [s]
|
||||
(money/bignumber (if (= s "0x") 0 s)))
|
||||
(money/bignumber (if (= s hex-prefix) 0 s)))
|
||||
|
||||
(defn zero-pad-64 [s]
|
||||
(str (apply str (drop (count s) (repeat 64 "0"))) s))
|
||||
|
@ -70,3 +77,9 @@
|
|||
(defn call-params [contract method-sig & params]
|
||||
(let [data (apply format-call-params (sig->method-id method-sig) params)]
|
||||
{:to contract :data data}))
|
||||
|
||||
(defn send-transaction [web3 params cb]
|
||||
(.sendTransaction (.-eth web3) (clj->js params) cb))
|
||||
|
||||
(def default-transaction-gas (money/bignumber 21000))
|
||||
(def default-gas-price (money/->wei :gwei 21))
|
|
@ -4,8 +4,10 @@
|
|||
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]
|
||||
(:require [clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.tokens :as tokens]
|
||||
[status-im.utils.money :as money]))
|
||||
|
||||
(def scheme "ethereum")
|
||||
|
@ -17,21 +19,27 @@
|
|||
(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 authority-path-pattern (re-pattern (str "^([^" chain-id-separator function-name-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]))))
|
||||
(def valid-native-arguments #{:value :gas})
|
||||
|
||||
(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-query [s]
|
||||
(into {} (for [[_ k v] (re-seq query-pattern (or s ""))]
|
||||
[(keyword k) v])))
|
||||
|
||||
(defn- parse-native-arguments [m]
|
||||
(when (set/superset? valid-native-arguments (set (keys m)))
|
||||
m))
|
||||
|
||||
(defn- parse-arguments [function-name s]
|
||||
(let [m (parse-query s)]
|
||||
(if function-name
|
||||
(merge {:function-name function-name} (when-not (empty? m) {:function-arguments m}))
|
||||
(parse-native-arguments m))))
|
||||
|
||||
;; TODO add ENS support
|
||||
|
||||
(defn parse-uri
|
||||
"Parse a EIP 681 URI as a map (keyword / strings). Parsed map will contain at least the key `address`.
|
||||
|
@ -42,10 +50,32 @@
|
|||
(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) (ethereum/chain-id :mainnet))}
|
||||
(parse-query query))))))))
|
||||
(when (ethereum/address? address)
|
||||
(when-let [arguments (parse-arguments function-name query)]
|
||||
(merge {:address address :chain-id (if chain-id (js/parseInt chain-id) (ethereum/chain-keyword->chain-id :mainnet))}
|
||||
arguments))))))))
|
||||
|
||||
(defn parse-eth-value [s]
|
||||
"Takes a map as returned by `parse-uri` and returns value as BigNumber"
|
||||
(when (string? s)
|
||||
(let [eth? (string/ends-with? s "ETH")
|
||||
n (money/bignumber (string/replace s "ETH" ""))]
|
||||
(if eth? (.times n 1e18) n))))
|
||||
|
||||
(defn extract-request-details [{:keys [value address chain-id function-name function-arguments]}]
|
||||
"Return a map encapsulating request details (with keys `value`, `address` and `symbol`) from a parsed URI.
|
||||
Supports ethereum and erc20 token."
|
||||
(when address
|
||||
(case function-name
|
||||
nil
|
||||
{:value (parse-eth-value value)
|
||||
:symbol :ETH
|
||||
:address address}
|
||||
"transfer"
|
||||
{:value (money/bignumber (:uint256 function-arguments))
|
||||
:symbol (:symbol (tokens/address->token (ethereum/chain-id->chain-keyword chain-id) address))
|
||||
:address (:address function-arguments)}
|
||||
nil)))
|
||||
|
||||
(defn- generate-query-string [m]
|
||||
(string/join parameter-separator
|
||||
|
@ -55,12 +85,14 @@
|
|||
(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
|
||||
[address {:keys [chain-id function-name function-arguments] :as m}]
|
||||
(when (ethereum/address? address)
|
||||
(let [parameters (dissoc (into {} (filter second m)) :chain-id)] ;; filter nil values
|
||||
(str scheme scheme-separator address
|
||||
(when (and chain-id (not= chain-id (ethereum/chain-id :mainnet)))
|
||||
(when (and chain-id (not= chain-id (ethereum/chain-keyword->chain-id :mainnet)))
|
||||
;; 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)))))))
|
||||
(if function-name
|
||||
(str function-name-separator function-name query-separator (generate-query-string function-arguments))
|
||||
(str query-separator (generate-query-string parameters))))))))
|
|
@ -34,25 +34,27 @@
|
|||
|
||||
(defn balance-of [web3 contract address cb]
|
||||
(ethereum/call web3
|
||||
(ethereum/call-params contract "balanceOf(address)" address)
|
||||
(ethereum/call-params contract "balanceOf(address)" (ethereum/normalized-address address))
|
||||
#(cb %1 (ethereum/hex->bignumber %2))))
|
||||
|
||||
(defn transfer [web3 contract address value cb]
|
||||
(ethereum/call web3
|
||||
(ethereum/call-params contract "transfer(address, uint256)" address (ethereum/int->hex value))
|
||||
(defn transfer [web3 contract from address value params cb]
|
||||
(ethereum/send-transaction web3
|
||||
(merge (ethereum/call-params contract "transfer(address,uint256)" (ethereum/normalized-address address) (ethereum/int->hex value))
|
||||
{:from from}
|
||||
params)
|
||||
#(cb %1 (ethereum/hex->boolean %2))))
|
||||
|
||||
(defn transfer-from [web3 contract from-address to-address value cb]
|
||||
(ethereum/call web3
|
||||
(ethereum/call-params contract "transferFrom(address, address, uint256)" from-address to-address (ethereum/int->hex value))
|
||||
(ethereum/call-params contract "transferFrom(address,address,uint256)" (ethereum/normalized-address from-address) (ethereum/normalized-address to-address) (ethereum/int->hex value))
|
||||
#(cb %1 (ethereum/hex->boolean %2))))
|
||||
|
||||
(defn approve [web3 contract address value cb]
|
||||
(ethereum/call web3
|
||||
(ethereum/call-params contract "approve(address, uint256)" address (ethereum/int->hex value))
|
||||
(ethereum/call-params contract "approve(address,uint256)" (ethereum/normalized-address address) (ethereum/int->hex value))
|
||||
#(cb %1 (ethereum/hex->boolean %2))))
|
||||
|
||||
(defn allowance [web3 contract owner-address spender-address cb]
|
||||
(ethereum/call web3
|
||||
(ethereum/call-params contract "allowance(address, address)" owner-address spender-address)
|
||||
(ethereum/call-params contract "allowance(address,address)" (ethereum/normalized-address owner-address) (ethereum/normalized-address spender-address))
|
||||
#(cb %1 (ethereum/hex->bignumber %2))))
|
|
@ -12,6 +12,11 @@
|
|||
:icon {:source (js/require "./resources/images/assets/ethereum.png")
|
||||
:style (asset-border styles/color-light-blue-transparent)}})
|
||||
|
||||
(defn ethereum? [k]
|
||||
(= k (:symbol ethereum)))
|
||||
|
||||
;; symbol are used as global identifier (per network) so they must be unique
|
||||
|
||||
(def all
|
||||
{:mainnet
|
||||
(resolve-icons
|
||||
|
@ -374,8 +379,16 @@
|
|||
:decimals 18
|
||||
:address "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"}])})
|
||||
|
||||
(defn tokens-for [chain-id]
|
||||
(get all chain-id))
|
||||
(defn tokens-for [chain]
|
||||
(get all chain))
|
||||
|
||||
(defn token-for [chain-id symbol]
|
||||
(some #(if (= symbol (:symbol %)) %) (tokens-for chain-id)))
|
||||
(defn symbol->token [chain symbol]
|
||||
(some #(when (= symbol (:symbol %)) %) (tokens-for chain)))
|
||||
|
||||
(defn address->token [chain address]
|
||||
(some #(when (= address (:address %)) %) (tokens-for chain)))
|
||||
|
||||
(defn asset-for [chain symbol]
|
||||
(if (= (:symbol ethereum) symbol)
|
||||
ethereum
|
||||
(symbol->token chain symbol)))
|
|
@ -68,6 +68,10 @@
|
|||
(when-let [bn (bignumber n)]
|
||||
(.dividedBy bn (eth-units unit))))
|
||||
|
||||
(defn ->wei [unit n]
|
||||
(when-let [bn (bignumber n)]
|
||||
(.times bn (eth-units unit))))
|
||||
|
||||
(defn to-fixed [bn]
|
||||
(when bn
|
||||
(.toFixed bn)))
|
||||
|
|
|
@ -10,35 +10,52 @@
|
|||
(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"))))
|
||||
(is (= nil(eip681/parse-uri "ethereum:0x1234")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "1" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=1")))
|
||||
(is (= nil (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?unknown=1")))
|
||||
(is (= nil (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?address=0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "2.014e18" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=2.014e18")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "-1e18" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=-1e18")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "+1E18" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=+1E18")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "1E18" :gas "100" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=1E18&gas=100")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "NOT_NUMBER" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=NOT_NUMBER")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "1ETH" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=1ETH")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "1e18" :gas "5000" :chain-id 1} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7@1?value=1e18&gas=5000")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "1e18" :gas "5000" :chain-id 3} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7@3?value=1e18&gas=5000")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :chain-id 1 :function-name "transfer"} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer")))
|
||||
(is (= {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :chain-id 1 :function-name "transfer" :function-arguments {:address "0x8e23ee67d1332ad560396262c48ffbb01f93d052" :uint256 "1"}} (eip681/parse-uri "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x8e23ee67d1332ad560396262c48ffbb01f93d052&uint256=1"))))
|
||||
|
||||
(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"}))))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" nil)))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" {})))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" {:value nil})))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=1" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" {:value (money/bignumber 1)})))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=1000000000000000000" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" {:value (money/bignumber 1e18)})))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=1&gas=100" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" {:value (money/bignumber 1) :gas (money/bignumber 100) :chain-id 1})))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7@3?value=1&gas=100" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" {:value (money/bignumber 1) :gas (money/bignumber 100) :chain-id 3})))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x8e23ee67d1332ad560396262c48ffbb01f93d052&uint256=1" (eip681/generate-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" {:value (money/bignumber 1) :gas (money/bignumber 100) :chain-id 1 :function-name "transfer" :function-arguments {:address "0x8e23ee67d1332ad560396262c48ffbb01f93d052" :uint256 1}}))))
|
||||
|
||||
(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"}))))
|
||||
(deftest parse-eth-value
|
||||
(is (= nil (eip681/parse-eth-value nil)))
|
||||
(is (= nil (eip681/parse-eth-value 1)))
|
||||
(is (= nil (eip681/parse-eth-value "NOT_NUMBER")))
|
||||
(is (.equals (money/bignumber 1) (eip681/parse-eth-value "1")))
|
||||
(is (.equals (money/bignumber 2.014e18) (eip681/parse-eth-value "2.014e18")))
|
||||
(is (.equals (money/bignumber 1e18) (eip681/parse-eth-value "1ETH")))
|
||||
(is (.equals (money/bignumber -1e18) (eip681/parse-eth-value "-1e18")))
|
||||
(is (.equals (money/bignumber 1e18) (eip681/parse-eth-value "1E18")))
|
||||
(is (.equals (money/bignumber "111122223333441239") (eip681/parse-eth-value "111122223333441239"))))
|
||||
|
||||
(deftest extract-request-details
|
||||
(let [{:keys [value symbol address]} (eip681/extract-request-details {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" :value "1ETH"})]
|
||||
(is (.equals (money/ether->wei (money/bignumber 1)) value))
|
||||
(is (= :ETH symbol))
|
||||
(is (= "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" address)))
|
||||
(is (nil? (eip681/extract-request-details {:address "0x744d70fdbe2ba4cf95131626614a1763df805b9e" :chain-id 1 :function-name "unknown"})))
|
||||
(let [{:keys [value symbol address]} (eip681/extract-request-details {:address "0x744d70fdbe2ba4cf95131626614a1763df805b9e" :chain-id 1
|
||||
:function-name "transfer" :function-arguments {:uint256 1000 :address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}})]
|
||||
(is (.equals (money/bignumber 1000) value))
|
||||
(is (= :SNT symbol))
|
||||
(is (= "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" address))))
|
Loading…
Reference in New Issue