[performance] improve wallet update performances

`wallet-autoconfig-token` is a very expensive call on mainnet
because it checks the balance of every known token.

it is called:
- when wallet is refreshed by pulling
- when user goes on any wallet screen

this PR changes that by:
- calling it only when the wallet is initialized and there is no
visible-token configuration

it only calls update-wallet when a new transaction arrives
This commit is contained in:
yenda 2019-05-18 13:06:42 +02:00
parent 2cd26c585d
commit 89680f4861
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
25 changed files with 763 additions and 742 deletions

View File

@ -2,14 +2,13 @@
(:require [re-frame.core :as re-frame]
[status-im.accounts.update.core :as accounts.update]
[status-im.i18n :as i18n]
[status-im.ui.screens.navigation :as navigation]
[status-im.native-module.core :as native-module]
[status-im.ui.screens.wallet.settings.models :as wallet.settings.models]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.build :as build]
[status-im.utils.config :as config]
[status-im.utils.utils :as utils]
[status-im.utils.fx :as fx]
[status-im.utils.platform :as platform]
[status-im.utils.build :as build]))
[status-im.utils.utils :as utils]))
(re-frame/reg-fx
::chaos-mode-changed
@ -49,7 +48,6 @@
[{:keys [db] :as cofx} modal?]
(fx/merge cofx
(continue-after-wallet-onboarding modal?)
(wallet.settings.models/wallet-autoconfig-tokens)
(accounts.update/account-update {:wallet-set-up-passed? true} {})))
(fx/defn update-dev-server-state

View File

@ -7,7 +7,6 @@
[status-im.ethereum.transactions.core :as transactions]
[status-im.fleet.core :as fleet]
[status-im.i18n :as i18n]
[status-im.models.wallet :as models.wallet]
[status-im.native-module.core :as status]
[status-im.node.core :as node]
[status-im.protocol.core :as protocol]
@ -22,6 +21,7 @@
[status-im.utils.security :as security]
[status-im.utils.types :as types]
[status-im.utils.universal-links.core :as universal-links]
[status-im.wallet.core :as wallet]
[taoensso.timbre :as log]))
(def rpc-endpoint "https://goerli.infura.io/v3/f315575765b14720b32382a61a89341a")
@ -84,10 +84,10 @@
(fx/defn initialize-wallet [cofx]
(fx/merge cofx
(models.wallet/initialize-tokens)
(wallet/initialize-tokens)
(transactions/initialize)
(ethereum.subscriptions/initialize)
(models.wallet/update-wallet)))
(wallet/update-wallet)))
(fx/defn user-login [{:keys [db] :as cofx} create-database?]
(let [{:keys [address password]} (accounts.db/credentials cofx)]

View File

@ -185,7 +185,7 @@
(defview send-status [tx-hash outgoing]
(letsubs [{:keys [exists? confirmed?]} [:chats/transaction-status tx-hash]]
[react/touchable-highlight {:on-press #(when exists?
(re-frame/dispatch [:show-transaction-details tx-hash]))}
(re-frame/dispatch [:wallet.ui/show-transaction-details tx-hash]))}
[react/view transactions-styles/command-send-status-container
[vector-icons/icon (if confirmed?
:tiny-icons/tiny-check

View File

@ -103,11 +103,7 @@
(defn default-account-settings []
{:web3-opt-in? true
:preview-privacy? false
:wallet {:visible-tokens {:testnet #{:STT :HND}
:mainnet #{:SNT}
:rinkeby #{:MOKSHA :KDO}
:xdai #{}
:poa #{}}}})
:wallet {:visible-tokens {}}})
(def currencies
{:aed {:id :aed :code "AED" :display-name (i18n/label :t/currency-display-name-aed) :symbol "د.إ"}

View File

@ -8,6 +8,7 @@
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.fx :as fx]
[status-im.utils.types :as types]
[status-im.wallet.core :as wallet]
[taoensso.timbre :as log]))
(def confirmations-count-threshold 12)
@ -180,14 +181,18 @@
;; -----------------------------------------------
(fx/defn new
[{:keys [db]} {:keys [hash] :as transaction}]
{:db (assoc-in db [:wallet :transactions hash] transaction)})
[{:keys [db] :as cofx} {:keys [hash] :as transaction}]
(fx/merge cofx
{:db (assoc-in db [:wallet :transactions hash] transaction)}
wallet/update-wallet))
(fx/defn handle-history
[{:keys [db]} transactions]
{:db (update-in db
[:wallet :transactions]
#(merge transactions %))})
[{:keys [db] :as cofx} transactions]
(fx/merge cofx
{:db (update-in db
[:wallet :transactions]
#(merge transactions %))}
wallet/update-wallet))
(fx/defn handle-token-history
[{:keys [db]} transactions]

View File

@ -57,6 +57,7 @@
[status-im.utils.handlers :as handlers]
[status-im.utils.logging.core :as logging]
[status-im.utils.utils :as utils]
[status-im.wallet.core :as wallet]
[status-im.wallet.db :as wallet.db]
[status-im.web3.core :as web3]
[taoensso.timbre :as log]
@ -2147,6 +2148,12 @@
(ethereum.transactions/new cofx transaction)))
;; wallet events
(handlers/register-handler-fx
:wallet.ui/pull-to-refresh
(fn [cofx _]
(wallet/update-wallet cofx)))
(handlers/register-handler-fx
:wallet.transactions/add-filter
(fn [{:keys [db]} [_ id]]
@ -2162,3 +2169,91 @@
(fn [{:keys [db]} _]
{:db (assoc-in db [:wallet :filters]
wallet.db/default-wallet-filters)}))
(handlers/register-handler-fx
:wallet.settings/toggle-visible-token
(fn [cofx [_ symbol checked?]]
(wallet/toggle-visible-token cofx symbol checked?)))
(handlers/register-handler-fx
:wallet/token-found
(fn [cofx [_ symbol balance]]
(wallet/configure-token-balance-and-visibility cofx symbol balance)))
(handlers/register-handler-fx
:TODO.remove/update-wallet
(fn [cofx _]
(wallet/update-wallet cofx)))
(handlers/register-handler-fx
:wallet.settings.ui/navigate-back-pressed
(fn [cofx [_ on-close]]
(fx/merge cofx
(when on-close
{:dispatch on-close})
(navigation/navigate-back)
(wallet/update-wallet))))
(handlers/register-handler-fx
:wallet.callback/update-balance-success
(fn [cofx [_ balance]]
(wallet/update-balance cofx balance)))
(handlers/register-handler-fx
:wallet.callback/update-balance-fail
(fn [cofx [_ err]]
(wallet/on-update-balance-fail cofx err)))
(handlers/register-handler-fx
:wallet.callback/update-token-balance-success
(fn [cofx [_ symbol balance]]
(wallet/update-token-balance cofx symbol balance)))
(handlers/register-handler-fx
:wallet.callback/update-token-balance-fail
(fn [cofx [_ symbol err]]
(wallet/on-update-token-balance-fail cofx symbol err)))
(handlers/register-handler-fx
:wallet.callback/update-prices-success
(fn [cofx [_ prices]]
(wallet/update-prices cofx prices)))
(handlers/register-handler-fx
:wallet.callback/update-prices-fail
(fn [cofx [_ err]]
(wallet/on-update-prices-fail cofx err)))
(handlers/register-handler-fx
:wallet.ui/show-transaction-details
(fn [cofx [_ hash]]
(wallet/open-transaction-details cofx hash)))
(handlers/register-handler-fx
:wallet/show-sign-transaction
(fn [cofx [_ {:keys [id method]} from-chat?]]
(wallet/open-send-transaction-modal cofx id method from-chat?)))
(handlers/register-handler-fx
:wallet/update-gas-price-success
(fn [cofx [_ price edit?]]
(wallet/update-gas-price cofx price edit?)))
(handlers/register-handler-fx
:TODO.remove/update-estimated-gas
(fn [{:keys [db]} [_ obj]]
{:update-estimated-gas {:web3 (:web3 db)
:obj obj
:success-event :wallet/update-estimated-gas-success}}))
(handlers/register-handler-fx
:wallet/update-estimated-gas-success
(fn [cofx [_ gas]]
(wallet/update-estimated-gas-price cofx gas)))
(handlers/register-handler-fx
:wallet.setup.ui/navigate-back-pressed
(fn [{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc-in db [:wallet :send-transaction] {})}
(navigation/navigate-back))))

View File

@ -2,17 +2,17 @@
(:require [clojure.string :as string]
[status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.models.wallet :as models.wallet]
[status-im.utils.hex :as hex]
[status-im.native-module.core :as status]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.fx :as fx]
[status-im.utils.ethereum.ens :as ens]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.ens :as ens]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers]
[status-im.utils.hex :as hex]
[status-im.utils.money :as money]
[status-im.utils.types :as types]
[status-im.native-module.core :as status]))
[status-im.wallet.core :as wallet]))
(handlers/register-handler-fx
:extensions/wallet-ui-on-success
@ -72,7 +72,7 @@
(let [tx-object (assoc (select-keys arguments [:to :gas :gas-price :value :nonce])
:data (when (and method params) (abi-spec/encode method params)))
transaction (prepare-extension-transaction tx-object (:contacts/contacts db) on-success on-failure)]
(models.wallet/open-modal-wallet-for-transaction db transaction tx-object)))
(wallet/open-modal-wallet-for-transaction db transaction tx-object)))
(handlers/register-handler-fx
:extensions/ethereum-send-transaction

View File

@ -1,24 +1,24 @@
(ns status-im.hardwallet.core
(:require [re-frame.core :as re-frame]
status-im.hardwallet.fx
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.accounts.create.core :as accounts.create]
[status-im.accounts.login.core :as accounts.login]
[status-im.accounts.logout.core :as accounts.logout]
[status-im.accounts.recover.core :as accounts.recover]
[status-im.data-store.accounts :as accounts-store]
[status-im.i18n :as i18n]
[status-im.node.core :as node]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.config :as config]
[status-im.utils.datetime :as utils.datetime]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.mnemonic :as mnemonic]
[status-im.utils.fx :as fx]
[status-im.utils.platform :as platform]
[taoensso.timbre :as log]
[status-im.i18n :as i18n]
[status-im.utils.types :as types]
[status-im.accounts.create.core :as accounts.create]
[status-im.node.core :as node]
[status-im.utils.datetime :as utils.datetime]
[status-im.data-store.accounts :as accounts-store]
[status-im.utils.ethereum.core :as ethereum]
[clojure.string :as string]
[status-im.accounts.login.core :as accounts.login]
[status-im.accounts.recover.core :as accounts.recover]
[status-im.models.wallet :as models.wallet]
[status-im.utils.ethereum.mnemonic :as mnemonic]
[status-im.accounts.logout.core :as accounts.logout]))
[status-im.wallet.core :as wallet]
[taoensso.timbre :as log]
status-im.hardwallet.fx))
(def default-pin "000000")
@ -54,7 +54,7 @@
(if navigate-to-browser?
(fx/merge cofx
{:db (assoc-in db [:hardwallet :on-card-connected] nil)}
(models.wallet/discard-transaction)
(wallet/discard-transaction)
(navigation/navigate-to-cofx :browser nil))
(if (= :enter-pin-login (:view-id db))
(navigation/navigate-to-clean cofx :accounts nil)

View File

@ -1,285 +0,0 @@
(ns status-im.models.wallet
(:require [clojure.set :as set]
[status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.config :as config]
[status-im.utils.core :as utils.core]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.fx :as fx]
[status-im.utils.hex :as utils.hex]
[status-im.utils.money :as money]))
(def min-gas-price-wei (money/bignumber 1))
(defmulti invalid-send-parameter? (fn [type _] type))
(defmethod invalid-send-parameter? :gas-price [_ value]
(cond
(not value) :invalid-number
(.lt (money/->wei :gwei value) min-gas-price-wei) :not-enough-wei
(-> (money/->wei :gwei value) .decimalPlaces pos?) :invalid-number))
(defmethod invalid-send-parameter? :default [_ value]
(when (or (not value)
(<= value 0))
:invalid-number))
(defn- calculate-max-fee
[gas gas-price]
(if (and gas gas-price)
(money/to-fixed (money/wei->ether (.times gas gas-price)))
"0"))
(defn- edit-max-fee [edit]
(let [gas (get-in edit [:gas-price :value-number])
gas-price (get-in edit [:gas :value-number])]
(assoc edit :max-fee (calculate-max-fee gas gas-price))))
(defn add-max-fee [{:keys [gas gas-price] :as transaction}]
(assoc transaction :max-fee (calculate-max-fee gas gas-price)))
(defn build-edit [edit-value key value]
"Takes the previous edit, either :gas or :gas-price and a value as string.
Wei for gas, and gwei for gas price.
Validates them and sets max fee"
(let [bn-value (money/bignumber value)
invalid? (invalid-send-parameter? key bn-value)
data (if invalid?
{:value value
:max-fee 0
:invalid? invalid?}
{:value value
:value-number (if (= :gas-price key)
(money/->wei :gwei bn-value)
bn-value)
:invalid? false})]
(-> edit-value
(assoc key data)
edit-max-fee)))
(defn edit-value
[key value {:keys [db]}]
{:db (update-in db [:wallet :edit] build-edit key value)})
;; DAPP TRANSACTION -> SEND TRANSACTION
(defn prepare-dapp-transaction [{{:keys [id method params]} :payload message-id :message-id} contacts]
(let [{:keys [to value data gas gasPrice nonce]} (first params)
contact (get contacts (utils.hex/normalize-hex to))]
(cond-> {:id (str id)
:to-name (or (when (nil? to)
(i18n/label :t/new-contract))
contact)
:symbol :ETH
:method method
:to to
:amount (money/bignumber (or value 0))
:gas (cond
gas
(money/bignumber gas)
(and value (empty? data))
(money/bignumber 21000))
:gas-price (when gasPrice
(money/bignumber gasPrice))
:data data
:on-result [:wallet.dapp/transaction-on-result message-id]
:on-error [:wallet.dapp/transaction-on-error message-id]}
nonce
(assoc :nonce nonce))))
;; SEND TRANSACTION -> RPC TRANSACTION
(defn prepare-send-transaction [from {:keys [amount to gas gas-price data nonce]}]
(cond-> {:from (ethereum/normalized-address from)
:to (ethereum/normalized-address to)
:value (ethereum/int->hex amount)
:gas (ethereum/int->hex gas)
:gasPrice (ethereum/int->hex gas-price)}
data
(assoc :data data)
nonce
(assoc :nonce nonce)))
;; NOTE (andrey) we need this function, because params may be mixed up, so we need to figure out which one is address
;; and which message
(defn normalize-sign-message-params [params]
(let [first_param (first params)
second_param (second params)
first-param-address? (ethereum/address? first_param)
second-param-address? (ethereum/address? second_param)]
(when (or first-param-address? second-param-address?)
(if first-param-address?
[first_param second_param]
[second_param first_param]))))
(defn web3-error-callback [fx {:keys [webview-bridge]} message-id message]
(assoc fx :browser/send-to-bridge {:message {:type constants/web3-send-async-callback
:messageId message-id
:error message}
:webview webview-bridge}))
(defn dapp-complete-transaction [id result method message-id webview keycard?]
(cond-> {:browser/send-to-bridge {:message {:type constants/web3-send-async-callback
:messageId message-id
:result {:jsonrpc "2.0"
:id (int id)
:result result}}
:webview webview}
:dispatch [:navigate-back]}
(constants/web3-sign-message? method)
(assoc :dispatch (if keycard? [:navigate-to :browser] [:navigate-back]))
(= method constants/web3-send-transaction)
(assoc :dispatch [:navigate-to-clean :wallet-transaction-sent-modal])))
(fx/defn discard-transaction
[{:keys [db]}]
(let [{:keys [on-error]} (get-in db [:wallet :send-transaction])]
(merge {:db (update db :wallet
assoc
:send-transaction {}
:transactions-queue nil)}
(when on-error
{:dispatch (conj on-error "transaction was cancelled by user")}))))
(defn prepare-unconfirmed-transaction [db now hash]
(let [transaction (get-in db [:wallet :send-transaction])
all-tokens (:wallet/all-tokens db)]
(let [chain (:chain db)
token (tokens/symbol->token all-tokens (keyword chain) (:symbol transaction))]
(-> transaction
(assoc :confirmations "0"
:timestamp (str now)
:type :outbound
:hash hash
:value (:amount transaction)
:token token
:gas-limit (str (:gas transaction)))
(update :gas-price str)
(dissoc :message-id :id :gas)))))
(fx/defn handle-transaction-error
[{:keys [db] :as cofx} {:keys [code message]}]
(let [{:keys [on-error]} (get-in db [:wallet :send-transaction])]
(case code
;;WRONG PASSWORD
constants/send-transaction-err-decrypt
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] true))}
(fx/merge cofx
(merge {:db (-> db
(assoc-in [:wallet :transactions-queue] nil)
(assoc-in [:wallet :send-transaction] {}))
:wallet/show-transaction-error message}
(when on-error
{:dispatch (conj on-error message)}))
navigation/navigate-back))))
(defn clear-error-message [db error-type]
(update-in db [:wallet :errors] dissoc error-type))
(defn tokens-symbols [visible-token-symbols all-tokens chain]
(set/difference (set visible-token-symbols) (set (map :symbol (tokens/nfts-for all-tokens chain)))))
(fx/defn initialize-tokens
[{:keys [db]}]
(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
(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)}}))))
(fx/defn update-wallet
[{{:keys [web3 network network-status]
{:keys [address settings]} :account/account :as db} :db}]
(let [all-tokens (:wallet/all-tokens db)
network (get-in db [:account/account :networks network])
chain (ethereum/network->chain-keyword network)
mainnet? (= :mainnet chain)
assets (get-in settings [:wallet :visible-tokens chain])
tokens (tokens-symbols (get-in settings [:wallet :visible-tokens chain]) all-tokens chain)
currency-id (or (get-in settings [:wallet :currency]) :usd)
currency (get constants/currencies currency-id)]
(when (not= network-status :offline)
{:get-balance {:web3 web3
:account-id address
:success-event :update-balance-success
:error-event :update-balance-fail}
:get-tokens-balance {:web3 web3
:account-id address
:symbols assets
:chain chain
:all-tokens all-tokens
:success-event :update-token-balance-success
:error-event :update-token-balance-fail}
:get-prices {:from (if mainnet?
(conj tokens "ETH")
[(-> (tokens/native-currency chain)
(wallet.utils/exchange-symbol))])
:to [(:code currency)]
:mainnet? mainnet?
:success-event :update-prices-success
:error-event :update-prices-fail
:chaos-mode? (:chaos-mode? settings)}
:db (-> db
(clear-error-message :prices-update)
(clear-error-message :balance-update)
(assoc-in [:wallet :balance-loading?] true)
(assoc :prices-loading? true))})))
(defn open-modal-wallet-for-transaction [db transaction tx-object]
(let [{:keys [gas gas-price]} transaction
{:keys [wallet-set-up-passed?]} (:account/account db)]
{:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction] transaction)
(assoc-in [:wallet :send-transaction :original-gas] gas))
:dispatch-n [[:update-wallet]
(when-not gas
[:wallet/update-estimated-gas tx-object])
(when-not gas-price
[:wallet/update-gas-price])
[:navigate-to
(if wallet-set-up-passed?
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]]}))
(fx/defn open-sign-transaction-flow
[{:keys [db] :as cofx} {:keys [gas gas-price] :as transaction}]
(let [go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?])
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]
(fx/merge cofx
(cond-> {:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction]
transaction)
(assoc-in [:wallet :send-transaction :original-gas]
gas))}
(not gas)
(assoc :update-estimated-gas
{:web3 (:web3 db)
:obj (select-keys transaction [:to :data])
:success-event :wallet/update-estimated-gas-success})
(not gas-price)
(assoc :update-gas-price
{:web3 (:web3 db)
:success-event :wallet/update-gas-price-success
:edit? false}))
(update-wallet)
(navigation/navigate-to-cofx go-to-view-id {}))))
(defn send-transaction-screen-did-load
[{:keys [db]}]
{:db (assoc-in db [:navigation/screen-params :wallet-send-modal-stack :modal?] false)})

View File

@ -1,17 +1,17 @@
(ns status-im.stickers.core
(:require [status-im.utils.fx :as fx]
[cljs.reader :as edn]
[status-im.accounts.core :as accounts]
[status-im.ui.screens.navigation :as navigation]
(:require [cljs.reader :as edn]
[re-frame.core :as re-frame]
[status-im.utils.multihash :as multihash]
[status-im.accounts.core :as accounts]
[status-im.constants :as constants]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.stickers :as ethereum.stickers]
[status-im.models.wallet :as models.wallet]
[status-im.utils.money :as money]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.ethereum.erc20 :as erc20]))
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.erc20 :as erc20]
[status-im.utils.ethereum.stickers :as ethereum.stickers]
[status-im.utils.fx :as fx]
[status-im.utils.money :as money]
[status-im.utils.multihash :as multihash]
[status-im.wallet.core :as wallet]))
(fx/defn init-stickers-packs [{:keys [db]}]
(let [sticker-packs (into {} (map #(let [pack (edn/read-string %)]
@ -82,7 +82,7 @@
data (abi-spec/encode "buyToken(uint256,address)" [pack-id address])
tx-object {:to (get erc20/snt-contracts chain)
:data (abi-spec/encode "approveAndCall(address,uint256,bytes)" [stickers-contract price data])}]
(models.wallet/open-modal-wallet-for-transaction
(wallet/open-modal-wallet-for-transaction
db
(prepare-transaction "approve" tx-object [:stickers/pending-pack pack-id])
tx-object)))

View File

@ -11,11 +11,9 @@
[status-im.constants :as constants]
[status-im.contact.db :as contact.db]
[status-im.ethereum.transactions.core :as transactions]
[status-im.ethereum.transactions.etherscan :as transactions.etherscan]
[status-im.fleet.core :as fleet]
[status-im.i18n :as i18n]
[status-im.ethereum.transactions.core :as transactions]
[status-im.ethereum.transactions.etherscan :as transactions.etherscan]
[status-im.models.wallet :as models.wallet]
[status-im.ui.components.bottom-bar.styles :as tabs.styles]
[status-im.ui.components.toolbar.styles :as toolbar.styles]
[status-im.ui.screens.add-new.new-public-chat.db :as db]
@ -29,18 +27,18 @@
[status-im.utils.datetime :as datetime]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.hex :as utils.hex]
[status-im.utils.identicon :as identicon]
[status-im.utils.money :as money]
[status-im.utils.platform :as platform]
[status-im.utils.security :as security]
[status-im.utils.universal-links.core :as links]
[status-im.wallet.core :as wallet]
[status-im.wallet.db :as wallet.db]
status-im.tribute-to-talk.subs
status-im.ui.screens.hardwallet.connect.subs
status-im.ui.screens.hardwallet.settings.subs
status-im.ui.screens.hardwallet.pin.subs
status-im.ui.screens.hardwallet.setup.subs
[status-im.wallet.db :as wallet.db]))
status-im.ui.screens.hardwallet.setup.subs))
;; TOP LEVEL ===========================================================================================================
@ -1128,7 +1126,7 @@
:contact to-contact
:address to))
:time-formatted (datetime/timestamp->time timestamp)
:on-touch-fn #(re-frame/dispatch [:show-transaction-details hash]))))
:on-touch-fn #(re-frame/dispatch [:wallet.ui/show-transaction-details hash]))))
(defn- group-transactions-by-date
[transactions]
@ -1257,12 +1255,12 @@
[transaction edit]
(cond-> edit
(not (get-in edit [:gas-price :value]))
(models.wallet/build-edit
(wallet/build-edit
:gas-price
(money/to-fixed (money/wei-> :gwei (:gas-price transaction))))
(not (get-in edit [:gas :value]))
(models.wallet/build-edit
(wallet/build-edit
:gas
(money/to-fixed (:gas transaction)))))
@ -1299,7 +1297,7 @@
:<- [:balance]
(fn [[{:keys [amount symbol] :as transaction} balance]]
(-> transaction
(models.wallet/add-max-fee)
(wallet/add-max-fee)
(check-sufficient-funds balance symbol amount)
(check-sufficient-gas balance symbol amount))))

View File

@ -1,7 +1,7 @@
(ns status-im.ui.screens.currency-settings.models
(:require [status-im.accounts.update.core :as accounts.update]
[status-im.models.wallet :as wallet]
[status-im.utils.fx :as fx]))
[status-im.utils.fx :as fx]
[status-im.wallet.core :as wallet]))
(fx/defn set-currency
[{:keys [db] :as cofx} currency]

View File

@ -10,30 +10,29 @@
status-im.ui.screens.add-new.new-chat.navigation
status-im.ui.screens.profile.events
status-im.ui.screens.extensions.add.events
status-im.ui.screens.wallet.events
status-im.ui.screens.wallet.collectibles.events
status-im.ui.screens.wallet.send.events
status-im.ui.screens.wallet.request.events
status-im.ui.screens.wallet.settings.events
status-im.ui.screens.wallet.choose-recipient.events
status-im.ui.screens.wallet.collectibles.cryptokitties.events
status-im.ui.screens.wallet.collectibles.cryptostrikers.events
status-im.ui.screens.wallet.collectibles.etheremon.events
status-im.ui.screens.wallet.collectibles.superrare.events
status-im.ui.screens.wallet.collectibles.kudos.events
status-im.ui.screens.wallet.navigation
status-im.utils.keychain.events
[re-frame.core :as re-frame]
[status-im.hardwallet.core :as hardwallet]
[status-im.chat.models :as chat]
[status-im.native-module.core :as status]
[status-im.hardwallet.core :as hardwallet]
[status-im.mailserver.core :as mailserver]
[status-im.native-module.core :as status]
[status-im.ui.components.permissions :as permissions]
[status-im.utils.dimensions :as dimensions]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers]
[status-im.utils.http :as http]
[status-im.utils.utils :as utils]
[status-im.utils.fx :as fx]
[status-im.models.wallet :as wallet]))
[status-im.wallet.core :as wallet]))
(defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(re-frame/dispatch (success-event-creator %))

View File

@ -1,240 +0,0 @@
(ns status-im.ui.screens.wallet.events
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.models.wallet :as models]
[status-im.ui.screens.navigation :as navigation]
status-im.ui.screens.wallet.navigation
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.erc20 :as erc20]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]
[status-im.utils.prices :as prices]
[status-im.utils.utils :as utils.utils]
[taoensso.timbre :as log]))
(defn get-balance [{:keys [web3 account-id on-success on-error]}]
(if (and web3 account-id)
(.getBalance
(.-eth web3)
account-id
(fn [err resp]
(if-not err
(on-success resp)
(on-error err))))
(on-error "web3 or account-id not available")))
(defn get-token-balance [{:keys [web3 contract account-id on-success on-error]}]
(if (and web3 contract account-id)
(erc20/balance-of
web3
contract
(ethereum/normalized-address account-id)
(fn [err resp]
(if-not err
(on-success resp)
(on-error err))))
(on-error "web3, contract or account-id not available")))
(defn assoc-error-message [db error-type err]
(assoc-in db [:wallet :errors error-type] (or err :unknown-error)))
;; FX
(re-frame/reg-fx
:get-balance
(fn [{:keys [web3 account-id success-event error-event]}]
(get-balance {:web3 web3
:account-id account-id
:on-success #(re-frame/dispatch [success-event %])
:on-error #(re-frame/dispatch [error-event %])})))
(re-frame/reg-fx
:get-tokens-balance
(fn [{:keys [web3 symbols all-tokens chain account-id success-event error-event]}]
(doseq [symbol symbols]
(let [contract (:address (tokens/symbol->token all-tokens chain symbol))]
(get-token-balance {:web3 web3
:contract contract
:account-id account-id
:on-success #(re-frame/dispatch [success-event symbol %])
:on-error #(re-frame/dispatch [error-event symbol %])})))))
;; TODO(oskarth): At some point we want to get list of relevant assets to get prices for
(re-frame/reg-fx
:get-prices
(fn [{:keys [from to mainnet? success-event error-event chaos-mode?]}]
(prices/get-prices from
to
mainnet?
#(re-frame/dispatch [success-event %])
#(re-frame/dispatch [error-event %])
chaos-mode?)))
(re-frame/reg-fx
:update-gas-price
(fn [{:keys [web3 success-event edit?]}]
(ethereum/gas-price web3 #(re-frame/dispatch [success-event %2 edit?]))))
(re-frame/reg-fx
:update-estimated-gas
(fn [{:keys [web3 obj success-event]}]
(ethereum/estimate-gas-web3 web3 (clj->js obj) #(re-frame/dispatch [success-event %2]))))
(defn- validate-token-name! [web3 {:keys [address symbol name]}]
(erc20/name web3 address #(when (and (seq %2) ;;NOTE(goranjovic): skipping check if field not set in contract
(not= name %2))
(let [message (i18n/label :t/token-auto-validate-name-error
{:symbol symbol
:expected name
:actual %2
:address address})]
(log/warn message)
(utils.utils/show-popup (i18n/label :t/warning) message)))))
(defn- validate-token-symbol! [web3 {:keys [address symbol]}]
(erc20/symbol web3 address #(when (and (seq %2) ;;NOTE(goranjovic): skipping check if field not set in contract
(not= (clojure.core/name symbol) %2))
(let [message (i18n/label :t/token-auto-validate-symbol-error
{:symbol symbol
:expected (clojure.core/name symbol)
:actual %2
:address address})]
(log/warn message)
(utils.utils/show-popup (i18n/label :t/warning) message)))))
(defn- validate-token-decimals! [web3 {:keys [address symbol decimals nft? skip-decimals-check?]}]
;;NOTE(goranjovic): only skipping check if skip-decimals-check? flag is present because we can't differentiate
;;between unset decimals and 0 decimals.
(when-not skip-decimals-check?
(erc20/decimals web3 address #(when (and %2
(not nft?)
(not= decimals (int %2)))
(let [message (i18n/label :t/token-auto-validate-decimals-error
{:symbol symbol
:expected decimals
:actual %2
:address address})]
(log/warn message)
(utils.utils/show-popup (i18n/label :t/warning) message))))))
(re-frame/reg-fx
:wallet/validate-tokens
(fn [{:keys [web3 tokens]}]
(doseq [token tokens]
(validate-token-decimals! web3 token)
(validate-token-symbol! web3 token)
(validate-token-name! web3 token))))
;; Handlers
(handlers/register-handler-fx
:update-wallet
(fn [cofx _]
(models/update-wallet cofx)))
(handlers/register-handler-fx
:update-wallet-and-nav-back
(fn [cofx [_ on-close]]
(fx/merge cofx
(when on-close
{:dispatch on-close})
(navigation/navigate-back)
(models/update-wallet))))
(handlers/register-handler-fx
:update-balance-success
(fn [{:keys [db]} [_ balance]]
{:db (-> db
(assoc-in [:wallet :balance :ETH] balance)
(assoc-in [:wallet :balance-loading?] false))}))
(handlers/register-handler-fx
:update-balance-fail
(fn [{:keys [db]} [_ err]]
(log/debug "Unable to get balance: " err)
{:db (-> db
(assoc-error-message :balance-update :error-unable-to-get-balance)
(assoc-in [:wallet :balance-loading?] false))}))
(fx/defn update-token-balance-success [{:keys [db]} symbol balance]
{:db (-> db
(assoc-in [:wallet :balance symbol] balance)
(assoc-in [:wallet :balance-loading?] false))})
(handlers/register-handler-fx
:update-token-balance-success
(fn [cofx [_ symbol balance]]
(update-token-balance-success cofx symbol balance)))
(handlers/register-handler-fx
:update-token-balance-fail
(fn [{:keys [db]} [_ symbol err]]
(log/debug "Unable to get token " symbol "balance: " err)
{:db (-> db
(assoc-error-message :balance-update :error-unable-to-get-token-balance)
(assoc-in [:wallet :balance-loading?] false))}))
(handlers/register-handler-fx
:update-prices-success
(fn [{:keys [db]} [_ prices]]
{:db (assoc db
:prices prices
:prices-loading? false)}))
(handlers/register-handler-fx
:update-prices-fail
(fn [{:keys [db]} [_ err]]
(log/debug "Unable to get prices: " err)
{:db (-> db
(assoc-error-message :prices-update :error-unable-to-get-prices)
(assoc :prices-loading? false))}))
(handlers/register-handler-fx
:show-transaction-details
(fn [{:keys [db]} [_ hash]]
{:db (assoc-in db [:wallet :current-transaction] hash)
:dispatch [:navigate-to :wallet-transaction-details]}))
(handlers/register-handler-fx
:wallet/show-sign-transaction
(fn [{:keys [db]} [_ {:keys [id method]} from-chat?]]
{:db (assoc-in db [:wallet :send-transaction] {:id id
:method method
:from-chat? from-chat?})
:dispatch [:navigate-to-clean :wallet-send-transaction-modal]}))
(handlers/register-handler-fx
:wallet/update-gas-price-success
(fn [{:keys [db] :as cofx} [_ price edit?]]
(if edit?
(models/edit-value
:gas-price
(money/to-fixed
(money/wei-> :gwei price))
cofx)
{:db (assoc-in db [:wallet :send-transaction :gas-price] price)})))
(handlers/register-handler-fx
:wallet/update-estimated-gas
(fn [{:keys [db]} [_ obj]]
{:update-estimated-gas {:web3 (:web3 db)
:obj obj
:success-event :wallet/update-estimated-gas-success}}))
(handlers/register-handler-fx
:wallet/update-estimated-gas-success
(fn [{:keys [db]} [_ gas]]
(when gas
(let [adjusted-gas (money/bignumber (int (* gas 1.2)))
db-with-adjusted-gas (assoc-in db [:wallet :send-transaction :gas] adjusted-gas)]
{:db (if (some? (-> db :wallet :send-transaction :original-gas))
db-with-adjusted-gas
(assoc-in db-with-adjusted-gas [:wallet :send-transaction :original-gas] adjusted-gas))}))))
(handlers/register-handler-fx
:wallet.setup-ui/navigate-back-pressed
(fn [{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc-in db [:wallet :send-transaction] {})}
(navigation/navigate-back))))

View File

@ -167,11 +167,7 @@
[react/scroll-view {:end-fill-color colors/white
:refresh-control
(reagent/as-element
[react/refresh-control {:on-refresh (fn [_]
;;TODO temporay fix to update balance, should be fixed
;;properly later
(re-frame/dispatch [:wallet.ui/pull-to-refresh])
(re-frame/dispatch [:update-wallet]))
[react/refresh-control {:on-refresh #(re-frame/dispatch [:wallet.ui/pull-to-refresh])
:tint-color :white
:refreshing false}])}
(if error-message

View File

@ -2,29 +2,7 @@
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.core :as ethereum]
[status-im.constants :as constants]
[status-im.utils.utils :as utils]))
(defmethod navigation/preload-data! :wallet
[db _]
;;TODO(goranjovic) - get rid of this preload hook completely
;;TODO(andrey) - temporary "improvement" with timeout, to fix screen rendering, wallet update should be optimized
(utils/set-timeout (fn []
(re-frame/dispatch [:wallet.ui/pull-to-refresh])
(re-frame/dispatch [:update-wallet]))
500)
db)
(defmethod navigation/preload-data! :wallet-stack
[db _]
;;TODO(goranjovic) - get rid of this preload hook completely
;;TODO(andrey) - temporary "improvement" with timeout, to fix screen rendering, wallet update should be optimized
(utils/set-timeout (fn []
(re-frame/dispatch [:wallet.ui/pull-to-refresh])
(re-frame/dispatch [:update-wallet]))
500)
db)
[status-im.utils.ethereum.core :as ethereum]))
(def transaction-send-default
(let [symbol :ETH]

View File

@ -55,7 +55,7 @@
^{:key "toolbar"}
[wallet.components/toolbar
{:transparent? true}
(actions/back-white #(re-frame/dispatch [:wallet.setup-ui/navigate-back-pressed]))
(actions/back-white #(re-frame/dispatch [:wallet.setup.ui/navigate-back-pressed]))
(i18n/label :t/wallet-set-up-title)])
(defn main-panel [signing-phrase on-confirm]

View File

@ -3,11 +3,9 @@
[status-im.chat.commands.sending :as commands-sending]
[status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.models.wallet :as models.wallet]
[status-im.native-module.core :as status]
[status-im.transport.utils :as transport.utils]
[status-im.ui.screens.navigation :as navigation]
[status-im.wallet.db :as wallet.db]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.erc20 :as erc20]
[status-im.utils.ethereum.tokens :as tokens]
@ -16,7 +14,9 @@
[status-im.utils.money :as money]
[status-im.utils.security :as security]
[status-im.utils.types :as types]
[status-im.utils.utils :as utils]))
[status-im.utils.utils :as utils]
[status-im.wallet.core :as wallet]
[status-im.wallet.db :as wallet.db]))
;;;; FX
@ -66,7 +66,7 @@
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] false)
(assoc-in [:wallet :send-transaction :in-progress?] true))
::send-transaction [(models.wallet/prepare-send-transaction from transaction)
::send-transaction [(wallet/prepare-send-transaction from transaction)
all-tokens
symbol
chain
@ -116,7 +116,7 @@
modal-screen-was-used? (get-in db [:navigation/screen-params :wallet-send-modal-stack :modal?])]
(if error
;; ERROR
(models.wallet/handle-transaction-error (assoc cofx :db db') error)
(wallet/handle-transaction-error (assoc cofx :db db') error)
;; RESULT
(fx/merge cofx
(merge
@ -124,7 +124,7 @@
(not (constants/web3-sign-message? method))
(assoc-in [:wallet :transactions result]
(models.wallet/prepare-unconfirmed-transaction db now result)))}
(wallet/prepare-unconfirmed-transaction db now result)))}
(when on-result
{:dispatch (conj on-result id result method)}))
#(when (or (not on-result)
@ -168,7 +168,7 @@
db' (assoc-in db [:wallet :send-transaction :in-progress?] false)]
(if error
;; ERROR
(models.wallet/handle-transaction-error (assoc cofx :db db') error)
(wallet/handle-transaction-error (assoc cofx :db db') error)
;; RESULT
(fx/merge cofx
{:db (-> db
@ -186,19 +186,19 @@
(handlers/register-handler-fx
:wallet/discard-transaction
(fn [cofx _]
(models.wallet/discard-transaction cofx)))
(wallet/discard-transaction cofx)))
(handlers/register-handler-fx
:wallet.dapp/transaction-on-result
(fn [{db :db} [_ message-id id result method]]
(let [webview (:webview-bridge db)
keycard? (boolean (get-in db [:account/account :keycard-instance-uid]))]
(models.wallet/dapp-complete-transaction (int id) result method message-id webview keycard?))))
(wallet/dapp-complete-transaction (int id) result method message-id webview keycard?))))
(handlers/register-handler-fx
:wallet.dapp/transaction-on-error
(fn [{db :db} [_ message-id message]]
(models.wallet/web3-error-callback {} db message-id message)))
(wallet/web3-error-callback {} db message-id message)))
;; DAPP TRANSACTIONS QUEUE
;; NOTE(andrey) We need this queue because dapp can send several transactions in a row, this is bad behaviour
@ -219,13 +219,13 @@
;;SEND TRANSACTION
(= method constants/web3-send-transaction)
(let [transaction (models.wallet/prepare-dapp-transaction queued-transaction (:contacts/contacts db))]
(models.wallet/open-modal-wallet-for-transaction db' transaction (first params)))
(let [transaction (wallet/prepare-dapp-transaction queued-transaction (:contacts/contacts db))]
(wallet/open-modal-wallet-for-transaction db' transaction (first params)))
;;SIGN MESSAGE
(constants/web3-sign-message? method)
(let [typed? (not= constants/web3-personal-sign method)
[address data] (models.wallet/normalize-sign-message-params params)]
[address data] (wallet/normalize-sign-message-params params)]
(if (and address data)
(let [signing-phrase (-> (get-in db [:account/account :signing-phrase])
(clojure.string/replace-all #" " " "))
@ -259,7 +259,7 @@
(fn [cofx _]
(fx/merge cofx
(navigation/navigate-back)
(models.wallet/discard-transaction))))
(wallet/discard-transaction))))
(defn update-gas-price
([db edit? success-event]
@ -311,7 +311,7 @@
(handlers/register-handler-fx
:wallet.send/edit-value
(fn [cofx [_ key value]]
(models.wallet/edit-value key value cofx)))
(wallet/edit-value key value cofx)))
(handlers/register-handler-fx
:wallet.send/set-gas-details
@ -333,7 +333,7 @@
(money/to-fixed
(ethereum/estimate-gas
(-> db :wallet :send-transaction :symbol))))]
(assoc (models.wallet/edit-value
(assoc (wallet/edit-value
:gas
gas-default
cofx)
@ -364,9 +364,9 @@
(defn- prepare-keycard-transaction
[transaction from symbol chain all-tokens]
(if (= :ETH symbol)
(models.wallet/prepare-send-transaction from transaction)
(wallet/prepare-send-transaction from transaction)
(let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))
{:keys [gas gasPrice to from value]} (models.wallet/prepare-send-transaction from transaction)]
{:keys [gas gasPrice to from value]} (wallet/prepare-send-transaction from transaction)]
(merge (ethereum/call-params contract "transfer(address,uint256)" to value)
{:from from
:gas gas

View File

@ -1,19 +0,0 @@
(ns status-im.ui.screens.wallet.settings.events
(:require [status-im.ui.screens.wallet.settings.models :as models]
[status-im.utils.handlers :as handlers]))
(handlers/register-handler-fx
:wallet.settings/toggle-visible-token
(fn [cofx [_ symbol checked?]]
(models/toggle-visible-token cofx symbol checked?)))
(handlers/register-handler-fx
:configure-token-balance-and-visibility
(fn [cofx [_ symbol balance]]
(models/configure-token-balance-and-visibility cofx symbol balance)))
(handlers/register-handler-fx
:wallet.ui/pull-to-refresh
(fn [cofx _]
(models/wallet-autoconfig-tokens cofx)))

View File

@ -1,58 +0,0 @@
(ns status-im.ui.screens.wallet.settings.models
(:require [re-frame.core :as re-frame]
[status-im.accounts.update.core :as accounts.update]
[status-im.ui.screens.wallet.events :as wallet.events]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.fx :as fx]))
(defn- set-checked [ids id checked?]
(if checked?
(conj (or ids #{}) id)
(disj ids id)))
(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)]
(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 remove-custom-token [{{:account/keys [account]} :db :as cofx} {:keys [symbol address]}]
(let [network (get (:networks account) (:network account))
chain (ethereum/network->chain-keyword network)
settings (update-toggle-in-settings cofx symbol false)
new-settings (update-in settings [:wallet :custom-tokens chain] dissoc address)]
(accounts.update/update-settings cofx new-settings {})))
(fx/defn configure-token-balance-and-visibility [cofx symbol balance]
(fx/merge cofx
(toggle-visible-token symbol true)
;;TODO(goranjovic): move `update-token-balance-success` function to wallet models
(wallet.events/update-token-balance-success symbol balance)))
(fx/defn wallet-autoconfig-tokens [{:keys [db]}]
(let [{:keys [account/account web3 network-status] :wallet/keys [all-tokens]} db
network (get (:networks account) (:network account))
chain (ethereum/network->chain-keyword network)
contracts (->> (tokens/tokens-for all-tokens chain)
(remove :hidden?))]
(when-not (= network-status :offline)
(doseq [{:keys [address symbol]} contracts]
;;TODO(goranjovic): move `get-token-balance` function to wallet models
(wallet.events/get-token-balance {:web3 web3
:contract address
:account-id (:address account)
:on-error #(re-frame/dispatch [:update-token-balance-fail symbol %])
:on-success #(when (> % 0)
(re-frame/dispatch [:configure-token-balance-and-visibility symbol %]))})))))

View File

@ -19,7 +19,8 @@
(defn toolbar []
[toolbar/toolbar nil
[toolbar/nav-button
(toolbar.actions/back #(re-frame/dispatch [:update-wallet-and-nav-back]))]
(toolbar.actions/back #(re-frame/dispatch
[:wallet.settings.ui/navigate-back-pressed]))]
[toolbar/content-title
(i18n/label :t/wallet-assets)]])
@ -96,7 +97,7 @@
{:style {:border-bottom-color colors/white-light-transparent}}
[toolbar/nav-button
(actions/back-white
#(re-frame/dispatch [:update-wallet-and-nav-back
#(re-frame/dispatch [:wallet.settings.ui/navigate-back-pressed
(when (fn? on-close)
(on-close (create-payload address)))]))]
[toolbar/content-title {:color colors/white}

View File

@ -1,10 +1,10 @@
(ns status-im.utils.ethereum.contracts
(:require [re-frame.core :as re-frame]
[status-im.models.wallet :as wallet]
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.fx :as fx]
[status-im.utils.money :as money]))
[status-im.utils.money :as money]
[status-im.wallet.core :as wallet]))
(def contracts
{:status/snt

View File

@ -0,0 +1,557 @@
(ns status-im.wallet.core
(:require [clojure.set :as set]
[re-frame.core :as re-frame]
[status-im.accounts.update.core :as accounts.update]
[status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.config :as config]
[status-im.utils.core :as utils.core]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.erc20 :as erc20]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.fx :as fx]
[status-im.utils.hex :as utils.hex]
[status-im.utils.money :as money]
[status-im.utils.prices :as prices]
[status-im.utils.utils :as utils.utils]
[taoensso.timbre :as log]))
(defn get-balance [{:keys [web3 account-id on-success on-error]}]
(if (and web3 account-id)
(.getBalance
(.-eth web3)
account-id
(fn [err resp]
(if-not err
(on-success resp)
(on-error err))))
(on-error "web3 or account-id not available")))
(defn get-token-balance
[{:keys [web3 contract account-id on-success on-error]}]
(if (and web3 contract account-id)
(erc20/balance-of
web3
contract
(ethereum/normalized-address account-id)
(fn [err resp]
(if-not err
(on-success resp)
(on-error err))))
(on-error "web3, contract or account-id not available")))
(defn assoc-error-message [db error-type err]
(assoc-in db [:wallet :errors error-type] (or err :unknown-error)))
(fx/defn on-update-prices-fail
[{:keys [db]} err]
(log/debug "Unable to get prices: " err)
{:db (-> db
(assoc-error-message :prices-update :error-unable-to-get-prices)
(assoc :prices-loading? false))})
(fx/defn on-update-balance-fail
[{:keys [db]} err]
(log/debug "Unable to get balance: " err)
{:db (-> db
(assoc-error-message :balance-update :error-unable-to-get-balance)
(assoc-in [:wallet :balance-loading?] false))})
(fx/defn on-update-token-balance-fail
[{:keys [db]} symbol err]
(log/debug "Unable to get token " symbol "balance: " err)
{:db (-> db
(assoc-error-message :balance-update :error-unable-to-get-token-balance)
(assoc-in [:wallet :balance-loading?] false))})
(fx/defn open-transaction-details
[{:keys [db] :as cofx} hash]
(fx/merge cofx
{:db (assoc-in db [:wallet :current-transaction] hash)}
(navigation/navigate-to-cofx :wallet-transaction-details nil)))
(fx/defn open-send-transaction-modal
[{:keys [db] :as cofx} id method from-chat?]
(fx/merge cofx
{:db (assoc-in db [:wallet :send-transaction] {:id id
:method method
:from-chat? from-chat?})}
(navigation/navigate-to-clean :wallet-send-transaction-modal nil)))
;; FX
(re-frame/reg-fx
:get-balance
(fn [{:keys [web3 account-id success-event error-event]}]
(get-balance {:web3 web3
:account-id account-id
:on-success #(re-frame/dispatch [success-event %])
:on-error #(re-frame/dispatch [error-event %])})))
(re-frame/reg-fx
:get-tokens-balance
(fn [{:keys [web3 symbols all-tokens chain account-id success-event error-event]}]
(doseq [symbol symbols]
(let [contract (:address (tokens/symbol->token all-tokens chain symbol))]
(get-token-balance {:web3 web3
:contract contract
:account-id account-id
:on-success #(re-frame/dispatch [success-event symbol %])
:on-error #(re-frame/dispatch [error-event symbol %])})))))
;; TODO(oskarth): At some point we want to get list of relevant assets to get prices for
(re-frame/reg-fx
:get-prices
(fn [{:keys [from to mainnet? success-event error-event chaos-mode?]}]
(prices/get-prices from
to
mainnet?
#(re-frame/dispatch [success-event %])
#(re-frame/dispatch [error-event %])
chaos-mode?)))
(re-frame/reg-fx
:update-gas-price
(fn [{:keys [web3 success-event edit?]}]
(ethereum/gas-price web3 #(re-frame/dispatch [success-event %2 edit?]))))
(re-frame/reg-fx
:update-estimated-gas
(fn [{:keys [web3 obj success-event]}]
(ethereum/estimate-gas-web3 web3 (clj->js obj) #(re-frame/dispatch [success-event %2]))))
(defn- validate-token-name! [web3 {:keys [address symbol name]}]
(erc20/name web3 address #(when (and (seq %2) ;;NOTE(goranjovic): skipping check if field not set in contract
(not= name %2))
(let [message (i18n/label :t/token-auto-validate-name-error
{:symbol symbol
:expected name
:actual %2
:address address})]
(log/warn message)
(utils.utils/show-popup (i18n/label :t/warning) message)))))
(defn- validate-token-symbol! [web3 {:keys [address symbol]}]
(erc20/symbol web3 address #(when (and (seq %2) ;;NOTE(goranjovic): skipping check if field not set in contract
(not= (clojure.core/name symbol) %2))
(let [message (i18n/label :t/token-auto-validate-symbol-error
{:symbol symbol
:expected (clojure.core/name symbol)
:actual %2
:address address})]
(log/warn message)
(utils.utils/show-popup (i18n/label :t/warning) message)))))
(defn- validate-token-decimals! [web3 {:keys [address symbol decimals nft? skip-decimals-check?]}]
;;NOTE(goranjovic): only skipping check if skip-decimals-check? flag is present because we can't differentiate
;;between unset decimals and 0 decimals.
(when-not skip-decimals-check?
(erc20/decimals web3 address #(when (and %2
(not nft?)
(not= decimals (int %2)))
(let [message (i18n/label :t/token-auto-validate-decimals-error
{:symbol symbol
:expected decimals
:actual %2
:address address})]
(log/warn message)
(utils.utils/show-popup (i18n/label :t/warning) message))))))
(re-frame/reg-fx
:wallet/validate-tokens
(fn [{:keys [web3 tokens]}]
(doseq [token tokens]
(validate-token-decimals! web3 token)
(validate-token-symbol! web3 token)
(validate-token-name! web3 token))))
(re-frame/reg-fx
:wallet/check-all-known-tokens-balance
(fn [{:keys [web3 contracts account]}]
(doseq [{:keys [address symbol]} contracts]
;;TODO(goranjovic): move `get-token-balance` function to wallet models
(get-token-balance {:web3 web3
:contract address
:account-id (:address account)
:on-error #(re-frame/dispatch [:update-token-balance-fail symbol %])
:on-success #(when (> % 0)
(re-frame/dispatch [:wallet/token-found symbol %]))}))))
(fx/defn wallet-autoconfig-tokens
[{:keys [db] :as cofx}]
(let [{:keys [account/account web3 network-status] :wallet/keys [all-tokens]} db
network (get (:networks account) (:network account))
chain (ethereum/network->chain-keyword network)
contracts (->> (tokens/tokens-for all-tokens chain)
(remove :hidden?))
settings (:settings account)
assets (get-in settings [:wallet :visible-tokens chain])]
(when-not (or (= network-status :offline)
assets)
(let [new-settings (assoc-in settings
[:wallet :visible-tokens chain]
#{})]
(fx/merge cofx
{:wallet/check-all-known-tokens-balance {:web3 web3
:contracts contracts
:account account}}
(accounts.update/update-settings new-settings {}))))))
(def min-gas-price-wei (money/bignumber 1))
(defmulti invalid-send-parameter? (fn [type _] type))
(defmethod invalid-send-parameter? :gas-price [_ value]
(cond
(not value) :invalid-number
(.lt (money/->wei :gwei value) min-gas-price-wei) :not-enough-wei
(-> (money/->wei :gwei value) .decimalPlaces pos?) :invalid-number))
(defmethod invalid-send-parameter? :default [_ value]
(when (or (not value)
(<= value 0))
:invalid-number))
(defn- calculate-max-fee
[gas gas-price]
(if (and gas gas-price)
(money/to-fixed (money/wei->ether (.times gas gas-price)))
"0"))
(defn- edit-max-fee [edit]
(let [gas (get-in edit [:gas-price :value-number])
gas-price (get-in edit [:gas :value-number])]
(assoc edit :max-fee (calculate-max-fee gas gas-price))))
(defn add-max-fee [{:keys [gas gas-price] :as transaction}]
(assoc transaction :max-fee (calculate-max-fee gas gas-price)))
(defn build-edit [edit-value key value]
"Takes the previous edit, either :gas or :gas-price and a value as string.
Wei for gas, and gwei for gas price.
Validates them and sets max fee"
(let [bn-value (money/bignumber value)
invalid? (invalid-send-parameter? key bn-value)
data (if invalid?
{:value value
:max-fee 0
:invalid? invalid?}
{:value value
:value-number (if (= :gas-price key)
(money/->wei :gwei bn-value)
bn-value)
:invalid? false})]
(-> edit-value
(assoc key data)
edit-max-fee)))
(defn edit-value
[key value {:keys [db]}]
{:db (update-in db [:wallet :edit] build-edit key value)})
;; DAPP TRANSACTION -> SEND TRANSACTION
(defn prepare-dapp-transaction [{{:keys [id method params]} :payload message-id :message-id} contacts]
(let [{:keys [to value data gas gasPrice nonce]} (first params)
contact (get contacts (utils.hex/normalize-hex to))]
(cond-> {:id (str id)
:to-name (or (when (nil? to)
(i18n/label :t/new-contract))
contact)
:symbol :ETH
:method method
:to to
:amount (money/bignumber (or value 0))
:gas (cond
gas
(money/bignumber gas)
(and value (empty? data))
(money/bignumber 21000))
:gas-price (when gasPrice
(money/bignumber gasPrice))
:data data
:on-result [:wallet.dapp/transaction-on-result message-id]
:on-error [:wallet.dapp/transaction-on-error message-id]}
nonce
(assoc :nonce nonce))))
;; SEND TRANSACTION -> RPC TRANSACTION
(defn prepare-send-transaction [from {:keys [amount to gas gas-price data nonce]}]
(cond-> {:from (ethereum/normalized-address from)
:to (ethereum/normalized-address to)
:value (ethereum/int->hex amount)
:gas (ethereum/int->hex gas)
:gasPrice (ethereum/int->hex gas-price)}
data
(assoc :data data)
nonce
(assoc :nonce nonce)))
;; NOTE (andrey) we need this function, because params may be mixed up, so we need to figure out which one is address
;; and which message
(defn normalize-sign-message-params [params]
(let [first_param (first params)
second_param (second params)
first-param-address? (ethereum/address? first_param)
second-param-address? (ethereum/address? second_param)]
(when (or first-param-address? second-param-address?)
(if first-param-address?
[first_param second_param]
[second_param first_param]))))
(defn web3-error-callback [fx {:keys [webview-bridge]} message-id message]
(assoc fx :browser/send-to-bridge {:message {:type constants/web3-send-async-callback
:messageId message-id
:error message}
:webview webview-bridge}))
(defn dapp-complete-transaction [id result method message-id webview keycard?]
(cond-> {:browser/send-to-bridge {:message {:type constants/web3-send-async-callback
:messageId message-id
:result {:jsonrpc "2.0"
:id (int id)
:result result}}
:webview webview}
:dispatch [:navigate-back]}
(constants/web3-sign-message? method)
(assoc :dispatch (if keycard? [:navigate-to :browser] [:navigate-back]))
(= method constants/web3-send-transaction)
(assoc :dispatch [:navigate-to-clean :wallet-transaction-sent-modal])))
(fx/defn discard-transaction
[{:keys [db]}]
(let [{:keys [on-error]} (get-in db [:wallet :send-transaction])]
(merge {:db (update db :wallet
assoc
:send-transaction {}
:transactions-queue nil)}
(when on-error
{:dispatch (conj on-error "transaction was cancelled by user")}))))
(defn prepare-unconfirmed-transaction [db now hash]
(let [transaction (get-in db [:wallet :send-transaction])
all-tokens (:wallet/all-tokens db)]
(let [chain (:chain db)
token (tokens/symbol->token all-tokens (keyword chain) (:symbol transaction))]
(-> transaction
(assoc :confirmations "0"
:timestamp (str now)
:type :outbound
:hash hash
:value (:amount transaction)
:token token
:gas-limit (str (:gas transaction)))
(update :gas-price str)
(dissoc :message-id :id :gas)))))
(fx/defn handle-transaction-error
[{:keys [db] :as cofx} {:keys [code message]}]
(let [{:keys [on-error]} (get-in db [:wallet :send-transaction])]
(case code
;;WRONG PASSWORD
constants/send-transaction-err-decrypt
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] true))}
(fx/merge cofx
(merge {:db (-> db
(assoc-in [:wallet :transactions-queue] nil)
(assoc-in [:wallet :send-transaction] {}))
:wallet/show-transaction-error message}
(when on-error
{:dispatch (conj on-error message)}))
navigation/navigate-back))))
(defn clear-error-message [db error-type]
(update-in db [:wallet :errors] dissoc error-type))
(defn tokens-symbols [visible-token-symbols all-tokens chain]
(set/difference (set visible-token-symbols) (set (map :symbol (tokens/nfts-for all-tokens chain)))))
(fx/defn initialize-tokens
[{:keys [db] :as cofx}]
(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)
all-tokens (merge-with
merge
(utils.core/map-values #(utils.core/index-by :address %)
tokens/all-default-tokens)
custom-tokens)]
(fx/merge cofx
(merge
{:db (assoc db :wallet/all-tokens all-tokens)}
(when config/erc20-contract-warnings-enabled?
{:wallet/validate-tokens {:web3 (:web3 db)
:tokens (get tokens/all-default-tokens chain)}}))
wallet-autoconfig-tokens)))
(fx/defn update-wallet
[{{:keys [web3 network network-status]
{:keys [address settings]} :account/account :as db} :db}]
(let [all-tokens (:wallet/all-tokens db)
network (get-in db [:account/account :networks network])
chain (ethereum/network->chain-keyword network)
mainnet? (= :mainnet chain)
assets (get-in settings [:wallet :visible-tokens chain])
tokens (tokens-symbols (get-in settings [:wallet :visible-tokens chain]) all-tokens chain)
currency-id (or (get-in settings [:wallet :currency]) :usd)
currency (get constants/currencies currency-id)]
(when (not= network-status :offline)
{:get-balance {:web3 web3
:account-id address
:success-event :wallet.callback/update-balance-success
:error-event :wallet.callback/update-balance-fail}
:get-tokens-balance {:web3 web3
:account-id address
:symbols assets
:chain chain
:all-tokens all-tokens
:success-event :wallet.callback/update-token-balance-success
:error-event :wallet.callback/update-token-balance-fail}
:get-prices {:from (if mainnet?
(conj tokens "ETH")
[(-> (tokens/native-currency chain)
(wallet.utils/exchange-symbol))])
:to [(:code currency)]
:mainnet? mainnet?
:success-event :wallet.callback/update-prices-success
:error-event :wallet.callback/update-prices-fail
:chaos-mode? (:chaos-mode? settings)}
:db (-> db
(clear-error-message :prices-update)
(clear-error-message :balance-update)
(assoc-in [:wallet :balance-loading?] true)
(assoc :prices-loading? true))})))
(defn open-modal-wallet-for-transaction [db transaction tx-object]
(let [{:keys [gas gas-price]} transaction
{:keys [wallet-set-up-passed?]} (:account/account db)]
{:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction] transaction)
(assoc-in [:wallet :send-transaction :original-gas] gas))
:dispatch-n [[:TODO.remove/update-wallet]
(when-not gas
[:TODO.remove/update-estimated-gas tx-object])
(when-not gas-price
[:wallet/update-gas-price])
[:navigate-to
(if wallet-set-up-passed?
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]]}))
2
(fx/defn open-sign-transaction-flow
[{:keys [db] :as cofx} {:keys [gas gas-price] :as transaction}]
(let [go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?])
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]
(fx/merge cofx
(cond-> {:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction]
transaction)
(assoc-in [:wallet :send-transaction :original-gas]
gas))}
(not gas)
(assoc :update-estimated-gas
{:web3 (:web3 db)
:obj (select-keys transaction [:to :data])
:success-event :wallet/update-estimated-gas-success})
(not gas-price)
(assoc :update-gas-price
{:web3 (:web3 db)
:success-event :wallet/update-gas-price-success
:edit? false}))
(update-wallet)
(navigation/navigate-to-cofx go-to-view-id {}))))
(defn send-transaction-screen-did-load
[{:keys [db]}]
{:db (assoc-in db
[:navigation/screen-params :wallet-send-modal-stack :modal?]
false)})
(defn- set-checked [ids id checked?]
(if checked?
(conj (or ids #{}) id)
(disj ids id)))
(fx/defn update-prices
[{:keys [db]} prices]
{:db (assoc db
:prices prices
:prices-loading? false)})
(fx/defn update-balance
[{:keys [db]} balance]
{:db (-> db
(assoc-in [:wallet :balance :ETH] balance)
(assoc-in [:wallet :balance-loading?] false))})
(fx/defn update-token-balance
[{:keys [db]} symbol balance]
{:db (-> db
(assoc-in [:wallet :balance symbol] balance)
(assoc-in [:wallet :balance-loading?] false))})
(fx/defn update-gas-price
[{:keys [db] :as cofx} price edit?]
(if edit?
(edit-value
:gas-price
(money/to-fixed
(money/wei-> :gwei price))
cofx)
{:db (assoc-in db [:wallet :send-transaction :gas-price] price)}))
(fx/defn update-estimated-gas-price
[{:keys [db]} gas]
(when gas
(let [adjusted-gas (money/bignumber (int (* gas 1.2)))
db-with-adjusted-gas (assoc-in db
[:wallet :send-transaction :gas]
adjusted-gas)]
{:db (if (some? (-> db :wallet :send-transaction :original-gas))
db-with-adjusted-gas
(assoc-in db-with-adjusted-gas
[:wallet :send-transaction :original-gas]
adjusted-gas))})))
(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)]
(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 remove-custom-token [{{:account/keys [account]} :db :as cofx} {:keys [symbol address]}]
(let [network (get (:networks account) (:network account))
chain (ethereum/network->chain-keyword network)
settings (update-toggle-in-settings cofx symbol false)
new-settings (update-in settings [:wallet :custom-tokens chain] dissoc address)]
(accounts.update/update-settings cofx new-settings {})))
(fx/defn configure-token-balance-and-visibility [cofx symbol balance]
(fx/merge cofx
(toggle-visible-token symbol true)
;;TODO(goranjovic): move `update-token-balance-success` function to wallet models
(update-token-balance symbol balance)))

View File

@ -8,7 +8,7 @@
[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]))
[status-im.wallet.core :as wallet]))
(re-frame/reg-fx
:wallet.custom-token/get-decimals
@ -111,12 +111,12 @@
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))))
(wallet/add-custom-token new-token))))
(fx/defn remove-custom-token [{:keys [db] :as cofx} {:keys [address] :as token}]
(let [chain-key (ethereum/get-chain-keyword db)]
(fx/merge (update-in cofx [:db :wallet/all-tokens chain-key] dissoc address)
(models/remove-custom-token token))))
(wallet/remove-custom-token token))))
(fx/defn field-is-edited [{:keys [db] :as cofx} field-key value]
(case field-key

View File

@ -1,31 +1,31 @@
(ns status-im.test.models.wallet
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.money :as money]
[status-im.models.wallet :as model]))
[status-im.wallet.core :as wallet]))
(deftest valid-min-gas-price-test
(testing "not an number"
(is (= :invalid-number (model/invalid-send-parameter? :gas-price nil))))
(is (= :invalid-number (wallet/invalid-send-parameter? :gas-price nil))))
(testing "a number less than the minimum"
(is (= :not-enough-wei (model/invalid-send-parameter? :gas-price (money/bignumber "0.0000000001")))))
(is (= :not-enough-wei (wallet/invalid-send-parameter? :gas-price (money/bignumber "0.0000000001")))))
(testing "a number greater than the mininum"
(is (not (model/invalid-send-parameter? :gas-price 3))))
(is (not (wallet/invalid-send-parameter? :gas-price 3))))
(testing "the minimum"
(is (not (model/invalid-send-parameter? :gas-price (money/bignumber "0.000000001"))))))
(is (not (wallet/invalid-send-parameter? :gas-price (money/bignumber "0.000000001"))))))
(deftest valid-gas
(testing "not an number"
(is (= :invalid-number (model/invalid-send-parameter? :gas nil))))
(is (= :invalid-number (wallet/invalid-send-parameter? :gas nil))))
(testing "0"
(is (= :invalid-number (model/invalid-send-parameter? :gas 0))))
(is (= :invalid-number (wallet/invalid-send-parameter? :gas 0))))
(testing "a number"
(is (not (model/invalid-send-parameter? :gas 1)))))
(is (not (wallet/invalid-send-parameter? :gas 1)))))
(deftest build-edit-test
(testing "an invalid edit"
(let [actual (-> {}
(model/build-edit :gas "invalid")
(model/build-edit :gas-price "0.00000000001"))]
(wallet/build-edit :gas "invalid")
(wallet/build-edit :gas-price "0.00000000001"))]
(testing "it marks gas-price as invalid"
(is (get-in actual [:gas-price :invalid?])))
(testing "it does not change value"
@ -38,13 +38,13 @@
(is (= "0" (:max-fee actual))))))
(testing "gas price in wei should be round"
(let [actual (-> {}
(model/build-edit :gas "21000")
(model/build-edit :gas-price "0.0000000023"))]
(wallet/build-edit :gas "21000")
(wallet/build-edit :gas-price "0.0000000023"))]
(is (get-in actual [:gas-price :invalid?]))))
(testing "an valid edit"
(let [actual (-> {}
(model/build-edit :gas "21000")
(model/build-edit :gas-price "10"))]
(wallet/build-edit :gas "21000")
(wallet/build-edit :gas-price "10"))]
(testing "it does not mark gas-price as invalid"
(is (not (get-in actual [:gas-price :invalid?]))))
(testing "it sets the value in number for gas-price, in gwei"