[#7983] Basic user added ERC-20 support

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2019-05-20 10:02:56 +02:00
parent 543ccb69e8
commit 23b04288f1
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
27 changed files with 463 additions and 85 deletions

View File

@ -286,7 +286,6 @@
:wallet-sign-message-modal
:contact-code
:wallet-onboarding-setup
:wallet-settings-assets
:wallet-modal
:wallet-onboarding-setup-modal
:wallet-settings-hook)
@ -301,7 +300,6 @@
:recent-recipients
:wallet-send-assets
:wallet-request-assets
:wallet-settings-assets
:wallet-modal} current-view)
[view {:background-color colors/white
:position :absolute

View File

@ -23,17 +23,21 @@
[status-im.ui.screens.wallet.db :as wallet.db]
[status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events]
[status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.wallet.utils :as wallet.utils]))
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.ui.components.chat-icon.screen :as chat-icon]))
;; common `send/request` functionality
(defn- render-asset [{:keys [name symbol amount decimals] :as asset}]
(defn- render-asset [{:keys [name symbol amount decimals icon color] :as asset}]
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:chat.ui/set-command-parameter (wallet.utils/display-symbol asset)])}
[react/view transactions-styles/asset-container
[react/view transactions-styles/asset-main
[react/image {:source (-> asset :icon :source)
:style transactions-styles/asset-icon}]
(if icon
[react/image {:source (:source icon)
:style transactions-styles/asset-icon}]
[react/view {:style transactions-styles/asset-icon}
[chat-icon/custom-icon-view-list name color 30]])
[react/text (wallet.utils/display-symbol asset)]
[react/text {:style transactions-styles/asset-name} name]]
;;TODO(goranjovic) : temporarily disabled to fix https://github.com/status-im/status-react/issues/4963

View File

@ -4,3 +4,7 @@
(defn uint
[hex]
(first (abi-spec/decode hex ["uint"])))
(defn string
[hex]
(first (abi-spec/decode hex ["string"])))

View File

@ -57,7 +57,8 @@
[status-im.utils.logging.core :as logging]
[status-im.utils.utils :as utils]
[status-im.web3.core :as web3]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[status-im.wallet.custom-tokens.core :as custom-tokens]))
;; init module
@ -2057,3 +2058,52 @@
:ethereum.signal/new-block
(fn [cofx [_ block-number]]
(ethereum.subscriptions/new-block cofx block-number)))
;;custom tokens
(handlers/register-handler-fx
:wallet.custom-token/decimals-result
(fn [cofx [_ result]]
(custom-tokens/decimals-result cofx result)))
(handlers/register-handler-fx
:wallet.custom-token/symbol-result
(fn [cofx [_ contract result]]
(custom-tokens/symbol-result cofx contract result)))
(handlers/register-handler-fx
:wallet.custom-token/name-result
(fn [cofx [_ contract result]]
(custom-tokens/name-result cofx contract result)))
(handlers/register-handler-fx
:wallet.custom-token/balance-result
(fn [cofx [_ contract result]]
(custom-tokens/balance-result cofx contract result)))
(handlers/register-handler-fx
:wallet.custom-token/total-supply-result
(fn [cofx [_ contract result]]
(custom-tokens/total-supply-result cofx contract result)))
(handlers/register-handler-fx
:wallet.custom-token/contract-address-is-pasted
(fn [cofx [_ contract]]
(custom-tokens/contract-address-is-changed cofx contract)))
(handlers/register-handler-fx
:wallet.custom-token.ui/contract-address-paste
(fn [_ _]
{:wallet.custom-token/contract-address-paste nil}))
(handlers/register-handler-fx
:wallet.custom-token.ui/field-is-edited
(fn [cofx [_ field-key value]]
(custom-tokens/field-is-edited cofx field-key value)))
(handlers/register-handler-fx
:wallet.custom-token.ui/add-pressed
(fn [cofx _]
(fx/merge cofx
(custom-tokens/add-pressed)
(navigation/navigate-back))))

View File

@ -186,12 +186,15 @@
(fx/defn initialize-tokens
[{:keys [db]}]
(let [network-id (get-in db [:account/account :network])
network (get-in db [:account/account :networks network-id])
chain (ethereum/network->chain-keyword network)]
(let [network-id (get-in db [:account/account :network])
network (get-in db [:account/account :networks network-id])
custom-tokens (get-in db [:account/account :settings :wallet :custom-tokens])
chain (ethereum/network->chain-keyword network)]
(merge
{:db (assoc db :wallet/all-tokens
(utils.core/map-values #(utils.core/index-by :address %) tokens/all-default-tokens))}
(merge-with merge
(utils.core/map-values #(utils.core/index-by :address %) tokens/all-default-tokens)
custom-tokens))}
(when config/erc20-contract-warnings-enabled?
{:wallet/validate-tokens {:web3 (:web3 db)
:tokens (get tokens/all-default-tokens chain)}}))))

View File

@ -150,6 +150,7 @@
(reg-root-key-sub :wallet/all-tokens :wallet/all-tokens)
(reg-root-key-sub :prices-loading? :prices-loading?)
(reg-root-key-sub :wallet.transactions :wallet.transactions)
(reg-root-key-sub :wallet/custom-token-screen :wallet/custom-token-screen)
;;ethereum
(reg-root-key-sub :ethereum/current-block :ethereum/current-block)
@ -1001,7 +1002,7 @@
(fn [[network visible-tokens-symbols all-tokens]]
(let [chain (ethereum/network->chain-keyword network)]
(conj (filter #(contains? visible-tokens-symbols (:symbol %))
(tokens/sorted-tokens-for all-tokens (ethereum/network->chain-keyword network)))
(tokens/sorted-tokens-for all-tokens chain))
(tokens/native-currency chain)))))
(re-frame/reg-sub

View File

@ -81,6 +81,12 @@
:default-chat-icon-text styles/default-chat-icon-text}
hide-dapp?])
(defn custom-icon-view-list
[name color & [size]]
[react/view (styles/container-list-size (or size 40))
[default-chat-icon name {:default-chat-icon (styles/default-chat-icon-profile color (or size 40))
:default-chat-icon-text styles/default-chat-icon-text}]])
(defn contact-icon-view
[{:keys [photo-path name dapp?]} {:keys [container] :as styles}]
[react/view container

View File

@ -180,6 +180,10 @@
{:width 40
:height 40})
(defn container-list-size [size]
{:width size
:height size})
(def container-chat-toolbar
{:width 36
:height 36})

View File

@ -68,19 +68,20 @@
on-press
forward?
back?]}]
[react/touchable-highlight {:on-press on-press :disabled disabled?}
[react/view (styles/bottom-button disabled?)
(when back?
[vector-icons/icon :main-icons/back {:color colors/blue
:container-style {:align-self :baseline}}])
[react/text {:style styles/bottom-button-label
:accessibility-label accessibility-label}
(or label (i18n/label :t/next))]
(when forward?
[vector-icons/icon :main-icons/next {:color colors/blue}])]])
(let [color (if disabled? colors/gray colors/blue)]
[react/touchable-highlight {:on-press on-press :disabled disabled?}
[react/view styles/bottom-button
(when back?
[vector-icons/icon :main-icons/back {:color color
:container-style {:align-self :baseline}}])
[react/text {:style {:color color}
:accessibility-label accessibility-label}
(or label (i18n/label :t/next))]
(when forward?
[vector-icons/icon :main-icons/next {:color color}])]]))
(defn button [{:keys [on-press label background? button-style label-style disabled?] :or {background? true disabled false}}]
[react/touchable-highlight {:style (styles/button button-style background? disabled?)
(defn button [{:keys [on-press label background? button-style label-style disabled?] :or {background? true disabled? false}}]
[react/touchable-highlight {:style (styles/button button-style background? disabled?)
:on-press on-press
:disabled disabled?}
[react/text {:style (merge styles/button-label label-style)}
@ -89,8 +90,8 @@
(defn red-button [props]
[react/view {:align-items :center}
[button (merge props
{:label-style {:color colors/red :font-size 15}
:button-style {:padding-horizontal 32 :background-color colors/red-light}})]])
{:label-style {:color colors/red :font-size 15}
:button-style {:padding-horizontal 32 :background-color colors/red-light}})]])
(defn counter
([value] (counter nil value))

View File

@ -103,13 +103,9 @@
:width icon-size
:height icon-size})
(defn bottom-button [disabled?]
(def bottom-button
{:flex-direction :row
:align-items :center
:opacity (if disabled? 0.4 1)})
(def bottom-button-label
{:color colors/blue})
:align-items :center})
(defn button [style background? disabled?]
(merge

View File

@ -0,0 +1,7 @@
(ns status-im.ui.components.list-header.views
(:require [status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors]))
(defn list-header [title]
[react/view {:style {:padding-top 14 :padding-bottom 4 :padding-horizontal 16}}
[react/text {:style {:color colors/gray}} title]])

View File

@ -70,7 +70,8 @@
:wallet-transaction-fee {:type :modal-wallet}
:wallet-onboarding-setup-modal {:type :modal-wallet}
:wallet-send-transaction-modal {:type :modal-wallet}
:wallet-settings-assets {:type :modal-wallet}
:wallet-settings-assets {:type :wallet}
:wallet-add-custom-token {:type :wallet}
:wallet-sign-message-modal {:type :modal-wallet}
:wallet-settings-hook {:type :wallet}
:wallet-transaction-sent {:type :transparent}

View File

@ -3,20 +3,26 @@
(:require [status-im.ui.components.colors :as colors]
[status-im.utils.platform :as p]))
(def label
{:font-size 14})
(defn label [editable]
(merge
{:margin-vertical 10}
(when-not editable {:color colors/gray})))
(defn input-container [height]
{:padding 16
:justify-content :center
:margin-vertical 8
:height (or height 52)
:border-radius 8
:background-color colors/gray-lighter})
(defn input-container [height editable]
(merge
{:padding 16
:justify-content :center
:height (or height 52)
:border-radius 8
:background-color (when editable colors/gray-lighter)}
(when-not editable
{:border-color colors/gray-lighter
:border-width 1})))
(defstyle input
{:padding 0
:desktop {:height 52}})
{:padding 0
:text-align-vertical :top
:desktop {:height 52}})
(defn error [label?]
{:bottom-value (if label? -20 0)

View File

@ -6,20 +6,20 @@
[status-im.ui.components.tooltip.views :as tooltip]))
(defn merge-container-styles
[height container]
(let [merged-styles (merge (styles/input-container height) container)]
[height container editable]
(let [merged-styles (merge (styles/input-container height editable) container)]
;; `:background-color` can't be nil; in this case the app will crash.
;; Nevertheless, we need to be able to remove background if necessary.
(if (nil? (:background-color merged-styles))
(dissoc merged-styles :background-color)
merged-styles)))
(defn text-input-with-label [{:keys [label content error style height container text] :as props}]
(defn text-input-with-label [{:keys [label content error style height container text editable] :as props :or {editable true}}]
[react/view
(when label
[react/text {:style styles/label}
[react/text {:style (styles/label editable)}
label])
[react/view {:style (merge-container-styles height container)}
[react/view {:style (merge-container-styles height container editable)}
[react/text-input
(merge
{:style (merge styles/input style)

View File

@ -198,6 +198,7 @@
(spec/def :stickers/selected-pack (spec/nilable any?))
(spec/def :stickers/recent (spec/nilable vector?))
(spec/def :extensions/profile (spec/nilable any?))
(spec/def :wallet/custom-token-screen (spec/nilable map?))
(spec/def ::db (spec/keys :opt [:contacts/contacts
:contacts/new-identity
@ -273,7 +274,8 @@
:stickers/packs-pendning
:bottom-sheet/show?
:bottom-sheet/view
:extensions/profile]
:extensions/profile
:wallet/custom-token-screen]
:opt-un [::modal
::was-modal?
::rpc-url

View File

@ -24,7 +24,6 @@
:enter-pin-modal
:hardwallet-connect-modal
:selection-modal-screen
:wallet-settings-assets
:wallet-transaction-fee
:wallet-transactions-filter
:profile-qr-viewer

View File

@ -77,7 +77,8 @@
[status-im.ui.screens.wallet.transaction-sent.views
:as
transaction-sent]
[status-im.ui.screens.wallet.transactions.views :as wallet-transactions]))
[status-im.ui.screens.wallet.transactions.views :as wallet-transactions]
[status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens]))
(def all-screens
{:login login/login
@ -142,7 +143,8 @@
:wallet-transaction-details wallet-transactions/transaction-details
:wallet-settings-hook wallet-settings/settings-hook
:selection-modal-screen [:modal screens.extensions/selection-modal-screen]
:wallet-settings-assets [:modal wallet-settings/manage-assets]
:wallet-settings-assets wallet-settings/manage-assets
:wallet-add-custom-token custom-tokens/add-custom-token
:wallet-transactions-filter [:modal wallet-transactions/filter-history]
:my-profile profile.user/my-profile
:my-profile-ext-settings profile.user/extensions-settings

View File

@ -24,5 +24,7 @@
:transactions-history
:wallet-transaction-details
:wallet-settings-hook
:extension-screen-holder]
:extension-screen-holder
:wallet-settings-assets
:wallet-add-custom-token]
:config {:initialRouteName :wallet}})

View File

@ -28,7 +28,8 @@
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.utils.ethereum.eip55 :as eip55]))
[status-im.utils.ethereum.eip55 :as eip55]
[status-im.ui.components.chat-icon.screen :as chat-icon]))
;; Wallet tab has a different coloring scheme (dark) that forces color changes (background, text)
;; It might be replaced by some theme mechanism
@ -115,12 +116,14 @@
:request :wallet.request/set-symbol
(throw (str "Unknown type: " k))))
(defn- render-token [{:keys [symbol name icon decimals amount] :as token} type]
(defn- render-token [{:keys [symbol name icon decimals amount color] :as token} type]
[list/touchable-item #(do (re-frame/dispatch [(type->handler type) symbol])
(re-frame/dispatch [:navigate-back]))
[react/view
[list/item
[list/item-image icon]
(if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list name color])
[list/item-content
[react/view {:flex-direction :row}
[react/text {:style styles/text}
@ -155,14 +158,16 @@
(views/letsubs [balance [:balance]
network [:network]
all-tokens [:wallet/all-tokens]]
(let [{:keys [name icon decimals] :as token} (tokens/asset-for all-tokens (ethereum/network->chain-keyword network) symbol)]
(let [{:keys [name icon decimals color] :as token} (tokens/asset-for all-tokens (ethereum/network->chain-keyword network) symbol)]
(when name
[react/view
[cartouche {:disabled? disabled? :on-press #(re-frame/dispatch [:navigate-to (type->view type)])}
(i18n/label :t/wallet-asset)
[react/view {:style styles/asset-content-container
:accessibility-label :choose-asset-button}
[list/item-image (assoc icon :style styles/asset-icon :image-style {:width 32 :height 32})]
(if icon
[list/item-image (assoc icon :style styles/asset-icon :image-style {:width 32 :height 32})]
[chat-icon/custom-icon-view-list name color 32])
[react/view styles/asset-text-content
[react/view styles/asset-label-content
[react/text {:style (merge styles/text-content styles/asset-label)}

View File

@ -0,0 +1,101 @@
(ns status-im.ui.screens.wallet.custom-tokens.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.text-input.view :as text-input]
[status-im.ui.components.common.common :as components.common]
[clojure.string :as string]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.i18n :as i18n]))
(def debounce-timers (atom {}))
(defn debounce-and-save [field-key value]
(let [timeout (get @debounce-timers field-key)]
(when timeout (js/clearTimeout timeout))
(swap! debounce-timers assoc field-key
(js/setTimeout
#(re-frame/dispatch [:wallet.custom-token.ui/field-is-edited field-key (string/trim value)])
500))))
(defview add-custom-token []
(letsubs [{:keys [contract name symbol balance decimals in-progress? error error-name error-symbol]}
[:wallet/custom-token-screen]]
[react/keyboard-avoiding-view {:flex 1 :background-color :white}
[status-bar/status-bar]
[toolbar/toolbar nil
toolbar/default-nav-back
[toolbar/content-title
(i18n/label :t/add-custom-token)]]
[react/scroll-view {:keyboard-should-persist-taps :handled :style {:flex 1 :margin-top 8 :padding-horizontal 16}}
[react/view {:style {:flex-direction :row :justify-content :space-between :padding-vertical 10}}
[react/text (i18n/label :t/contract-address)]
(if in-progress?
[react/view {:flex-direction :row :justify-content :center}
[react/view {:height 20}
[react/activity-indicator {:width 24 :height 24 :animating true}]]
[react/text {:style {:color colors/gray :margin-left 5}}
(i18n/label :t/processing)]])]
(when-not in-progress?
;;tooltip covers button
[react/view {:position :absolute :z-index 1000 :right 0 :top 10}
[react/touchable-highlight {:on-press #(re-frame/dispatch [:wallet.custom-token.ui/contract-address-paste])}
[react/text {:style {:color colors/blue}}
(i18n/label :t/paste)]]])
[text-input/text-input-with-label
{:on-change-text #(debounce-and-save :contract %)
:error error
:default-value contract
:multiline true
:height 78
:placeholder (i18n/label :t/specify-address)}]
[react/view {:height 16}]
[text-input/text-input-with-label
{:on-change-text #(debounce-and-save :name %)
:label (i18n/label :t/name)
:default-value name
:error error-name
:placeholder (i18n/label :t/name-of-token)}]
[react/view {:height 16}]
[react/view {:style {:flex-direction :row}}
[react/view {:flex 1}
[text-input/text-input-with-label
{:on-change-text #(debounce-and-save :symbol %)
:label (i18n/label :t/symbol)
:error error-symbol
:default-value symbol
:placeholder "ABC"}]]
[react/view {:flex 1 :margin-left 33}
[text-input/text-input-with-label
{:label (i18n/label :t/decimals)
:on-change-text #(debounce-and-save :decimals %)
:default-value decimals
:keyboard-type :number-pad
:max-length 2
:placeholder "18"}]]]
[react/view {:height 16}]
[text-input/text-input-with-label
{:label (i18n/label :t/balance)
:default-value (when (and balance decimals)
(wallet.utils/format-amount balance decimals))
:editable false
:placeholder (i18n/label :t/no-tokens-found)}]]
[react/view {:style {:height 1 :background-color colors/gray-lighter}}]
[react/view {:flex-direction :row
:margin-horizontal 12
:margin-vertical 15
:align-items :center}
[react/view {:style {:flex 1}}]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/add)
:disabled? (boolean
(or in-progress?
error error-name error-symbol
(string/blank? contract) (string/blank? name)
(string/blank? symbol) (string/blank? decimals)))
:on-press #(re-frame/dispatch [:wallet.custom-token.ui/add-pressed])}]]]))

View File

@ -22,7 +22,8 @@
status-im.ui.screens.wallet.collectibles.superrare.views
status-im.ui.screens.wallet.collectibles.kudos.views
[status-im.ui.components.status-bar.view :as status-bar.view]
[status-im.ui.screens.wallet.transactions.views :as transactions.views]))
[status-im.ui.screens.wallet.transactions.views :as transactions.views]
[status-im.ui.components.chat-icon.screen :as chat-icon]))
(defn toolbar-modal [modal-history?]
[react/view
@ -79,11 +80,13 @@
:action #(re-frame/dispatch [:navigate-to :transactions-history])}])
(defn- render-asset [currency]
(fn [{:keys [symbol symbol-display icon decimals amount] :as token}]
(fn [{:keys [symbol icon decimals amount color] :as token}]
(let [asset-value (re-frame/subscribe [:asset-value symbol decimals (-> currency :code keyword)])]
[react/view {:style styles/asset-item-container}
[list/item
[list/item-image icon]
(if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list (:name token) color])
[react/view {:style styles/asset-item-value-container}
[react/text {:style styles/asset-item-value
:number-of-lines 1

View File

@ -54,3 +54,7 @@
(do
(re-frame/dispatch [:wallet/update-gas-price])
(assoc-in db [:wallet :send-transaction] transaction-send-default))))
(defmethod navigation/preload-data! :wallet-add-custom-token
[db [event]]
(dissoc db :wallet/custom-token-screen))

View File

@ -11,11 +11,21 @@
(conj (or ids #{}) id)
(disj ids id)))
(fx/defn toggle-visible-token [{{:account/keys [account]} :db :as cofx} symbol checked?]
(defn update-toggle-in-settings [{{:account/keys [account]} :db} symbol checked?]
(let [network (get (:networks account) (:network account))
chain (ethereum/network->chain-keyword network)
settings (get account :settings)
new-settings (update-in settings [:wallet :visible-tokens chain] #(set-checked % symbol checked?))]
settings (get account :settings)]
(update-in settings [:wallet :visible-tokens chain] #(set-checked % symbol checked?))))
(fx/defn toggle-visible-token [cofx symbol checked?]
(let [new-settings (update-toggle-in-settings cofx symbol checked?)]
(accounts.update/update-settings cofx new-settings {})))
(fx/defn add-custom-token [{{:account/keys [account]} :db :as cofx} {:keys [symbol address] :as token}]
(let [network (get (:networks account) (:network account))
chain (ethereum/network->chain-keyword network)
settings (update-toggle-in-settings cofx symbol true)
new-settings (assoc-in settings [:wallet :custom-tokens chain address] token)]
(accounts.update/update-settings cofx new-settings {})))
(fx/defn configure-token-balance-and-visibility [cofx symbol balance]

View File

@ -9,17 +9,28 @@
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.toolbar.actions :as toolbar.actions]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.screens.wallet.styles :as wallet.styles]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.ui.components.toolbar.actions :as actions]))
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.action-button.action-button :as action-button]
[status-im.ui.components.list-header.views :as list-header]
[status-im.ui.components.chat-icon.screen :as chat-icon]))
(defn- render-token [{:keys [symbol name icon]} visible-tokens]
(defn toolbar []
[toolbar/toolbar nil
[toolbar/nav-button
(toolbar.actions/back #(re-frame/dispatch [:update-wallet-and-nav-back]))]
[toolbar/content-title
(i18n/label :t/wallet-assets)]])
(defn- render-token [{:keys [symbol name icon color]} visible-tokens]
[list/list-item-with-checkbox
{:checked? (contains? visible-tokens (keyword symbol))
:on-value-change #(re-frame/dispatch [:wallet.settings/toggle-visible-token (keyword symbol) %])}
[list/item
[list/item-image icon]
(if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list name color])
[list/item-content
[list/item-primary name]
[list/item-secondary symbol]]]])
@ -28,26 +39,35 @@
(letsubs [network [:network]
visible-tokens [:wallet/visible-tokens-symbols]
all-tokens [:wallet/all-tokens]]
[react/view (merge components.styles/flex {:background-color :white})
[status-bar/status-bar {:type :modal-wallet}]
[toolbar/toolbar
{:style {:background-color colors/blue
:border-bottom-width 0}}
[toolbar/nav-button
(toolbar.actions/close-white #(re-frame/dispatch [:update-wallet-and-nav-back]))]
[toolbar/content-title {:color colors/white}
(i18n/label :t/wallet-assets)]]
[react/view {:style components.styles/flex}
[list/flat-list {:data (tokens/sorted-tokens-for all-tokens (ethereum/network->chain-keyword network))
:key-fn (comp str :symbol)
:render-fn #(render-token % visible-tokens)}]]]))
(let [chain-key (ethereum/network->chain-keyword network)
{custom-tokens true default-tokens nil} (group-by :custom? (tokens/sorted-tokens-for all-tokens chain-key))]
[react/view (merge components.styles/flex {:background-color :white})
[status-bar/status-bar]
[toolbar]
[react/view {:style {:flex 1 :padding-top 16}}
[action-button/action-button {:label (i18n/label :t/add-custom-token)
:icon :main-icons/add
:icon-opts {:color :blue}
:on-press #(re-frame/dispatch [:navigate-to :wallet-add-custom-token])}]
[list/section-list
{:sections (concat
(when (seq custom-tokens)
[{:title (i18n/label :t/custom)
:data custom-tokens}])
[{:title (i18n/label :t/default)
:data default-tokens}])
:key-fn :address
:stickySectionHeadersEnabled false
:render-section-header-fn (fn [{:keys [title data]}]
[list-header/list-header title])
:render-fn #(render-token % visible-tokens)}]]])))
(defn- create-payload [address]
{:address (ethereum/normalized-address address)})
(defview settings-hook []
(letsubs [{:keys [label view on-close]} [:get-screen-params :wallet-settings-hook]
{address :address} [:account/account]]
{address :address} [:account/account]]
[react/keyboard-avoiding-view {:style {:flex 1 :background-color colors/blue}}
[status-bar/status-bar {:type :wallet}]
[toolbar/toolbar
@ -69,7 +89,7 @@
(re-frame/dispatch [:navigate-to :wallet-settings-hook m]))})
(defview toolbar-view []
(letsubs [settings [:wallet/settings]
(letsubs [settings [:wallet/settings]
{address :address} [:account/account]]
[toolbar/toolbar {:style {:background-color colors/blue
:border-bottom-width 0}}

View File

@ -57,6 +57,9 @@
(defn network->chain-keyword [network]
(chain-id->chain-keyword (network->chain-id network)))
(defn get-chain-keyword [db]
(network->chain-keyword (get-in db [:account/account :networks (:network db)])))
(defn network->chain-name [network]
(-> network
network->chain-keyword

View File

@ -0,0 +1,131 @@
(ns status-im.wallet.custom-tokens.core
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.utils.ethereum.core :as ethereum]
[clojure.string :as string]
[status-im.ethereum.decode :as decode]
[status-im.utils.fx :as fx]
[status-im.ui.screens.wallet.settings.models :as models]))
(re-frame/reg-fx
:wallet.custom-token/get-decimals
(fn [contract]
(ethereum/call
(ethereum/call-params contract "decimals()")
#(re-frame/dispatch [:wallet.custom-token/decimals-result %]))))
(re-frame/reg-fx
:wallet.custom-token/get-symbol
(fn [contract]
(ethereum/call
(ethereum/call-params contract "symbol()")
#(re-frame/dispatch [:wallet.custom-token/symbol-result contract %]))))
(re-frame/reg-fx
:wallet.custom-token/get-balance
(fn [[contract address]]
(ethereum/call
(ethereum/call-params contract "balanceOf(address)" (ethereum/normalized-address address))
#(re-frame/dispatch [:wallet.custom-token/balance-result contract %]))))
(re-frame/reg-fx
:wallet.custom-token/get-name
(fn [contract]
(ethereum/call
(ethereum/call-params contract "name()")
#(re-frame/dispatch [:wallet.custom-token/name-result contract %]))))
(re-frame/reg-fx
:wallet.custom-token/get-total-supply
(fn [contract]
(ethereum/call
(ethereum/call-params contract "totalSupply()")
#(re-frame/dispatch [:wallet.custom-token/total-supply-result contract %]))))
(re-frame/reg-fx
:wallet.custom-token/contract-address-paste
(fn []
(react/get-from-clipboard #(re-frame/dispatch [:wallet.custom-token/contract-address-is-pasted (string/trim %)]))))
(defn field-exists? [{:wallet/keys [all-tokens] :as db} field-key field-value]
(let [chain-key (ethereum/get-chain-keyword db)]
(some #(= field-value (get % field-key)) (vals (get all-tokens chain-key)))))
(fx/defn total-supply-result [{:keys [db]} contract result]
(if (and (string? result) (string/starts-with? result "0x") (> (count result) 2))
{:wallet.custom-token/get-balance [contract (get-in db [:account/account :address])]}
{:db (update db :wallet/custom-token-screen merge {:in-progress? nil :error (i18n/label :t/wrong-contract)})}))
(defn token-in-list? [{:wallet/keys [all-tokens] :as db} contract]
(let [chain-key (ethereum/get-chain-keyword db)
addresses (set (map string/lower-case (keys (get all-tokens chain-key))))]
(not (nil? (get addresses (string/lower-case contract))))))
(fx/defn contract-address-is-changed [{:keys [db]} contract]
(if (ethereum/address? contract)
(if (token-in-list? db contract)
{:db (assoc db :wallet/custom-token-screen {:contract contract :error (i18n/label :t/already-have-asset)})}
{:db (assoc db :wallet/custom-token-screen {:contract contract :in-progress? true})
:wallet.custom-token/get-total-supply contract})
{:db (assoc db :wallet/custom-token-screen {:contract contract :error (i18n/label :t/wrong-address)})}))
(fx/defn decimals-result [{:keys [db]} result]
{:db (update db :wallet/custom-token-screen merge {:decimals (str (decode/uint result))
:in-progress? nil})})
(fx/defn symbol-result [{:keys [db]} contract result]
(let [token-symbol (decode/string result)
symbol-exists? (field-exists? db :symbol (keyword token-symbol))]
{:db
(update db :wallet/custom-token-screen merge
{:symbol token-symbol
:error-symbol (when symbol-exists?
(i18n/label :t/you-already-have-an-asset {:value token-symbol}))})
:wallet.custom-token/get-decimals
contract}))
(fx/defn name-result [{:keys [db]} contract result]
(let [token-name (decode/string result)
name-exists? (field-exists? db :name token-name)]
{:db
(update db :wallet/custom-token-screen merge
{:name token-name
:error-name (when name-exists?
(i18n/label :t/you-already-have-an-asset {:value token-name}))})
:wallet.custom-token/get-symbol
contract}))
(fx/defn balance-result [{:keys [db]} contract result]
(if (and (string? result) (string/starts-with? result "0x") (> (count result) 2))
{:db (assoc-in db [:wallet/custom-token-screen :balance] (str (decode/uint result)))
:wallet.custom-token/get-name contract}
{:db (update db :wallet/custom-token-screen merge {:in-progress? nil :error (i18n/label :t/wrong-contract)})}))
(fx/defn add-pressed [{:keys [db] :as cofx}]
(let [{:keys [contract name symbol decimals]} (get db :wallet/custom-token-screen)
chain-key (ethereum/get-chain-keyword db)
symbol (keyword symbol)
new-token {:address contract :name name :symbol symbol :custom? true
:decimals (int decimals) :color (rand-nth colors/chat-colors)}]
(fx/merge (assoc-in cofx [:db :wallet/all-tokens chain-key contract] new-token)
(models/add-custom-token new-token))))
(fx/defn field-is-edited [{:keys [db] :as cofx} field-key value]
(case field-key
:contract (contract-address-is-changed cofx value)
:name {:db (update db :wallet/custom-token-screen merge
{field-key
value
:error-name
(when (field-exists? db field-key value)
(i18n/label :t/you-already-have-an-asset {:value value}))})}
:symbol {:db (update db :wallet/custom-token-screen merge
{field-key
value
:error-symbol
(when (field-exists? db field-key (keyword value))
(i18n/label :t/you-already-have-an-asset {:value value}))})}
:decimals {:db (assoc-in db [:wallet/custom-token-screen :decimals] value)}))

View File

@ -1029,5 +1029,20 @@
"dapps-can-access" : "ÐApps can access my wallet and contact code",
"require-my-permission" : "Require my permission",
"might-break" : "Might break some ÐApps",
"always-allow" : "Always allow"
"always-allow" : "Always allow",
"you-already-have-an-asset" : "You already have an asset {{value}}",
"add-custom-token" : "Add custom token",
"contract-address" : "Contract address",
"processing" : "Processing",
"paste" : "Paste",
"specify-address" : "Specify address",
"name-of-token" : "The name of your token",
"symbol" : "Symbol",
"decimals" : "Decimals",
"balance" : "Balance",
"no-tokens-found" : "No tokens found",
"wrong-contract" :"Wrong contract",
"already-have-asset" : "You already have this asset",
"wrong-address" : "Wrong address",
"default" : "Default"
}