diff --git a/STATUS_GO_SHA256 b/STATUS_GO_SHA256 index 8f0d9840a85..9484c62978c 100644 --- a/STATUS_GO_SHA256 +++ b/STATUS_GO_SHA256 @@ -1,3 +1,3 @@ ## DO NOT EDIT THIS FILE BY HAND. USE `scripts/update-status-go.sh ` instead -0cj202bj2rwfrw327gibj8hj8i94ciyp3hkq2hck9l6711qlhpnb +0049i6znvl45hc651bqyzwgmzlv0fp40maggfjsrv13q5avd0g6d diff --git a/STATUS_GO_VERSION b/STATUS_GO_VERSION index 0f578940681..6162c3d599e 100644 --- a/STATUS_GO_VERSION +++ b/STATUS_GO_VERSION @@ -1,3 +1,3 @@ ## DO NOT EDIT THIS FILE BY HAND. USE `scripts/update-status-go.sh ` instead -v0.25.0-beta.0 +v0.25.0-beta.1 diff --git a/src/status_im/accounts/core.cljs b/src/status_im/accounts/core.cljs index d21ea66aeb4..c38bf7583d1 100644 --- a/src/status_im/accounts/core.cljs +++ b/src/status_im/accounts/core.cljs @@ -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 diff --git a/src/status_im/accounts/login/core.cljs b/src/status_im/accounts/login/core.cljs index 5e4252efd4d..5a3d1d6f326 100644 --- a/src/status_im/accounts/login/core.cljs +++ b/src/status_im/accounts/login/core.cljs @@ -4,10 +4,9 @@ [status-im.chaos-mode.core :as chaos-mode] [status-im.data-store.core :as data-store] [status-im.ethereum.subscriptions :as ethereum.subscriptions] + [status-im.ethereum.transactions.core :as transactions] [status-im.fleet.core :as fleet] [status-im.i18n :as i18n] - [status-im.models.transactions :as transactions] - [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,11 @@ (fx/defn initialize-wallet [cofx] (fx/merge cofx - (models.wallet/initialize-tokens) - (ethereum.subscriptions/initialize) - (models.wallet/update-wallet) - (transactions/start-sync))) + (wallet/initialize-tokens) + (wallet/update-balances) + (wallet/update-prices) + (transactions/initialize) + (ethereum.subscriptions/initialize))) (fx/defn user-login [{:keys [db] :as cofx} create-database?] (let [{:keys [address password]} (accounts.db/credentials cofx)] diff --git a/src/status_im/accounts/logout/core.cljs b/src/status_im/accounts/logout/core.cljs index f1c9c3b82ef..c4e8a815e7f 100644 --- a/src/status_im/accounts/logout/core.cljs +++ b/src/status_im/accounts/logout/core.cljs @@ -1,19 +1,17 @@ (ns status-im.accounts.logout.core (:require [re-frame.core :as re-frame] + [status-im.chaos-mode.core :as chaos-mode] [status-im.i18n :as i18n] - [status-im.transport.core :as transport] - [status-im.utils.fx :as fx] - [status-im.models.transactions :as transactions] - [status-im.node.core :as node] [status-im.init.core :as init] - [status-im.chaos-mode.core :as chaos-mode])) + [status-im.node.core :as node] + [status-im.transport.core :as transport] + [status-im.utils.fx :as fx])) (fx/defn logout [{:keys [db] :as cofx}] (fx/merge cofx {:keychain/clear-user-password (get-in db [:account/account :address]) :dev-server/stop nil} - (transactions/stop-sync) (transport/stop-whisper #(re-frame/dispatch [:accounts.logout/filters-removed])) (chaos-mode/stop-checking))) diff --git a/src/status_im/chat/commands/impl/transactions.cljs b/src/status_im/chat/commands/impl/transactions.cljs index 8f7a1d08dbd..160fefe4699 100644 --- a/src/status_im/chat/commands/impl/transactions.cljs +++ b/src/status_im/chat/commands/impl/transactions.cljs @@ -1,30 +1,34 @@ (ns status-im.chat.commands.impl.transactions - (:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require [clojure.string :as string] - [reagent.core :as reagent] [re-frame.core :as re-frame] + [reagent.core :as reagent] + [status-im.chat.commands.impl.transactions.styles + :as + transactions-styles] [status-im.chat.commands.protocol :as protocol] - [status-im.chat.commands.impl.transactions.styles :as transactions-styles] + [status-im.contact.db :as db.contact] [status-im.data-store.messages :as messages-store] - [status-im.ui.components.react :as react] - [status-im.ui.components.icons.vector-icons :as vector-icons] + [status-im.i18n :as i18n] + [status-im.ui.components.animation :as animation] [status-im.ui.components.colors :as colors] + [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.list.views :as list] - [status-im.ui.components.animation :as animation] + [status-im.ui.components.react :as react] [status-im.ui.components.svgimage :as svgimage] - [status-im.i18n :as i18n] - [status-im.contact.db :as db.contact] + [status-im.ui.screens.navigation :as navigation] + [status-im.ui.screens.wallet.choose-recipient.events + :as + choose-recipient.events] + [status-im.ui.screens.wallet.utils :as wallet.utils] + [status-im.utils.datetime :as datetime] [status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.tokens :as tokens] - [status-im.utils.datetime :as datetime] [status-im.utils.fx :as fx] [status-im.utils.money :as money] [status-im.utils.platform :as platform] - [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.components.chat-icon.screen :as chat-icon])) + [status-im.ui.components.chat-icon.screen :as chat-icon] + [status-im.wallet.db :as wallet.db]) + (:require-macros [status-im.utils.views :refer [defview letsubs]])) ;; common `send/request` functionality @@ -179,19 +183,22 @@ ;; `/send` command (defview send-status [tx-hash outgoing] - (letsubs [confirmed? [:chats/transaction-confirmed? tx-hash] - tx-exists? [:chats/wallet-transaction-exists? tx-hash]] - [react/touchable-highlight {:on-press #(when tx-exists? - (re-frame/dispatch [:show-transaction-details tx-hash]))} + (letsubs [{:keys [exists? confirmed?]} [:chats/transaction-status tx-hash]] + [react/touchable-highlight {:on-press #(when exists? + (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 :tiny-icons/tiny-pending) - {:color (if outgoing colors/blue-light colors/blue) + [vector-icons/icon (if confirmed? + :tiny-icons/tiny-check + :tiny-icons/tiny-pending) + {:color (if outgoing + colors/blue-light + colors/blue) :container-style (transactions-styles/command-send-status-icon outgoing)}] [react/view [react/text {:style (transactions-styles/command-send-status-text outgoing)} (i18n/label (cond confirmed? :status-confirmed - tx-exists? :status-pending + exists? :status-pending :else :status-tx-not-found))]]]])) (defn transaction-status [{:keys [tx-hash outgoing]}] @@ -326,9 +333,9 @@ ;; TODO(janherich) - refactor wallet send events, updating gas price ;; is generic thing which shouldn't be defined in wallet.send, then ;; we can include the utility helper without running into circ-dep problem - :update-gas-price {:web3 (:web3 db) - :success-event :wallet/update-gas-price-success - :edit? false}} + :wallet/update-gas-price + {:success-event :wallet/update-gas-price-success + :edit? false}} (navigation/navigate-to-cofx next-view-id {})))) protocol/EnhancedParameters (enhance-send-parameters [_ parameters cofx] diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 732ef73de69..57f31d4259c 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -30,13 +30,6 @@ (def system "system") -(def default-wallet-transactions - {:filters - {:type [{:id :inbound :label (i18n/label :t/incoming) :checked? true} - {:id :outbound :label (i18n/label :t/outgoing) :checked? true} - {:id :pending :label (i18n/label :t/pending) :checked? true} - {:id :failed :label (i18n/label :t/failed) :checked? true}]}}) - (def mainnet-networks {"mainnet" {:id "mainnet", :name "Mainnet", @@ -110,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 "د.إ"} diff --git a/src/status_im/ethereum/contracts.cljs b/src/status_im/ethereum/contracts.cljs new file mode 100644 index 00000000000..47fa87a71c4 --- /dev/null +++ b/src/status_im/ethereum/contracts.cljs @@ -0,0 +1,17 @@ +(ns status-im.ethereum.contracts + (:require [status-im.utils.ethereum.core :as ethereum])) + +(def contracts + {:status/snt + {:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e" + :testnet "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"} + :status/tribute-to-talk + {:testnet "0x3da3fc53e24707f36c5b4433b442e896c4955f0e"} + :status/stickers + {:testnet "0x39d16CdB56b5a6a89e1A397A13Fe48034694316E"}}) + +(defn get-address + [db contract] + (let [chain-keyword (-> (get-in db [:account/account :networks (:network db)]) + ethereum/network->chain-keyword)] + (get-in contracts [contract chain-keyword]))) diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs new file mode 100644 index 00000000000..250ae942f31 --- /dev/null +++ b/src/status_im/ethereum/json_rpc.cljs @@ -0,0 +1,74 @@ +(ns status-im.ethereum.json-rpc + (:require [clojure.string :as string] + [re-frame.core :as re-frame] + [status-im.ethereum.decode :as decode] + [status-im.native-module.core :as status] + [status-im.utils.ethereum.abi-spec :as abi-spec] + [status-im.utils.money :as money] + [status-im.utils.types :as types] + [taoensso.timbre :as log])) + +(def json-rpc-api + {"eth_call" {} + "eth_getBalance" + {:on-result money/bignumber} + "eth_estimateGas" + {:on-result money/bignumber} + "eth_gasPrice" + {:on-result money/bignumber} + "eth_getBlockByHash" + {:on-result #(-> (update % :number decode/uint) + (update :timestamp decode/uint))} + "eth_getTransactionByHash" {} + "eth_getTransactionReceipt" {} + "eth_newBlockFilter" {:subscription? true} + "eth_newFilter" {:subscription? true}}) + +(defn call + [{:keys [method params on-success on-error]}] + (when-let [method-options (json-rpc-api method)] + (let [{:keys [id on-result subscription?] + :or {on-result identity + id 1 + params []}} method-options + on-error (or on-error + #(log/warn :json-rpc/error method :params params :error %))] + (if (nil? method) + (log/error :json-rpc/method-not-found method) + (status/call-private-rpc + (types/clj->json {:jsonrpc "2.0" + :id id + :method (if subscription? + "eth_subscribeSignal" + method) + :params (if subscription? + [method params] + params)}) + (fn [response] + (if (string/blank? response) + (on-error {:message "Blank response"}) + (let [{:keys [error result] :as response2} (types/json->clj response)] + (if error + (on-error error) + (if subscription? + (re-frame/dispatch + [:ethereum.callback/subscription-success + result on-success]) + (on-success (on-result result)))))))))))) + +(defn eth-call + [{:keys [contract method params outputs on-success on-error block] + :or {block "latest" + params []}}] + (call {:method "eth_call" + :params [{:to contract + :data (abi-spec/encode method params)} + (if (int? block) + (abi-spec/number-to-hex block) + block)] + :on-success + (if outputs + #(on-success (abi-spec/decode % outputs)) + on-success) + :on-error + on-error})) diff --git a/src/status_im/ethereum/subscriptions.cljs b/src/status_im/ethereum/subscriptions.cljs index 6570bf6335c..ac25fa915ff 100644 --- a/src/status_im/ethereum/subscriptions.cljs +++ b/src/status_im/ethereum/subscriptions.cljs @@ -1,25 +1,15 @@ (ns status-im.ethereum.subscriptions (:require [clojure.string :as string] [re-frame.core :as re-frame] - [status-im.ethereum.decode :as decode] + [status-im.constants :as constants] + [status-im.ethereum.transactions.core :as transactions] [status-im.native-module.core :as status] + [status-im.utils.ethereum.core :as ethereum] + [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.fx :as fx] - [taoensso.timbre :as log])) - -(defn get-block-by-hash [block-hash callback] - (status/call-private-rpc - (.stringify js/JSON (clj->js {:jsonrpc "2.0" - :id 1 - :method "eth_getBlockByHash" - :params [block-hash false]})) - (fn [response] - (if (string/blank? response) - (log/warn :web3-response-error) - (callback (-> (.parse js/JSON response) - (js->clj :keywordize-keys true) - :result - :number - decode/uint)))))) + [status-im.utils.types :as types] + [taoensso.timbre :as log] + [status-im.ethereum.json-rpc :as json-rpc])) (fx/defn handle-signal [cofx {:keys [subscription_id data] :as event}] @@ -27,49 +17,90 @@ (handler data) (log/warn ::unknown-subscription :event event))) +(fx/defn handle-error + [cofx {:keys [subscription_id data] :as event}] + (log/error ::error event)) + (fx/defn register-subscription [{:keys [db]} id handler] {:db (assoc-in db [:ethereum/subscriptions id] handler)}) +(defn keep-user-transactions + [wallet-address transactions] + (keep (fn [{:keys [to from] :as transaction}] + (when-let [direction (cond + (= wallet-address to) :inbound + (= wallet-address from) :outbound)] + (assoc transaction :direction direction))) + transactions)) + (fx/defn new-block - [{:keys [db]} block-number] - {:db (assoc-in db [:ethereum/current-block] block-number)}) + [{:keys [db] :as cofx} {:keys [number transactions] :as block}] + (when number + (let [{:keys [:account/account :wallet/all-tokens network + :ethereum/current-block]} db + chain (ethereum/network->chain-keyword (get-in account [:networks network])) + chain-tokens (into {} (map (juxt :address identity) + (tokens/tokens-for all-tokens chain))) + wallet-address (ethereum/normalized-address (:address account)) + token-contracts-addresses (into #{} (keys chain-tokens))] + (fx/merge cofx + {:db (assoc-in db [:ethereum/current-block] number) + :ethereum.transactions/enrich-transactions-from-new-blocks + {:chain-tokens chain-tokens + :block block + :transactions (keep-user-transactions wallet-address + transactions)}} + (when (or (not current-block) + (not= number (inc current-block))) + ;; in case we skipped some blocks or got an uncle, re-fetch history + ;; from etherscan + (transactions/initialize)))))) + +(defn new-token-transaction-filter + [{:keys [chain-tokens from to] :as args}] + (json-rpc/call + {:method "eth_newFilter" + :params [{:fromBlock "latest" + :toBlock "latest" + :topics [constants/event-transfer-hash from to]}] + :on-success (transactions/inbound-token-transfer-handler chain-tokens)})) -(defn subscribe-signal - [filter params callback] - (status/call-private-rpc - (.stringify js/JSON (clj->js {:jsonrpc "2.0" - :id 1 - :method "eth_subscribeSignal" - :params [filter, params]})) - (fn [response] - (if (string/blank? response) - (log/error ::subscription-unknown-error :filter filter :params params) - (let [{:keys [error result]} - (-> (.parse js/JSON response) - (js->clj :keywordize-keys true))] - (if error - (log/error ::subscription-error error :filter filter :params params) - (re-frame/dispatch [:ethereum.callback/subscription-success - result - callback]))))))) +(re-frame/reg-fx + :ethereum.subscriptions/token-transactions + (fn [{:keys [address] :as args}] + ;; start inbound token transaction subscriptions + ;; outbound token transactions are already caught in new blocks filter + (new-token-transaction-filter (merge args + {:direction :inbound + :to address})))) (defn new-block-filter [] - (subscribe-signal - "eth_newBlockFilter" [] - (fn [[block-hash]] - (get-block-by-hash - block-hash - (fn [block-number] - (when block-number - (re-frame/dispatch [:ethereum.signal/new-block - block-number]))))))) + (json-rpc/call + {:method "eth_newBlockFilter" + :on-success + (fn [[block-hash]] + (json-rpc/call + {:method "eth_getBlockByHash" + :params [block-hash true] + :on-success + (fn [block] + (re-frame/dispatch [:ethereum.signal/new-block block]))}))})) (re-frame/reg-fx - :ethereum.subscriptions/new-block-filter + :ethereum.subscriptions/new-block new-block-filter) (fx/defn initialize - [cofx] - {:ethereum.subscriptions/new-block-filter nil}) + [{:keys [db] :as cofx}] + (let [{:keys [:account/account :wallet/all-tokens network]} db + chain (ethereum/network->chain-keyword (get-in account [:networks network])) + chain-tokens (into {} (map (juxt :address identity) + (tokens/tokens-for all-tokens chain))) + normalized-address (ethereum/normalized-address (:address account)) + padded-address (transactions/add-padding normalized-address)] + {:ethereum.subscriptions/new-block nil + :ethereum.subscriptions/token-transactions + {:chain-tokens chain-tokens + :address padded-address}})) diff --git a/src/status_im/ethereum/transactions/core.cljs b/src/status_im/ethereum/transactions/core.cljs new file mode 100644 index 00000000000..3ad90681631 --- /dev/null +++ b/src/status_im/ethereum/transactions/core.cljs @@ -0,0 +1,167 @@ +(ns status-im.ethereum.transactions.core + (:require [re-frame.core :as re-frame] + [status-im.constants :as constants] + [status-im.ethereum.decode :as decode] + [status-im.ethereum.json-rpc :as json-rpc] + [status-im.ethereum.transactions.etherscan :as transactions.etherscan] + [status-im.utils.fx :as fx] + [status-im.utils.money :as money] + [status-im.wallet.core :as wallet])) + +(def confirmations-count-threshold 12) + +(defn add-padding [address] + {:pre [(string? address)]} + (str "0x000000000000000000000000" (subs address 2))) + +(defn- remove-padding [address] + {:pre [(string? address)]} + (str "0x" (subs address 26))) + +(def default-erc20-token + {:symbol :ERC20 + :decimals 18 + :name "ERC20"}) + +(defn- parse-token-transfer + [chain-tokens direction transfer] + (let [{:keys [blockHash transactionHash topics data address]} transfer + [_ from to] topics + {:keys [nft? symbol] :as token} (get chain-tokens address + default-erc20-token)] + (when-not nft? + (cond-> {:hash transactionHash + :symbol symbol + :from (remove-padding from) + :to (remove-padding to) + :value (money/bignumber data) + :type direction + :token token + :error? false + ;; NOTE(goranjovic) - just a flag we need when we merge this entry + ;; with the existing entry in the app, e.g. transaction info with + ;; gas details, or a previous transfer entry with old confirmations + ;; count. + :transfer true} + (= :inbound direction) + (assoc :block-hash blockHash))))) + +(defn enrich-transaction-from-new-block + [chain-tokens + {:keys [number timestamp]} + {:keys [transfer direction hash gasPrice value gas from input nonce to] :as transaction}] + (json-rpc/call + {:method "eth_getTransactionReceipt" + :params [hash] + :on-success + (fn [{:keys [gasUsed logs] :as receipt}] + (let [[event _ _] (:topics (first logs)) + transfer (= constants/event-transfer-hash event)] + (re-frame/dispatch + [:ethereum.transactions/new + (merge {:block (str number) + :timestamp (str (* timestamp 1000)) + :gas-used (str (decode/uint gasUsed)) + :gas-price (str (decode/uint gasPrice)) + :gas-limit (str (decode/uint gas)) + :nonce (str (decode/uint nonce)) + :data input} + (if transfer + (parse-token-transfer chain-tokens + :outbound + (first logs)) + ;; this is not a ERC20 token transaction + {:hash hash + :symbol :ETH + :from from + :to to + :type direction + :value (str (decode/uint value))}))])))})) + +(re-frame/reg-fx + :ethereum.transactions/enrich-transactions-from-new-blocks + (fn [{:keys [chain-tokens block transactions]}] + (doseq [transaction transactions] + (enrich-transaction-from-new-block chain-tokens + block + transaction)))) + +(defn inbound-token-transfer-handler + "The handler gets a list of inbound token transfer events and parses each + transfer. Transfers are grouped by block the following chain of callbacks + follows: + - get block by hash is called to get the `timestamp` of each block + - get transaction by hash is called on each transaction to get the `gasPrice` + `gas` used, `input` data and `nonce` of each transaction + - get transaction receipt is used to get the `gasUsed` + - finally everything is merged into one map that is dispatched in a + `ethereum.signal/new-transaction` event for each transfer" + [chain-tokens] + (fn [transfers] + (let [transfers-by-block + (group-by :block-hash + (keep #(parse-token-transfer + chain-tokens + :inbound + %) + transfers))] + ;; TODO: remove this callback chain by implementing a better status-go api + ;; This function takes the map of supported tokens as params and returns a + ;; handler for token transfer events + (doseq [[block-hash block-transfers] transfers-by-block] + (json-rpc/call + {:method "eth_getBlockByHash" + :params [block-hash false] + :on-success + (fn [{:keys [timestamp number]}] + (let [timestamp (str (* timestamp 1000))] + (doseq [{:keys [hash] :as transfer} block-transfers] + (json-rpc/call + {:method "eth_getTransactionByHash" + :params [hash] + :on-success + (fn [{:keys [gasPrice gas input nonce]}] + (json-rpc/call + {:method "eth_getTransactionReceipt" + :params [hash] + :on-success + (fn [{:keys [gasUsed]}] + (re-frame/dispatch + [:ethereum.transactions/new + (-> transfer + (dissoc :block-hash) + (assoc :timestamp timestamp + :block (str number) + :gas-used (str (decode/uint gasUsed)) + :gas-price (str (decode/uint gasPrice)) + :gas-limit (str (decode/uint gas)) + :data input + :nonce (str (decode/uint nonce))))]))}))}))))}))))) + +;; ----------------------------------------------- +;; transactions api +;; ----------------------------------------------- + +(fx/defn new + [{:keys [db] :as cofx} {:keys [hash] :as transaction}] + (fx/merge cofx + {:db (assoc-in db [:wallet :transactions hash] transaction)} + wallet/update-balances)) + +(fx/defn handle-history + [{:keys [db] :as cofx} transactions] + (fx/merge cofx + {:db (update-in db + [:wallet :transactions] + #(merge transactions %))} + wallet/update-balances)) + +(fx/defn handle-token-history + [{:keys [db]} transactions] + {:db (update-in db + [:wallet :transactions] + merge transactions)}) + +(fx/defn initialize + [cofx] + (transactions.etherscan/fetch-history cofx)) diff --git a/src/status_im/ethereum/transactions/etherscan.cljs b/src/status_im/ethereum/transactions/etherscan.cljs new file mode 100644 index 00000000000..8076e93183a --- /dev/null +++ b/src/status_im/ethereum/transactions/etherscan.cljs @@ -0,0 +1,192 @@ +(ns status-im.ethereum.transactions.etherscan + (:require [re-frame.core :as re-frame] + [status-im.utils.ethereum.core :as ethereum] + [status-im.utils.ethereum.tokens :as tokens] + [status-im.utils.fx :as fx] + [status-im.utils.http :as http] + [status-im.utils.types :as types] + [taoensso.timbre :as log])) + +;; -------------------------------------------------------------------------- +;; etherscan transactions +;; -------------------------------------------------------------------------- + +(def etherscan-supported? #{:testnet :mainnet :rinkeby}) + +(let [network->subdomain {:testnet "ropsten" :rinkeby "rinkeby"}] + (defn get-transaction-details-url [chain hash] + {:pre [(keyword? chain) (string? hash)] + :post [(or (nil? %) (string? %))]} + (when (etherscan-supported? chain) + (let [network-subdomain (when-let [subdomain (network->subdomain chain)] + (str subdomain "."))] + (str "https://" network-subdomain "etherscan.io/tx/" hash))))) + +(def etherscan-api-key "DMSI4UAAKUBVGCDMVP3H2STAMSAUV7BYFI") + +(defn- get-api-network-subdomain [chain] + (case chain + (:testnet) "api-ropsten" + (:mainnet) "api" + (:rinkeby) "api-rinkeby")) + +(defn- get-transaction-url + ([chain address] (get-transaction-url chain address false)) + ([chain address chaos-mode?] + {:pre [(keyword? chain) (string? address)] + :post [(string? %)]} + (let [network-subdomain (get-api-network-subdomain chain)] + (if chaos-mode? + "http://httpstat.us/500" + (str "https://" network-subdomain + ".etherscan.io/api?module=account&action=txlist&address=" address + "&startblock=0&endblock=99999999&sort=desc&apikey=" etherscan-api-key + "&q=json"))))) + +(defn- get-token-transaction-url + ([chain address] (get-token-transaction-url chain address false)) + ([chain address chaos-mode?] + {:pre [(keyword? chain) (string? address)] + :post [(string? %)]} + (let [network-subdomain (get-api-network-subdomain chain)] + (if chaos-mode? + "http://httpstat.us/500" + (str "https://" network-subdomain + ".etherscan.io/api?module=account&action=tokentx&address=" address + "&startblock=0&endblock=999999999&sort=asc&apikey=" etherscan-api-key + "&q=json"))))) + +(defn- format-transaction + [address + {:keys [value timeStamp blockNumber hash from to + gas gasPrice gasUsed nonce input isError]}] + (let [inbound? (= address to) + error? (= "1" isError)] + {:value value + ;; timestamp is in seconds, we convert it in ms + :timestamp (str timeStamp "000") + :symbol :ETH + :type (cond error? :failed + inbound? :inbound + :else :outbound) + :block blockNumber + :hash hash + :from from + :to to + :gas-limit gas + :gas-price gasPrice + :gas-used gasUsed + :nonce nonce + :data input})) + +(defn- format-token-transaction + [address + chain-tokens + {:keys [contractAddress blockHash hash tokenDecimal gasPrice value + gas tokenName timeStamp transactionIndex tokenSymbol + confirmations blockNumber from gasUsed input nonce + cumulativeGasUsed to]}] + (let [inbound? (= address to) + token (get chain-tokens contractAddress + {:name tokenName + :symbol tokenSymbol + :decimals tokenDecimal + :address contractAddress})] + {:value value + ;; timestamp is in seconds, we convert it in ms + :timestamp (str timeStamp "000") + :symbol (keyword tokenSymbol) + :type (if inbound? + :inbound + :outbound) + :block blockNumber + :hash hash + :from from + :to to + :gas-limit gas + :gas-price gasPrice + :gas-used gasUsed + :nonce nonce + :data input + :error? false + :transfer true + :token token})) + +(defn- format-transactions-response [response format-fn] + (let [{:keys [result]} (types/json->clj response)] + (cond-> {} + (vector? result) + (into (comp + (map format-fn) + (map (juxt :hash identity))) + result)))) + +(defn- etherscan-history + [chain address on-success on-error chaos-mode?] + (if (etherscan-supported? chain) + (let [url (get-transaction-url chain address chaos-mode?)] + (log/debug :etherscan-transactions :url url) + (http/get url + #(on-success (format-transactions-response + % + (partial format-transaction address))) + on-error)) + (log/info "Etherscan not supported for " chain))) + +(defn- etherscan-token-history + [chain address chain-tokens on-success on-error chaos-mode?] + (if (etherscan-supported? chain) + (let [token-url (get-token-transaction-url chain address chaos-mode?)] + (log/debug :etherscan-token-transactions :token-url token-url) + (http/get token-url + #(on-success (format-transactions-response + % + (partial format-token-transaction address chain-tokens))) + on-error)) + (log/info "Etherscan not supported for " chain))) + +(re-frame/reg-fx + :ethereum.transactions.etherscan/fetch-history + (fn [{:keys [chain address on-success on-error chaos-mode?]}] + (etherscan-history chain address on-success on-error chaos-mode?))) + +(re-frame/reg-fx + :ethereum.transactions.etherscan/fetch-token-history + (fn [{:keys [chain chain-tokens address on-success on-error chaos-mode?]}] + (etherscan-token-history chain address chain-tokens on-success on-error chaos-mode?))) + +;; ----------------------------------------------- +;; chain transactions +;; ----------------------------------------------- + +(fx/defn fetch-history + [{:keys [db] :as cofx}] + (let [{:keys [:account/account :wallet/all-tokens network]} db + chain (ethereum/network->chain-keyword + (get-in account [:networks network])) + chain-tokens (into {} (map (juxt :address identity) + (tokens/tokens-for all-tokens chain))) + chaos-mode? (get-in account [:settings :chaos-mode?]) + normalized-address (ethereum/normalized-address (:address account))] + #:ethereum.transactions.etherscan + {:fetch-history + {:chain chain + :address normalized-address + :on-success + #(re-frame/dispatch + [:ethereum.transactions.callback/fetch-history-success %]) + :on-error + #(re-frame/dispatch + [:ethereum.transactions.callback/etherscan-error %]) + :chaos-mode? chaos-mode?} + :fetch-token-history + {:chain chain + :chain-tokens chain-tokens + :address normalized-address + :on-success + #(re-frame/dispatch + [:ethereum.transactions.callback/fetch-token-history-success %]) + :on-error + #(re-frame/dispatch + [:ethereum.transactions.callback/etherscan-error %]) + :chaos-mode? chaos-mode?}})) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 88d3304af7d..ea005a5fe9f 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -21,6 +21,7 @@ [status-im.contact.block :as contact.block] [status-im.contact.core :as contact] [status-im.ethereum.subscriptions :as ethereum.subscriptions] + [status-im.ethereum.transactions.core :as ethereum.transactions] [status-im.extensions.core :as extensions] [status-im.extensions.registry :as extensions.registry] [status-im.fleet.core :as fleet] @@ -56,6 +57,8 @@ [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] [status-im.wallet.custom-tokens.core :as custom-tokens])) @@ -976,11 +979,6 @@ (fn [cofx [_ error sync]] (web3/update-syncing-progress cofx error sync))) -(handlers/register-handler-fx - :web3.callback/get-block-number - (fn [cofx [_ error block-number]] - (node/update-block-number cofx error block-number))) - ;; notifications module (handlers/register-handler-fx @@ -2054,17 +2052,6 @@ (fn [cofx _] (bottom-sheet/hide-bottom-sheet cofx))) -;; ethereum subscriptions events -(handlers/register-handler-fx - :ethereum.callback/subscription-success - (fn [cofx [_ id handler]] - (ethereum.subscriptions/register-subscription cofx id handler))) - -(handlers/register-handler-fx - :ethereum.signal/new-block - (fn [cofx [_ block-number]] - (ethereum.subscriptions/new-block cofx block-number))) - ;;custom tokens (handlers/register-handler-fx @@ -2120,4 +2107,143 @@ (fx/merge cofx (custom-tokens/remove-custom-token token) (when navigate-back? - (navigation/navigate-back))))) \ No newline at end of file + (navigation/navigate-back))))) + +;; ethereum subscriptions events + +(handlers/register-handler-fx + :ethereum.callback/subscription-success + (fn [cofx [_ id handler]] + (ethereum.subscriptions/register-subscription cofx id handler))) + +(handlers/register-handler-fx + :ethereum.signal/new-block + (fn [cofx [_ block]] + (ethereum.subscriptions/new-block cofx block))) + +;; ethereum transactions events +(handlers/register-handler-fx + :ethereum.transactions.callback/fetch-history-success + (fn [cofx [_ transactions]] + (ethereum.transactions/handle-history cofx transactions))) + +(handlers/register-handler-fx + :ethereum.transactions.callback/etherscan-error + (fn [cofx [event error]] + (log/info event error))) + +(handlers/register-handler-fx + :ethereum.transactions.callback/fetch-token-history-success + (fn [cofx [_ transactions]] + (ethereum.transactions/handle-token-history cofx transactions))) + +(handlers/register-handler-fx + :ethereum.transactions/new + (fn [cofx [_ transaction]] + (ethereum.transactions/new cofx transaction))) + +;; wallet events + +(handlers/register-handler-fx + :wallet.ui/pull-to-refresh + (fn [cofx _] + (wallet/update-prices cofx))) + +(handlers/register-handler-fx + :wallet.transactions/add-filter + (fn [{:keys [db]} [_ id]] + {:db (update-in db [:wallet :filters] conj id)})) + +(handlers/register-handler-fx + :wallet.transactions/remove-filter + (fn [{:keys [db]} [_ id]] + {:db (update-in db [:wallet :filters] disj id)})) + +(handlers/register-handler-fx + :wallet.transactions/add-all-filters + (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 + :wallet.settings.ui/navigate-back-pressed + (fn [cofx [_ on-close]] + (fx/merge cofx + (when on-close + {:dispatch on-close}) + (navigation/navigate-back) + (wallet/update-balances)))) + +(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/on-update-prices-success 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]] + {:wallet/update-estimated-gas + {: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)))) diff --git a/src/status_im/extensions/capacities/ethereum.cljs b/src/status_im/extensions/capacities/ethereum.cljs index 8857b16743c..4de54e8de24 100644 --- a/src/status_im/extensions/capacities/ethereum.cljs +++ b/src/status_im/extensions/capacities/ethereum.cljs @@ -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 diff --git a/src/status_im/hardwallet/core.cljs b/src/status_im/hardwallet/core.cljs index 1c61b9c1771..47120b69193 100644 --- a/src/status_im/hardwallet/core.cljs +++ b/src/status_im/hardwallet/core.cljs @@ -1,24 +1,24 @@ (ns status-im.hardwallet.core - (:require [re-frame.core :as re-frame] - status-im.hardwallet.fx - [status-im.ui.screens.navigation :as navigation] - [status-im.utils.config :as config] - [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] + (: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.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.utils.fx :as fx] + [status-im.utils.platform :as platform] + [status-im.utils.types :as types] + [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) diff --git a/src/status_im/init/core.cljs b/src/status_im/init/core.cljs index 9d99fd91d30..b717262dc7f 100644 --- a/src/status_im/init/core.cljs +++ b/src/status_im/init/core.cljs @@ -1,37 +1,28 @@ (ns status-im.init.core (:require [re-frame.core :as re-frame] - [status-im.chat.models.loading :as chat-loading] - [status-im.accounts.core :as accounts.core] [status-im.accounts.login.core :as accounts.login] [status-im.accounts.update.core :as accounts.update] - [status-im.constants :as constants] - [status-im.react-native.js-dependencies :as rn-dependencies] + [status-im.browser.core :as browser] + [status-im.chat.models :as chat-model] + [status-im.contact.core :as contact] [status-im.data-store.core :as data-store] [status-im.data-store.realm.core :as realm] [status-im.extensions.registry :as extensions.registry] [status-im.i18n :as i18n] - [status-im.browser.core :as browser] - [status-im.contact.core :as contact] [status-im.models.dev-server :as models.dev-server] - [status-im.protocol.core :as protocol] - [status-im.pairing.core :as pairing] - [status-im.models.transactions :as transactions] - [status-im.models.wallet :as models.wallet] [status-im.native-module.core :as status] [status-im.node.core :as node] [status-im.notifications.core :as notifications] + [status-im.pairing.core :as pairing] + [status-im.react-native.js-dependencies :as rn-dependencies] + [status-im.stickers.core :as stickers] [status-im.ui.screens.db :refer [app-db]] [status-im.ui.screens.navigation :as navigation] - [status-im.utils.config :as config] [status-im.utils.ethereum.core :as ethereum] + [status-im.utils.fx :as fx] [status-im.utils.keychain.core :as keychain] [status-im.utils.platform :as platform] - [status-im.utils.utils :as utils] - [taoensso.timbre :as log] - [status-im.utils.fx :as fx] - [status-im.chat.models :as chat-model] - [status-im.accounts.db :as accounts.db] - [status-im.stickers.core :as stickers])) + [taoensso.timbre :as log])) (defn init-store! "Try to decrypt the database, move on if successful otherwise go back to diff --git a/src/status_im/models/transactions.cljs b/src/status_im/models/transactions.cljs deleted file mode 100644 index 1b46207a42a..00000000000 --- a/src/status_im/models/transactions.cljs +++ /dev/null @@ -1,422 +0,0 @@ -(ns status-im.models.transactions - (:require [clojure.set :as set] - [cljs.core.async :as async] - [clojure.string :as string] - [status-im.utils.async :as async-util] - [status-im.utils.ethereum.core :as ethereum] - [status-im.constants :as constants] - [status-im.native-module.core :as status] - [status-im.utils.ethereum.tokens :as tokens] - [status-im.utils.http :as http] - [status-im.utils.types :as types] - [taoensso.timbre :as log] - [status-im.utils.fx :as fx] - [re-frame.core :as re-frame] - [re-frame.db] - [status-im.utils.config :as config]) - (:require-macros - [cljs.core.async.macros :refer [go-loop go]])) - -(def sync-interval-ms 15000) -(def sync-timeout-ms 20000) -(def confirmations-count-threshold 12) -(def block-query-limit 100000) - -;; ---------------------------------------------------------------------------- -;; token transfer event logs from eth-node -;; ---------------------------------------------------------------------------- - -(defn- parse-json [s] - {:pre [(string? s)]} - (try - (let [res (-> s - js/JSON.parse - (js->clj :keywordize-keys true))] - (if (= (:error res) "") - {:result true} - res)) - (catch :default e - {:error (.-message e)}))) - -(defn- add-padding [address] - {:pre [(string? address)]} - (str "0x000000000000000000000000" (subs address 2))) - -(defn- remove-padding [topic] - {:pre [(string? topic)]} - (str "0x" (subs topic 26))) - -(defn- parse-transaction-entries [current-block-number block-info chain-tokens direction transfers] - {:pre [(integer? current-block-number) (map? block-info) - (map? chain-tokens) (every? (fn [[k v]] (and (string? k) (map? v))) chain-tokens) - (keyword? direction) - (every? map? transfers)]} - (into {} - (keep identity - (for [transfer transfers] - (when-let [token (->> transfer :address (get chain-tokens))] - (when-not (:nft? token) - [(:transactionHash transfer) - {:block (-> block-info :number str) - :hash (:transactionHash transfer) - :symbol (:symbol token) - :from (some-> transfer :topics second remove-padding) - :to (some-> transfer :topics last remove-padding) - :value (-> transfer :data ethereum/hex->bignumber) - :type direction - - :confirmations (str (- current-block-number (-> transfer :blockNumber ethereum/hex->int))) - - :gas-price nil - :nonce nil - :data nil - - :gas-limit nil - :timestamp (-> block-info :timestamp (* 1000) str) - - :gas-used nil - - ;; NOTE(goranjovic) - metadata on the type of token: contains name, symbol, decimas, address. - :token token - - ;; NOTE(goranjovic) - if an event has been emitted, we can say there was no error - :error? false - - ;; NOTE(goranjovic) - just a flag we need when we merge this entry with the existing entry in - ;; the app, e.g. transaction info with gas details, or a previous transfer entry with old - ;; confirmations count. - :transfer true}])))))) - -(defn- add-block-info [web3 current-block-number chain-tokens direction result success-fn] - {:pre [web3 (integer? current-block-number) (map? chain-tokens) (keyword? direction) - (every? map? result) - (fn? success-fn)]} - (let [transfers-by-block (group-by :blockNumber result)] - (doseq [[block-number transfers] transfers-by-block] - (ethereum/get-block-info web3 (ethereum/hex->int block-number) - (fn [block-info] - (if-not (map? block-info) - (log/error "Request for block info failed") - (success-fn (parse-transaction-entries current-block-number - block-info - chain-tokens - direction - transfers)))))))) - -(defn- response-handler [web3 current-block-number chain-tokens direction error-fn success-fn] - (fn handle-response - ([response] - #_(log/debug "Token transaction logs recieved --" (pr-str response)) - (let [{:keys [error result]} (parse-json response)] - (handle-response error result))) - ([error result] - (if error - (error-fn error) - (add-block-info web3 current-block-number chain-tokens direction result success-fn))))) - -(defn- limited-from-block [current-block-number latest-block-checked] - {:pre [(integer? current-block-number)] - ;; needs to be a positive etherium hex - :post [(string? %) (string/starts-with? % "0x")]} - (if latest-block-checked - (-> latest-block-checked (- confirmations-count-threshold) ethereum/int->hex) - (-> current-block-number (- block-query-limit) (max 0) ethereum/int->hex))) - -;; Here we are querying event logs for Transfer events. -;; -;; The parameters are as follows: -;; - address - token smart contract address -;; - fromBlock - we need to specify it, since default is latest -;; - topics[0] - hash code of the Transfer event signature -;; - topics[1] - address of token sender with leading zeroes padding up to 32 bytes -;; - topics[2] - address of token sender with leading zeroes padding up to 32 bytes -;; - -(defonce latest-block-checked (atom nil)) - -(defn- get-token-transfer-logs - ;; NOTE(goranjovic): here we use direct JSON-RPC calls to get event logs because of web3 event issues with infura - ;; we still use web3 to get other data, such as block info - [web3 current-block-number from-block chain-tokens direction address cb] - {:pre [web3 (integer? current-block-number) (map? chain-tokens) (keyword? direction) (string? address) (fn? cb)]} - (let [[from to] (if (= :inbound direction) - [nil (add-padding (ethereum/normalized-address address))] - [(add-padding (ethereum/normalized-address address)) nil]) - args {:jsonrpc "2.0" - :id 2 - :method constants/web3-get-logs - :params [{:address (keys chain-tokens) - :fromBlock from-block - :topics [constants/event-transfer-hash from to]}]} - payload (.stringify js/JSON (clj->js args))] - (status/call-private-rpc payload - (response-handler web3 current-block-number chain-tokens direction ethereum/handle-error cb)))) - -(defn- get-token-transactions - [web3 chain-tokens address cb] - {:pre [web3 (map? chain-tokens) (string? address) (fn? cb)]} - (ethereum/get-block-number web3 - (fn [current-block-number] - (let [from-block (limited-from-block current-block-number @latest-block-checked)] - (reset! latest-block-checked current-block-number) - (get-token-transfer-logs web3 current-block-number from-block chain-tokens :inbound address cb) - (get-token-transfer-logs web3 current-block-number from-block chain-tokens :outbound address cb))))) - -;; -------------------------------------------------------------------------- -;; etherscan transactions -;; -------------------------------------------------------------------------- - -(def etherscan-supported? #{:testnet :mainnet :rinkeby}) - -(let [network->subdomain {:testnet "ropsten" :rinkeby "rinkeby"}] - (defn get-transaction-details-url [chain hash] - {:pre [(keyword? chain) (string? hash)] - :post [(or (nil? %) (string? %))]} - (when (etherscan-supported? chain) - (let [network-subdomain (when-let [subdomain (network->subdomain chain)] - (str subdomain "."))] - (str "https://" network-subdomain "etherscan.io/tx/" hash))))) - -(def etherscan-api-key "DMSI4UAAKUBVGCDMVP3H2STAMSAUV7BYFI") - -(defn- get-api-network-subdomain [chain] - (case chain - (:testnet) "api-ropsten" - (:mainnet) "api" - (:rinkeby) "api-rinkeby")) - -(defn- get-transaction-url - ([chain account] (get-transaction-url chain account false)) - ([chain account chaos-mode?] - {:pre [(keyword? chain) (string? account)] - :post [(string? %)]} - (let [network-subdomain (get-api-network-subdomain chain)] - (if chaos-mode? - "http://httpstat.us/500" - (str "https://" network-subdomain - ".etherscan.io/api?module=account&action=txlist&address=0x" - account "&startblock=0&endblock=99999999&sort=desc&apikey=" etherscan-api-key "&q=json"))))) - -(defn- format-transaction [account - {:keys [value timeStamp blockNumber hash from to - gas gasPrice gasUsed nonce confirmations - input isError]}] - (let [inbound? (= (str "0x" account) to) - error? (= "1" isError)] - {:value value - ;; timestamp is in seconds, we convert it in ms - :timestamp (str timeStamp "000") - :symbol :ETH - :type (cond error? :failed - inbound? :inbound - :else :outbound) - :block blockNumber - :hash hash - :from from - :to to - :gas-limit gas - :gas-price gasPrice - :gas-used gasUsed - :nonce nonce - :confirmations confirmations - :data input})) - -(defn- format-transactions-response [response account] - (let [{:keys [result]} (types/json->clj response)] - (cond-> {} - (vector? result) - (into (comp - (map (partial format-transaction account)) - (map (juxt :hash identity))) - result)))) - -(defn- etherscan-transactions - ([chain account on-success on-error] - (etherscan-transactions chain account on-success on-error false)) - ([chain account on-success on-error chaos-mode?] - (if (etherscan-supported? chain) - (let [url (get-transaction-url chain account chaos-mode?)] - (log/debug "HTTP GET" url) - (http/get url - #(on-success (format-transactions-response % account)) - on-error)) - (log/info "Etherscan not supported for " chain)))) - -(defn- get-transactions [{:keys [web3 chain chain-tokens account-address - success-fn error-fn chaos-mode?]}] - (log/debug "Syncing transactions data..") - (etherscan-transactions chain - account-address - success-fn - error-fn - chaos-mode?) - (get-token-transactions web3 - chain-tokens - account-address - success-fn)) - -;; ----------------------------------------------------------------------------- -;; Helpers functions that help determine if a background sync should execute -;; ----------------------------------------------------------------------------- - -(defn- keyed-memoize - "Space bounded memoize. - - Takes a key-function that decides the key in the cache for the - memoized value. Takes a value function that will extract the value - that will invalidate the cache if it changes. And finally the - function to memoize. - - Memoize that doesn't grow bigger than the number of keys." - [key-fn val-fn f] - (let [val-store (atom {}) - res-store (atom {})] - (fn [arg] - (let [k (key-fn arg) - v (val-fn arg)] - (if (not= (get @val-store k) v) - (let [res (f arg)] - #_(prn "storing!!!!" res) - (swap! val-store assoc k v) - (swap! res-store assoc k res) - res) - (get @res-store k)))))) - -;; Map[id, chat] -> Set[transaction-id] -;; chat may or may not have a :messages Map -(let [chat-map-entry->transaction-ids - (keyed-memoize key (comp :messages val) - (fn [[_ chat]] - (some->> (:messages chat) - vals - (filter #(= "command" (:content-type %))) - (keep #(select-keys (get-in % [:content :params]) [:tx-hash :network])))))] - (defn- chat-map->transaction-ids [network chat-map] - {:pre [(string? network) (every? map? (vals chat-map))] - :post [(set? %)]} - (let [network (string/replace network "_rpc" "")] - (->> chat-map - (remove (comp :public? val)) - (mapcat chat-map-entry->transaction-ids) - (filter #(= network (:network %))) - (map :tx-hash) - set)))) - -;; Seq[transaction] -> truthy -(defn- have-unconfirmed-transactions? - "Detects if some of the transactions have less than 12 confirmations" - [transactions] - {:pre [(every? string? (map :confirmations transactions))]} - (->> transactions - (map :confirmations) - (map int) - (some #(< % confirmations-count-threshold)))) - -(letfn [(combine-entries [transaction token-transfer] - (merge transaction (select-keys token-transfer [:symbol :from :to :value :type :token :transfer]))) - (update-confirmations [tx1 tx2] - (assoc tx1 :confirmations (str (max (int (:confirmations tx1)) - (int (:confirmations tx2)))))) - (tx-and-transfer? [tx1 tx2] - (and (not (:transfer tx1)) (:transfer tx2))) - (both-transfer? - [tx1 tx2] - (and (:transfer tx1) (:transfer tx2)))] - (defn- dedupe-transactions [tx1 tx2] - (cond (tx-and-transfer? tx1 tx2) (combine-entries tx1 tx2) - (tx-and-transfer? tx2 tx1) (combine-entries tx2 tx1) - (both-transfer? tx1 tx2) (update-confirmations tx1 tx2) - :else tx2))) - -;; ---------------------------------------------------------------------------- -;; The following Code represents how fetching transactions is -;; complected with the rest of the application -;; ---------------------------------------------------------------------------- - -(defonce polling-executor (atom nil)) - -(defn transactions-query-helper [web3 all-tokens account-address chain done-fn chaos-mode?] - (get-transactions - {:account-address account-address - :chain chain - :chain-tokens (into {} (map (juxt :address identity) (tokens/tokens-for all-tokens chain))) - :web3 web3 - :success-fn (fn [transactions] - #_(log/debug "Transactions received: " (pr-str (keys transactions))) - (swap! re-frame.db/app-db - (fn [app-db] - (when (= (get-in app-db [:account/account :address]) - account-address) - (update-in app-db - [:wallet :transactions] - #(merge-with dedupe-transactions % transactions))))) - (done-fn)) - :error-fn (fn [http-error] - (log/debug "Unable to get transactions: " http-error) - (done-fn)) - :chaos-mode? chaos-mode?})) - -(defn- sync-now! [{:keys [network-status :account/account :wallet/all-tokens app-state network web3] :as opts}] - (when @polling-executor - (let [chain (ethereum/network->chain-keyword (get-in account [:networks network])) - account-address (:address account) - chaos-mode? (get-in account [:settings :chaos-mode?])] - (when (and (not= network-status :offline) - (= app-state "active") - (not= :custom chain)) - (async-util/async-periodic-run! - @polling-executor - #(transactions-query-helper web3 all-tokens account-address chain % chaos-mode?)))))) - -;; this function handles background syncing of transactions -(defn- background-sync [web3 account-address done-fn] - (let [{:keys [network network-status :account/account app-state wallet chats :wallet/all-tokens]} @re-frame.db/app-db - chain (ethereum/network->chain-keyword (get-in account [:networks network]))] - (assert (and web3 account-address network network-status account app-state wallet chats) - "Must have all necessary data to run background transaction sync") - (if-not (and (not= network-status :offline) - (= app-state "active") - (not= :custom chain)) - (done-fn) - (let [chat-transaction-ids (chat-map->transaction-ids network chats) - transaction-map (:transactions wallet) - transaction-ids (set (keys transaction-map)) - chaos-mode? (get-in account [:settings :chaos-mode?])] - (if-not (or @latest-block-checked - (have-unconfirmed-transactions? (vals transaction-map)) - (not-empty (set/difference chat-transaction-ids transaction-ids))) - (done-fn) - (transactions-query-helper web3 all-tokens account-address chain done-fn chaos-mode?)))))) - -(defn- start-sync! [{:keys [:account/account network web3] :as options}] - (reset! latest-block-checked nil) - (let [account-address (:address account)] - (when @polling-executor - (async-util/async-periodic-stop! @polling-executor)) - (reset! polling-executor - (async-util/async-periodic-exec - (partial #'background-sync web3 account-address) - sync-interval-ms - sync-timeout-ms))) - (sync-now! options)) - -(re-frame/reg-fx - ::sync-transactions-now - (fn [db] (sync-now! db))) - -(re-frame/reg-fx - ::start-sync-transactions - (fn [db] (start-sync! db))) - -(fx/defn start-sync [{:keys [db]}] - {::start-sync-transactions - (select-keys db [:network-status :account/account :wallet/all-tokens - :app-state :network :web3])}) - -(re-frame/reg-fx - ::stop-sync-transactions - #(when @polling-executor - (async-util/async-periodic-stop! @polling-executor))) - -(fx/defn stop-sync [_] - {::stop-sync-transactions nil}) diff --git a/src/status_im/models/wallet.cljs b/src/status_im/models/wallet.cljs deleted file mode 100644 index c6d3bb6cb9d..00000000000 --- a/src/status_im/models/wallet.cljs +++ /dev/null @@ -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)}) diff --git a/src/status_im/node/core.cljs b/src/status_im/node/core.cljs index 2f16f4513fa..71d1212ca41 100644 --- a/src/status_im/node/core.cljs +++ b/src/status_im/node/core.cljs @@ -157,11 +157,6 @@ {:error error} (when sync-state (js->clj sync-state :keywordize-keys true))))}) -(fx/defn update-block-number - [{:keys [db]} error block-number] - (when-not error - {:db (assoc db :node/latest-block-number block-number)})) - (fx/defn start [{:keys [db]} address] (let [network (if address diff --git a/src/status_im/protocol/core.cljs b/src/status_im/protocol/core.cljs index 4d28b482a63..35f6630383b 100644 --- a/src/status_im/protocol/core.cljs +++ b/src/status_im/protocol/core.cljs @@ -33,7 +33,6 @@ [{{:keys [web3] :as db} :db :as cofx}] (if (:account/account db) {:web3/get-syncing web3 - :web3/get-block-number web3 :utils/dispatch-later [{:ms 10000 :dispatch [:protocol/state-sync-timed-out]}]} (semaphores/free cofx :check-sync-state?))) diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 9a148622258..548365872e7 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -74,4 +74,5 @@ "messages.decrypt.failed" (contact-recovery/handle-contact-recovery-fx cofx (:sender event)) "discovery.summary" (summary cofx event) "subscriptions.data" (ethereum.subscriptions/handle-signal cofx event) + "subscriptions.error" (ethereum.subscriptions/handle-error cofx event) (log/debug "Event " type " not handled")))) diff --git a/src/status_im/stickers/core.cljs b/src/status_im/stickers/core.cljs index fccccb7a0ad..8a7ec3ee010 100644 --- a/src/status_im/stickers/core.cljs +++ b/src/status_im/stickers/core.cljs @@ -1,25 +1,96 @@ (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.ethereum.contracts :as contracts] + [status-im.ethereum.json-rpc :as json-rpc] + [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.fx :as fx] + [status-im.utils.multihash :as multihash] + [status-im.utils.utils :as utils] + [status-im.wallet.core :as wallet])) + +(defn pack-data-callback + [id open?] + (fn [[category owner mintable timestamp price contenthash]] + (let [proto-code (subs contenthash 2 4) + hash (when contenthash + (multihash/base58 (multihash/create :sha2-256 (subs contenthash 12))))] + (when (and (#{constants/swarm-proto-code constants/ipfs-proto-code} + proto-code) hash) + (re-frame/dispatch [:stickers/load-pack proto-code hash id price open?]))))) + +(re-frame/reg-fx + :stickers/set-pending-timout-fx + (fn [] + (utils/set-timeout #(re-frame/dispatch [:stickers/pending-timout]) + 10000))) + +(defn eth-call-pack-data + [contract id open?] + (json-rpc/eth-call + {:contract contract + ;; Returns vector of pack data parameters by pack id: + ;; [category owner mintable timestamp price contenthash] + :method "getPackData(uint256)" + :params [id] + :outputs ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"] + :on-success (pack-data-callback id open?)})) + +(re-frame/reg-fx + :stickers/pack-data-fx + (fn [[contract id]] + (eth-call-pack-data contract id true))) + +(re-frame/reg-fx + :stickers/load-packs-fx + (fn [[contract]] + (json-rpc/eth-call + {:contract contract + ;; Returns number of packs registered in the contract + :method "packCount()" + :outputs ["uint256"] + :on-success + (fn [[count]] + (dotimes [id count] + (eth-call-pack-data contract id false)))}))) -(fx/defn init-stickers-packs [{:keys [db]}] +(re-frame/reg-fx + :stickers/owned-packs-fx + (fn [[contract address]] + (json-rpc/eth-call + {:contract contract + ;; Returns vector of owned tokens ids in the contract by address + :method "tokensOwnedBy(address)" + :params [address] + :outputs ["uint256[]"] + :on-success + (fn [[tokens]] + (doseq [id tokens] + (json-rpc/eth-call + {:contract contract + ;; Returns pack id in the contract by token id + :method "tokenPackId(uint256)" + :params [id] + :outputs ["uint256"] + :on-success + (fn [[pack-id]] + (re-frame/dispatch [:stickers/pack-owned pack-id]))})))}))) + +(fx/defn init-stickers-packs + [{:keys [db]}] (let [sticker-packs (into {} (map #(let [pack (edn/read-string %)] (vector (:id pack) pack)) (get-in db [:account/account :stickers])))] - {:db (assoc db :stickers/packs-installed sticker-packs :stickers/packs sticker-packs)})) + {:db (assoc db + :stickers/packs-installed sticker-packs + :stickers/packs sticker-packs)})) -(fx/defn install-stickers-pack [{{:account/keys [account] :as db} :db :as cofx} id] +(fx/defn install-stickers-pack + [{{:account/keys [account] :as db} :db :as cofx} id] (let [pack (get-in db [:stickers/packs id])] (fx/merge cofx @@ -28,120 +99,91 @@ (assoc :stickers/selected-pack id))} (accounts/update-stickers (conj (:stickers account) (pr-str pack)))))) -(fx/defn load-sticker-pack-success [{:keys [db] :as cofx} edn-string id price open?] +(fx/defn load-sticker-pack-success + [{:keys [db] :as cofx} edn-string id price open?] (let [pack (assoc (get (edn/read-string edn-string) 'meta) :id id :price price)] (fx/merge cofx - {:db (-> db (assoc-in [:stickers/packs id] pack))} - #(when open? (navigation/navigate-to-cofx % :stickers-pack-modal pack))))) - -(defn pack-data-callback [id open?] - (fn [[category owner mintable timestamp price contenthash]] - (let [proto-code (subs contenthash 2 4) - hash (when contenthash (multihash/base58 (multihash/create :sha2-256 (subs contenthash 12))))] - (when (and (#{constants/swarm-proto-code constants/ipfs-proto-code} proto-code) hash) - (re-frame/dispatch [:stickers/load-pack proto-code hash id price open?]))))) + {:db (assoc-in db [:stickers/packs id] pack)} + #(when open? + (navigation/navigate-to-cofx % :stickers-pack-modal pack))))) (fx/defn open-sticker-pack - [{{:keys [network] :stickers/keys [packs packs-installed] :as db} :db :as cofx} id] + [{{:stickers/keys [packs packs-installed] :as db} :db :as cofx} id] (when id - (let [pack (or (get packs-installed id) (get packs id)) - network (get-in db [:account/account :networks network])] + (let [pack (or (get packs-installed id) + (get packs id)) + contract-address (contracts/get-address db :status/stickers)] (if pack (navigation/navigate-to-cofx cofx :stickers-pack-modal pack) - {:stickers/pack-data-fx [network id true]})))) - -(fx/defn load-pack [cofx proto-code hash id price open?] - {:http-get {:url (str (if (= constants/swarm-proto-code proto-code) - "https://swarm-gateways.net/bzz:/" - "https://ipfs.infura.io/ipfs/") - hash) - :success-event-creator (fn [o] - [:stickers/load-sticker-pack-success o id price open?]) - :failure-event-creator (constantly nil)}}) - -(fx/defn load-packs [{{:keys [network] :as db} :db}] - (let [network (get-in db [:account/account :networks network]) - address (ethereum/normalized-address (get-in db [:account/account :address]))] - {:stickers/owned-packs-fx [network address] - :stickers/load-packs-fx [network]})) - -(defn prepare-transaction [id tx on-result] - (merge {:id id - :symbol :ETH - :method constants/web3-send-transaction - :amount (money/bignumber 0)} - (when on-result {:on-result on-result}) - tx)) - -(fx/defn approve-pack [{db :db} pack-id price] - (let [network (get-in db [:account/account :networks (:network db)]) - address (ethereum/normalized-address (get-in db [:account/account :address])) - chain (ethereum/network->chain-keyword network) - stickers-contract (get ethereum.stickers/contracts chain) - 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 - db - (prepare-transaction "approve" tx-object [:stickers/pending-pack pack-id]) - tx-object))) + (when contract-address + {:stickers/pack-data-fx [contract-address id]}))))) + +(fx/defn load-pack + [cofx proto-code hash id price open?] + {:http-get {:url (str (if (= constants/swarm-proto-code proto-code) + "https://swarm-gateways.net/bzz:/" + "https://ipfs.infura.io/ipfs/") + hash) + :success-event-creator + (fn [o] + [:stickers/load-sticker-pack-success o id price open?]) + :failure-event-creator + (constantly nil)}}) + +(fx/defn load-packs + [{:keys [db]}] + (let [contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + {:stickers/owned-packs-fx [contract address] + :stickers/load-packs-fx [contract]}))) + +(fx/defn approve-pack + [{db :db :as cofx} pack-id price] + (let [address (ethereum/current-address db) + chain (ethereum/chain-keyword db) + stickers-contract (contracts/get-address db :status/stickers) + snt-contract (contracts/get-address db :status/snt)] + (wallet/eth-transaction-call + cofx + {:contract snt-contract + :method "approveAndCall(address,uint256,bytes)" + :params [stickers-contract + price + (abi-spec/encode "buyToken(uint256,address)" + [pack-id address])] + :on-result [:stickers/pending-pack pack-id]}))) (fx/defn pending-pack - [{{:keys [network] :as db} :db :as cofx} id] - (let [network (get-in db [:account/account :networks network]) - address (ethereum/normalized-address (get-in db [:account/account :address]))] - (fx/merge cofx - {:db (update db :stickers/packs-pendning conj id) - :stickers/owned-packs-fx [network address]} - (navigation/navigate-to-clean :wallet-transaction-sent-modal {}) - #(when (zero? (count (:stickers/packs-pendning db))) - {:stickers/set-pending-timout-fx nil})))) + [{:keys [db] :as cofx} id] + (let [contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + (fx/merge cofx + {:db (update db :stickers/packs-pending conj id) + :stickers/owned-packs-fx [contract address]} + (navigation/navigate-to-clean :wallet-transaction-sent-modal {}) + #(when (zero? (count (:stickers/packs-pending db))) + {:stickers/set-pending-timout-fx nil}))))) (fx/defn pending-timeout - [{{:keys [network] :stickers/keys [packs-pendning packs-owned] :as db} :db}] - (let [packs-diff (clojure.set/difference packs-pendning packs-owned) - network (get-in db [:account/account :networks network]) - address (ethereum/normalized-address (get-in db [:account/account :address]))] - (merge {:db (assoc db :stickers/packs-pendning packs-diff)} - (when-not (zero? (count packs-diff)) - {:stickers/owned-packs-fx [network address] - :stickers/set-pending-timout-fx nil})))) + [{{:stickers/keys [packs-pending packs-owned] :as db} :db}] + (let [packs-diff (clojure.set/difference packs-pending packs-owned) + contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + (merge {:db (assoc db :stickers/packs-pending packs-diff)} + (when-not (zero? (count packs-diff)) + {:stickers/owned-packs-fx [contract address] + :stickers/set-pending-timout-fx nil}))))) (fx/defn pack-owned [{db :db} id] {:db (update db :stickers/packs-owned conj id)}) (fx/defn get-owned-pack - [{{:keys [network] :as db} :db}] - (let [address (ethereum/normalized-address (get-in db [:account/account :address]))] - {:stickers/owned-packs-fx [network address]})) - -(re-frame/reg-fx - :stickers/pack-data-fx - (fn [[network id open?]] - (when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))] - (ethereum.stickers/pack-data contract id (pack-data-callback id open?))))) - -(re-frame/reg-fx - :stickers/set-pending-timout-fx - (fn [] - (js/setTimeout #(re-frame/dispatch [:stickers/pending-timout]) 10000))) - -(re-frame/reg-fx - :stickers/load-packs-fx - (fn [[network]] - (when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))] - (ethereum.stickers/pack-count contract - (fn [count] - (dotimes [n count] - (ethereum.stickers/pack-data contract n (pack-data-callback n false)))))))) - -(re-frame/reg-fx - :stickers/owned-packs-fx - (fn [[network address]] - (when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))] - (ethereum.stickers/owned-tokens contract address - (fn [tokens] - (doseq [n tokens] - (ethereum.stickers/token-pack-id contract n - #(re-frame/dispatch [:stickers/pack-owned %])))))))) + [{:keys [db]}] + (let [contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + {:stickers/owned-packs-fx [contract address]}))) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 3d095acbb0b..02331eedb13 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -10,10 +10,10 @@ [status-im.chat.db :as chat.db] [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.models.transactions :as transactions] - [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] @@ -27,12 +27,13 @@ [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 @@ -125,7 +126,7 @@ (reg-root-key-sub :stickers/packs :stickers/packs) (reg-root-key-sub :stickers/installed-packs :stickers/packs-installed) (reg-root-key-sub :stickers/packs-owned :stickers/packs-owned) -(reg-root-key-sub :stickers/packs-pendning :stickers/packs-pendning) +(reg-root-key-sub :stickers/packs-pending :stickers/packs-pending) ;;mailserver (reg-root-key-sub :mailserver/current-id :mailserver/current-id) @@ -279,6 +280,18 @@ (-> selected-participants (contains? element)))) +(re-frame/reg-sub + :ethereum/chain-keyword + :<- [:network] + (fn [network] + (ethereum/network->chain-keyword network))) + +(re-frame/reg-sub + :ethereum/native-currency + :<- [:ethereum/chain-keyword] + (fn [chain-keyword] + (tokens/native-currency chain-keyword))) + ;;ACCOUNT ============================================================================================================== (re-frame/reg-sub @@ -676,18 +689,16 @@ (filter-contacts selected-contacts active-contacts))) (re-frame/reg-sub - :chats/transaction-confirmed? - :<- [:wallet-transactions] - (fn [txs [_ tx-hash]] - (-> (get-in txs [tx-hash :confirmations] "0") - (js/parseInt) - (>= transactions/confirmations-count-threshold)))) - -(re-frame/reg-sub - :chats/wallet-transaction-exists? - :<- [:wallet-transactions] - (fn [txs [_ tx-hash]] - (not (nil? (get txs tx-hash))))) + :chats/transaction-status + :<- [:wallet/transactions] + :<- [:ethereum/current-block] + (fn [[transactions current-block] [_ hash]] + (when-let [transaction (get transactions hash)] + {:exists? true + :confirmed? + (-> transaction + (wallet.db/get-confirmations current-block) + (>= transactions/confirmations-count-threshold))}))) ;;BOOTNODES ============================================================================================================ @@ -757,7 +768,7 @@ :<- [:stickers/packs] :<- [:stickers/installed-packs] :<- [:stickers/packs-owned] - :<- [:stickers/packs-pendning] + :<- [:stickers/packs-pending] (fn [[packs installed owned pending]] (map (fn [{:keys [id] :as pack}] (cond-> pack @@ -903,23 +914,11 @@ (fn [prices [_ fsym tsym]] (get-in prices [fsym tsym :last-day]))) -(re-frame/reg-sub - :wallet-transactions - :<- [:wallet] - (fn [wallet] - (get wallet :transactions))) - (re-frame/reg-sub :wallet.settings/currency :<- [:account-settings] - (fn [sett] - (or (get-in sett [:wallet :currency]) :usd))) - -(re-frame/reg-sub - :wallet.transactions/filters - :<- [:wallet.transactions] - (fn [txs] - (get txs :filters))) + (fn [settings] + (or (get-in settings [:wallet :currency]) :usd))) (re-frame/reg-sub :asset-value @@ -935,7 +934,8 @@ str (i18n/format-currency (:code currency)))))) -(defn- get-balance-total-value [balance prices currency token->decimals] +(defn- get-balance-total-value + [balance prices currency token->decimals] (reduce-kv (fn [acc symbol value] (if-let [price (get-in prices [symbol currency :price])] (+ acc (or (some-> (money/internal->formatted value symbol (token->decimals symbol)) @@ -982,11 +982,6 @@ (or (get-in wallet [:errors :balance-update]) (get-in wallet [:errors :prices-update])))) -(re-frame/reg-sub - :get-wallet-unread-messages-number - (fn [db] - 0)) - (re-frame/reg-sub :wallet/visible-tokens-symbols :<- [:network] @@ -1028,80 +1023,133 @@ ;;WALLET TRANSACTIONS ================================================================================================== (re-frame/reg-sub - :wallet.transactions/current-tab + :wallet/transactions :<- [:wallet] (fn [wallet] - (get wallet :current-tab 0))) + (get wallet :transactions))) -(defn enrich-transaction [{:keys [type to from timestamp] :as transaction} contacts] - (let [[contact-address key-contact key-wallet] (if (= type :inbound) - [from :from-contact :to-wallet] - [to :to-contact :from-wallet]) +(re-frame/reg-sub + :wallet/filters + :<- [:wallet] + (fn [wallet] + (get wallet :filters))) + +(defn enrich-transaction + [{:keys [type to from value token] :as transaction} + contacts native-currency] + (let [[contact-address key-contact key-wallet] + (if (= type :inbound) + [from :from-contact :to-wallet] + [to :to-contact :from-wallet]) wallet (i18n/label :main-wallet) - contact (get contacts (utils.hex/normalize-hex contact-address))] + contact (get contacts contact-address) + {:keys [symbol-display symbol decimals] :as asset} + (or token native-currency) + amount-text (if value + (wallet.utils/format-amount value decimals) + "...") + currency-text (when asset + (clojure.core/name (or symbol-display symbol)))] (cond-> transaction contact (assoc key-contact (:name contact)) :always (assoc key-wallet wallet - :time-formatted (datetime/timestamp->time timestamp))))) + :amount-text amount-text + :currency-text currency-text)))) (re-frame/reg-sub :wallet.transactions/transactions - :<- [:wallet] + :<- [:wallet/transactions] :<- [:contacts/contacts-by-address] - (fn [[wallet contacts]] - (reduce (fn [acc [hash transaction]] - (assoc acc hash (enrich-transaction transaction contacts))) - {} - (:transactions wallet)))) + :<- [:ethereum/native-currency] + (fn [[transactions contacts native-currency]] + (reduce-kv (fn [acc hash transaction] + (assoc acc + hash + (enrich-transaction transaction contacts native-currency))) + {} + transactions))) (re-frame/reg-sub - :wallet.transactions/grouped-transactions - :<- [:wallet.transactions/transactions] - (fn [transactions] - (group-by :type (vals transactions)))) + :wallet.transactions/all-filters? + :<- [:wallet/filters] + (fn [filters] + (= wallet.db/default-wallet-filters + filters))) -(re-frame/reg-sub - :wallet.transactions/pending-transactions-list - :<- [:wallet.transactions/grouped-transactions] - (fn [{:keys [pending]}] - (when pending - {:title "Pending" - :key :pending - :data pending}))) +(def filters-labels + {:inbound (i18n/label :t/incoming) + :outbound (i18n/label :t/outgoing) + :pending (i18n/label :t/pending) + :failed (i18n/label :t/failed)}) (re-frame/reg-sub - :wallet.transactions/failed-transactions-list - :<- [:wallet.transactions/grouped-transactions] - (fn [{:keys [failed]}] - (when failed - {:title "Failed" - :key :failed - :data failed}))) - -(defn group-transactions-by-date [transactions] + :wallet.transactions/filters + :<- [:wallet/filters] + (fn [filters] + (map (fn [id] + (let [checked? (filters id)] + {:id id + :label (filters-labels id) + :checked? checked? + :on-touch #(if checked? + (re-frame/dispatch [:wallet.transactions/remove-filter id]) + (re-frame/dispatch [:wallet.transactions/add-filter id]))})) + wallet.db/default-wallet-filters))) + +(re-frame/reg-sub + :wallet.transactions.filters/screen + :<- [:wallet.transactions/filters] + :<- [:wallet.transactions/all-filters?] + (fn [[filters all-filters?]] + {:all-filters? all-filters? + :filters filters + :on-touch-select-all (when-not all-filters? + #(re-frame/dispatch + [:wallet.transactions/add-all-filters]))})) + +(defn- enrich-transaction-for-list + [filters + {:keys [type from-contact from to-contact to hash timestamp] :as transaction}] + (when (filters type) + (assoc (case type + :inbound + (assoc transaction + :label (i18n/label :t/from) + :contact-accessibility-label :sender-text + :address-accessibility-label :sender-address-text + :contact from-contact + :address from) + (assoc transaction + :label (i18n/label :t/to) + :contact-accessibility-label :recipient-name-text + :address-accessibility-label :recipient-address-text + :contact to-contact + :address to)) + :time-formatted (datetime/timestamp->time timestamp) + :on-touch-fn #(re-frame/dispatch [:wallet.ui/show-transaction-details hash])))) + +(defn- group-transactions-by-date + [transactions] (->> transactions (group-by #(datetime/timestamp->date-key (:timestamp %))) - (sort-by key) - reverse + (sort-by key >) (map (fn [[date-key transactions]] {:title (datetime/timestamp->mini-date (:timestamp (first transactions))) :key date-key :data (sort-by :timestamp > transactions)})))) (re-frame/reg-sub - :wallet.transactions/completed-transactions-list - :<- [:wallet.transactions/grouped-transactions] - (fn [{:keys [inbound outbound failed]}] - (group-transactions-by-date (concat inbound outbound failed)))) - -(re-frame/reg-sub - :wallet.transactions/transactions-history-list - :<- [:wallet.transactions/pending-transactions-list] - :<- [:wallet.transactions/completed-transactions-list] - (fn [[pending completed]] - (cond-> [] - pending (into pending) - completed (into completed)))) + :wallet.transactions.history/screen + :<- [:wallet.transactions/transactions] + :<- [:wallet/filters] + :<- [:wallet.transactions/all-filters?] + (fn [[transactions filters all-filters?]] + {:all-filters? all-filters? + :transaction-history-sections + (->> transactions + vals + (keep #(enrich-transaction-for-list filters %)) + (group-transactions-by-date))})) (re-frame/reg-sub :wallet.transactions/current-transaction @@ -1110,19 +1158,28 @@ (:current-transaction wallet))) (re-frame/reg-sub - :wallet.transactions/transaction-details + :wallet.transactions.details/current-transaction :<- [:wallet.transactions/transactions] :<- [:wallet.transactions/current-transaction] - :<- [:network] - (fn [[transactions current-transaction network]] - (let [{:keys [gas-used gas-price hash timestamp type] :as transaction} (get transactions current-transaction) - chain (ethereum/network->chain-keyword network) - native-currency (tokens/native-currency chain) - display-unit (wallet.utils/display-symbol native-currency)] + :<- [:ethereum/native-currency] + :<- [:ethereum/chain-keyword] + (fn [[transactions current-transaction native-currency chain-keyword]] + (let [{:keys [gas-used gas-price hash timestamp type token value] + :as transaction} + (get transactions current-transaction) + native-currency-text (name (or (:symbol-display native-currency) + (:symbol native-currency)))] (when transaction (merge transaction - {:gas-price-eth (if gas-price (money/wei->str :eth gas-price display-unit) "-") - :gas-price-gwei (if gas-price (money/wei->str :gwei gas-price) "-") + {:gas-price-eth (if gas-price + (money/wei->str :eth + gas-price + native-currency-text) + "-") + :gas-price-gwei (if gas-price + (money/wei->str :gwei + gas-price) + "-") :date (datetime/timestamp->long-date timestamp)} (if (= type :unsigned) {:block (i18n/label :not-applicable) @@ -1132,26 +1189,27 @@ :nonce (i18n/label :not-applicable) :hash (i18n/label :not-applicable)} {:cost (when gas-used - (money/wei->str :eth (money/fee-value gas-used gas-price) display-unit)) - :url (transactions/get-transaction-details-url chain hash)})))))) + (money/wei->str :eth + (money/fee-value gas-used gas-price) + native-currency-text)) + :url (transactions.etherscan/get-transaction-details-url + chain-keyword + hash)})))))) (re-frame/reg-sub - :wallet.transactions.details/confirmations + :wallet.transactions.details/screen + :<- [:wallet.transactions.details/current-transaction] :<- [:ethereum/current-block] - :<- [:wallet.transactions/transaction-details] - (fn [[current-block {:keys [block]}]] - (if (and current-block block) - (- current-block block) - 0))) - -(re-frame/reg-sub - :wallet.transactions.details/confirmations-progress - :<- [:wallet.transactions.details/confirmations] - (fn [confirmations] - (let [max-confirmations 10] - (if (>= confirmations max-confirmations) - 100 - (* 100 (/ confirmations max-confirmations)))))) + (fn [[transaction current-block]] + :wallet.transactions.details/current-transaction + (let [confirmations (wallet.db/get-confirmations transaction + current-block)] + (assoc transaction + :confirmations confirmations + :confirmations-progress + (if (>= confirmations transactions/confirmations-count-threshold) + 100 + (* 100 (/ confirmations transactions/confirmations-count-threshold))))))) ;;WALLET SEND ========================================================================================================== @@ -1196,12 +1254,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))))) @@ -1214,15 +1272,17 @@ send-transaction edit))) -(defn check-sufficient-funds [transaction balance symbol amount] +(defn check-sufficient-funds + [transaction balance symbol amount] (assoc transaction :sufficient-funds? (or (nil? amount) (money/sufficient-funds? amount (get balance symbol))))) -(defn check-sufficient-gas [transaction balance symbol amount] +(defn check-sufficient-gas + [transaction balance symbol amount] (assoc transaction :sufficient-gas? (or (nil? amount) - (let [available-ether (get balance :ETH (money/bignumber 0)) + (let [available-ether (money/bignumber (get balance :ETH 0)) available-for-gas (if (= :ETH symbol) (.minus available-ether (money/bignumber amount)) available-ether)] @@ -1238,7 +1298,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)))) diff --git a/src/status_im/tribute_to_talk/core.cljs b/src/status_im/tribute_to_talk/core.cljs index b33e26deddf..cf8bd80cfc7 100644 --- a/src/status_im/tribute_to_talk/core.cljs +++ b/src/status_im/tribute_to_talk/core.cljs @@ -4,13 +4,15 @@ [re-frame.core :as re-frame] [status-im.accounts.update.core :as accounts.update] [status-im.contact.db :as contact.db] + [status-im.ethereum.contracts :as contracts] + [status-im.ethereum.json-rpc :as json-rpc] [status-im.ipfs.core :as ipfs] [status-im.tribute-to-talk.db :as tribute-to-talk.db] [status-im.ui.screens.navigation :as navigation] [status-im.utils.contenthash :as contenthash] - [status-im.utils.ethereum.contracts :as contracts] [status-im.utils.ethereum.core :as ethereum] [status-im.utils.fx :as fx] + [status-im.wallet.core :as wallet] [taoensso.timbre :as log])) (fx/defn update-settings @@ -195,25 +197,35 @@ [:tribute-to-talk.callback/fetch-manifest-success identity manifest])))})) +(re-frame/reg-fx + :tribute-to-talk/get-manifest + (fn [{:keys [contract address on-success]}] + (json-rpc/eth-call + {:contract contract + :method "getManifest(address)" + :params [address] + :outputs ["bytes"] + :on-success on-success}))) + (fx/defn check-manifest - [{:keys [db] :as cofx} identity] - (or (contracts/call cofx - {:contract :status/tribute-to-talk - :method :get-manifest - :params [(contact.db/public-key->address identity)] - :return-params ["bytes"] - :callback - #(re-frame/dispatch - (if-let [contenthash (first %)] - [:tribute-to-talk.callback/check-manifest-success - identity - contenthash] - [:tribute-to-talk.callback/no-manifest-found identity]))}) - ;; `contracts/call` returns nil if there is no contract for the current network - ;; update settings if checking own manifest or do nothing otherwise - (when-let [me? (= identity - (get-in cofx [:db :account/account :public-key]))] - (update-settings cofx nil)))) + [{:keys [db] :as cofx} public-key] + (if-let [contract (contracts/get-address db :status/tribute-to-talk)] + (let [address (contact.db/public-key->address public-key)] + {:tribute-to-talk/get-manifest + {:contract contract + :address address + :on-success + (fn [[contenthash]] + (re-frame/dispatch + (if contenthash + [:tribute-to-talk.callback/check-manifest-success + public-key + contenthash] + [:tribute-to-talk.callback/no-manifest-found public-key])))}}) + ;; update settings if checking own manifest or do nothing otherwise + (when-let [me? (= identity + (get-in cofx [:db :account/account :public-key]))] + (update-settings cofx nil)))) (fx/defn check-own-manifest [cofx] @@ -224,13 +236,17 @@ (let [contenthash (when hash (contenthash/encode {:hash hash :namespace :ipfs}))] - (or (contracts/call cofx - {:contract :status/tribute-to-talk - :method :set-manifest - :params [contenthash] - :on-result [:tribute-to-talk.callback/set-manifest-transaction-completed] - :on-error [:tribute-to-talk.callback/set-manifest-transaction-failed]}) - {:db (assoc-in db [:navigation/screen-params :tribute-to-talk :state] :transaction-failed)}))) + (if-let [contract (contracts/get-address db :status/tribute-to-talk)] + (wallet/eth-transaction-call + cofx + {:contract contract + :method "setManifest(bytes)" + :params [contenthash] + :on-result [:tribute-to-talk.callback/set-manifest-transaction-completed] + :on-error [:tribute-to-talk.callback/set-manifest-transaction-failed]}) + {:db (assoc-in db + [:navigation/screen-params :tribute-to-talk :state] + :transaction-failed)}))) (defn remove [{:keys [db] :as cofx}] diff --git a/src/status_im/ui/components/bottom_bar/core.cljs b/src/status_im/ui/components/bottom_bar/core.cljs index 2c2e4f0b635..8db2c0a87ea 100644 --- a/src/status_im/ui/components/bottom_bar/core.cljs +++ b/src/status_im/ui/components/bottom_bar/core.cljs @@ -43,7 +43,6 @@ {:nav-stack :wallet-stack :content {:title (i18n/label :t/wallet) :icon :main-icons/wallet} - :count-subscription :get-wallet-unread-messages-number :accessibility-label :wallet-tab-button}) {:nav-stack :profile-stack :content {:title (i18n/label :t/profile) diff --git a/src/status_im/ui/screens/add_new/new_chat/events.cljs b/src/status_im/ui/screens/add_new/new_chat/events.cljs index 2c0c869b5d2..c2c4edc6c4f 100644 --- a/src/status_im/ui/screens/add_new/new_chat/events.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/events.cljs @@ -14,15 +14,14 @@ (handlers/register-handler-fx :new-chat/set-new-identity - (fn [{{:keys [network network-status] :as db} :db} [_ new-identity]] + (fn [{{:keys [network-status] :as db} :db} [_ new-identity]] (let [is-public-key? (and (string? new-identity) (string/starts-with? new-identity "0x"))] (merge {:db (assoc db :contacts/new-identity new-identity :contacts/new-identity-error (db/validate-pub-key db new-identity))} (when-not is-public-key? - (let [network (get-in db [:account/account :networks network]) - chain (ethereum/network->chain-keyword network)] + (let [chain (ethereum/chain-keyword db)] {:resolve-public-key {:registry (get ens/ens-registries chain) :ens-name (if (ens/is-valid-eth-name? new-identity) new-identity diff --git a/src/status_im/ui/screens/chat/stickers/views.cljs b/src/status_im/ui/screens/chat/stickers/views.cljs index 80221720086..b261ab8cfed 100644 --- a/src/status_im/ui/screens/chat/stickers/views.cljs +++ b/src/status_im/ui/screens/chat/stickers/views.cljs @@ -93,7 +93,8 @@ [stickers-panel (map #(assoc % :pack id) stickers) window-width])])) (defn pack-icon [{:keys [id on-press background-color] - :or {on-press #(re-frame/dispatch [:stickers/select-pack id])}} icon] + :or {on-press #(re-frame/dispatch [:stickers/select-pack id])}} + icon] [react/touchable-highlight {:on-press on-press} [react/view {:style {:align-items :center}} [react/view {:style (styles/pack-icon background-color icon-size icon-horizontal-margin)} @@ -148,7 +149,8 @@ [vector-icons/icon :stickers-icons/recent {:color colors/gray}]] (for [{:keys [id thumbnail]} installed-packs] ^{:key id} - [pack-icon {:id id} + [pack-icon {:id id + :background-color colors/white} [react/image {:style {:width icon-size :height icon-size :border-radius (/ icon-size 2)} :source {:uri thumbnail}}]])] [scroll-indicator]]]]])) diff --git a/src/status_im/ui/screens/currency_settings/models.cljs b/src/status_im/ui/screens/currency_settings/models.cljs index feed815f5da..2bf4462eb14 100644 --- a/src/status_im/ui/screens/currency_settings/models.cljs +++ b/src/status_im/ui/screens/currency_settings/models.cljs @@ -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] @@ -9,4 +9,4 @@ new-settings (assoc-in settings [:wallet :currency] currency)] (fx/merge cofx (accounts.update/update-settings new-settings {}) - (wallet/update-wallet)))) + (wallet/update-prices)))) diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 13406e088ca..c2c0041d5ba 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -16,7 +16,8 @@ status-im.browser.db status-im.ui.screens.add-new.db status-im.ui.screens.add-new.new-public-chat.db - status-im.ui.components.bottom-sheet.core)) + status-im.ui.components.bottom-sheet.core + [status-im.wallet.db :as wallet.db])) ;; initial state of app-db (def app-db {:keyboard-height 0 @@ -32,8 +33,7 @@ :selected-participants #{} :sync-state :done :app-state "active" - :wallet.transactions constants/default-wallet-transactions - :wallet-selected-asset {} + :wallet wallet.db/default-wallet :wallet/all-tokens {} :prices {} :peers-count 0 @@ -61,7 +61,7 @@ :push-notifications/stored {} :registry {} :stickers/packs-owned #{} - :stickers/packs-pendning #{} + :stickers/packs-pending #{} :hardwallet {:nfc-supported? false :nfc-enabled? false :pin {:original [] @@ -101,7 +101,6 @@ ;; contents of eth_syncing or `nil` if the node isn't syncing now (spec/def :node/chain-sync-state (spec/nilable map?)) -(spec/def :node/latest-block-number (spec/nilable number?)) ;;;;NAVIGATION @@ -193,7 +192,7 @@ (spec/def :stickers/packs (spec/nilable map?)) (spec/def :stickers/packs-owned (spec/nilable set?)) -(spec/def :stickers/packs-pendning (spec/nilable set?)) +(spec/def :stickers/packs-pending (spec/nilable set?)) (spec/def :stickers/packs-installed (spec/nilable map?)) (spec/def :stickers/selected-pack (spec/nilable any?)) (spec/def :stickers/recent (spec/nilable vector?)) @@ -234,7 +233,6 @@ :node/restart? :node/address :node/chain-sync-state - :node/latest-block-number :universal-links/url :push-notifications/stored :browser/browsers @@ -271,7 +269,7 @@ :stickers/selected-pack :stickers/recent :stickers/packs-owned - :stickers/packs-pendning + :stickers/packs-pending :bottom-sheet/show? :bottom-sheet/view :bottom-sheet/options @@ -330,8 +328,6 @@ :chat/id->command :chat/access-scope->command-id :wallet/wallet - :wallet/wallet.transactions - :wallet/wallet-selected-asset :prices/prices :prices/prices-loading? :notifications/notifications diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index ae3a3f87a94..4dcc391c3e4 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -10,31 +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.transactions.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 %)) diff --git a/src/status_im/ui/screens/wallet/choose_recipient/events.cljs b/src/status_im/ui/screens/wallet/choose_recipient/events.cljs index 72bd021dcb9..1b32c54734b 100644 --- a/src/status_im/ui/screens/wallet/choose_recipient/events.cljs +++ b/src/status_im/ui/screens/wallet/choose_recipient/events.cljs @@ -51,9 +51,9 @@ ;; NOTE(janherich) - whenever changing assets, we want to clear the previusly set amount/amount-text (defn changed-asset [{:keys [db] :as fx} old-symbol new-symbol] (-> fx - (merge {:update-gas-price {:web3 (:web3 db) - :success-event :wallet/update-gas-price-success - :edit? false}}) + (merge {:wallet/update-gas-price + {:success-event :wallet/update-gas-price-success + :edit? false}}) (assoc-in [:db :wallet :send-transaction :amount] nil) (assoc-in [:db :wallet :send-transaction :amount-text] nil) (assoc-in [:db :wallet :send-transaction :asset-error] diff --git a/src/status_im/ui/screens/wallet/events.cljs b/src/status_im/ui/screens/wallet/events.cljs deleted file mode 100644 index 3fd74858b37..00000000000 --- a/src/status_im/ui/screens/wallet/events.cljs +++ /dev/null @@ -1,248 +0,0 @@ -(ns status-im.ui.screens.wallet.events - (:require [re-frame.core :as re-frame] - [status-im.models.transactions :as wallet.transactions] - [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.handlers :as handlers] - [status-im.utils.money :as money] - [status-im.utils.prices :as prices] - [taoensso.timbre :as log] - [status-im.utils.fx :as fx] - [status-im.i18n :as i18n] - [status-im.utils.utils :as utils.utils])) - -(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-transactions - (fn [{:keys [db]} _] - {::wallet.transactions/sync-transactions-now - (select-keys db [:network-status :account/account :wallet/all-tokens - :app-state :network :web3])})) - -(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)))) diff --git a/src/status_im/ui/screens/wallet/main/views.cljs b/src/status_im/ui/screens/wallet/main/views.cljs index f278e83ac2c..e4e7d02109f 100644 --- a/src/status_im/ui/screens/wallet/main/views.cljs +++ b/src/status_im/ui/screens/wallet/main/views.cljs @@ -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 diff --git a/src/status_im/ui/screens/wallet/navigation.cljs b/src/status_im/ui/screens/wallet/navigation.cljs index 90d25270335..ea606f14bb1 100644 --- a/src/status_im/ui/screens/wallet/navigation.cljs +++ b/src/status_im/ui/screens/wallet/navigation.cljs @@ -1,34 +1,8 @@ (ns status-im.ui.screens.wallet.navigation (:require [re-frame.core :as re-frame] - [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) - (assoc-in db [:wallet :current-tab] 0)) - -(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) - (assoc-in db [:wallet :current-tab] 0)) - -(defmethod navigation/preload-data! :transactions-history - [db _] - (re-frame/dispatch [:update-transactions]) - db) + [status-im.ui.screens.navigation :as navigation] + [status-im.utils.ethereum.core :as ethereum])) (def transaction-send-default (let [symbol :ETH] diff --git a/src/status_im/ui/screens/wallet/onboarding/views.cljs b/src/status_im/ui/screens/wallet/onboarding/views.cljs index 913b47a8d5e..181473d3230 100644 --- a/src/status_im/ui/screens/wallet/onboarding/views.cljs +++ b/src/status_im/ui/screens/wallet/onboarding/views.cljs @@ -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] diff --git a/src/status_im/ui/screens/wallet/request/events.cljs b/src/status_im/ui/screens/wallet/request/events.cljs index a8329d176e1..8bd06811276 100644 --- a/src/status_im/ui/screens/wallet/request/events.cljs +++ b/src/status_im/ui/screens/wallet/request/events.cljs @@ -1,12 +1,10 @@ (ns status-im.ui.screens.wallet.request.events - (:require [re-frame.core :as re-frame] - [status-im.ui.screens.wallet.db :as wallet-db] - [status-im.ui.screens.navigation :as navigation] - [status-im.utils.handlers :as handlers] + (:require [status-im.chat.commands.sending :as commands-sending] [status-im.chat.models :as chat-model] - [status-im.chat.commands.sending :as commands-sending] [status-im.utils.fx :as fx] - [status-im.utils.money :as money])) + [status-im.utils.handlers :as handlers] + [status-im.utils.money :as money] + [status-im.wallet.db :as wallet.db])) (handlers/register-handler-fx :wallet-send-request @@ -23,7 +21,7 @@ (handlers/register-handler-fx :wallet.request/set-and-validate-amount (fn [{:keys [db]} [_ amount symbol decimals]] - (let [{:keys [value error]} (wallet-db/parse-amount amount decimals)] + (let [{:keys [value error]} (wallet.db/parse-amount amount decimals)] {:db (-> db (assoc-in [:wallet :request-transaction :amount] (money/formatted->internal value symbol decimals)) (assoc-in [:wallet :request-transaction :amount-text] amount) diff --git a/src/status_im/ui/screens/wallet/send/events.cljs b/src/status_im/ui/screens/wallet/send/events.cljs index f8bf32cfeee..cec87137a7c 100644 --- a/src/status_im/ui/screens/wallet/send/events.cljs +++ b/src/status_im/ui/screens/wallet/send/events.cljs @@ -1,17 +1,13 @@ (ns status-im.ui.screens.wallet.send.events (:require [re-frame.core :as re-frame] [status-im.chat.commands.sending :as commands-sending] - [status-im.chat.models.message :as models.message] - [status-im.chat.models :as chat.models] [status-im.constants :as constants] [status-im.i18n :as i18n] - [status-im.models.transactions :as wallet.transactions] - [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.ui.screens.wallet.db :as wallet.db] + [status-im.utils.ethereum.abi-spec :as abi-spec] [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] @@ -19,9 +15,8 @@ [status-im.utils.security :as security] [status-im.utils.types :as types] [status-im.utils.utils :as utils] - [status-im.utils.config :as config] - [status-im.transport.utils :as transport.utils] - [status-im.hardwallet.core :as hardwallet])) + [status-im.wallet.core :as wallet] + [status-im.wallet.db :as wallet.db])) ;;;; FX @@ -30,9 +25,20 @@ (security/safe-unmask-data masked-password) on-completed)) -(defn- send-tokens [all-tokens symbol chain {:keys [from to value gas gasPrice]} on-completed masked-password] +(defn- send-tokens + [all-tokens symbol chain + {:keys [from to value gas gasPrice]} on-completed masked-password] (let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))] - (erc20/transfer contract from to value gas gasPrice masked-password on-completed))) + (status/send-transaction (types/clj->json + {:to contract + :from from + :data (abi-spec/encode + "transfer(address,uint256)" + [to value]) + :gas gas + :gasPrice gasPrice}) + (security/safe-unmask-data masked-password) + on-completed))) (re-frame/reg-fx ::send-transaction @@ -64,14 +70,15 @@ (handlers/register-handler-fx :wallet/send-transaction (fn [{{:keys [chain] :as db} :db} _] - (let [{:keys [password symbol in-progress?] :as transaction} (get-in db [:wallet :send-transaction]) + (let [{:keys [password symbol in-progress?] :as transaction} + (get-in db [:wallet :send-transaction]) all-tokens (:wallet/all-tokens db) - from (get-in db [:account/account :address])] + from (ethereum/current-address db)] (when-not in-progress? {: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 @@ -121,7 +128,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 @@ -129,7 +136,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) @@ -173,7 +180,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 @@ -191,19 +198,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 @@ -224,13 +231,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 #" " " ")) @@ -264,13 +271,13 @@ (fn [cofx _] (fx/merge cofx (navigation/navigate-back) - (models.wallet/discard-transaction)))) + (wallet/discard-transaction)))) (defn update-gas-price ([db edit? success-event] - {:update-gas-price {:web3 (:web3 db) - :success-event (or success-event :wallet/update-gas-price-success) - :edit? edit?}}) + {:wallet/update-gas-price + {:success-event (or success-event :wallet/update-gas-price-success) + :edit? edit?}}) ([db edit?] (update-gas-price db edit? :wallet/update-gas-price-success)) ([db] (update-gas-price db false :wallet/update-gas-price-success))) @@ -316,7 +323,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 @@ -338,7 +345,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) @@ -369,11 +376,12 @@ (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)] - (merge (ethereum/call-params contract "transfer(address,uint256)" to value) - {:from from + {:keys [gas gasPrice to from value]} (wallet/prepare-send-transaction from transaction)] + (merge (abi-spec/encode "transfer(address,uint256)" [to value]) + {:to contract + :from from :gas gas :gasPrice gasPrice})))) diff --git a/src/status_im/ui/screens/wallet/settings/events.cljs b/src/status_im/ui/screens/wallet/settings/events.cljs deleted file mode 100644 index 636fe3f244e..00000000000 --- a/src/status_im/ui/screens/wallet/settings/events.cljs +++ /dev/null @@ -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))) diff --git a/src/status_im/ui/screens/wallet/settings/models.cljs b/src/status_im/ui/screens/wallet/settings/models.cljs deleted file mode 100644 index adc841190c1..00000000000 --- a/src/status_im/ui/screens/wallet/settings/models.cljs +++ /dev/null @@ -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 %]))}))))) diff --git a/src/status_im/ui/screens/wallet/settings/views.cljs b/src/status_im/ui/screens/wallet/settings/views.cljs index 351043d74c5..389c60e7e7a 100644 --- a/src/status_im/ui/screens/wallet/settings/views.cljs +++ b/src/status_im/ui/screens/wallet/settings/views.cljs @@ -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} diff --git a/src/status_im/ui/screens/wallet/transactions/events.cljs b/src/status_im/ui/screens/wallet/transactions/events.cljs deleted file mode 100644 index e35cca3593c..00000000000 --- a/src/status_im/ui/screens/wallet/transactions/events.cljs +++ /dev/null @@ -1,21 +0,0 @@ -(ns status-im.ui.screens.wallet.transactions.events - (:require [status-im.utils.handlers :as handlers])) - -(defn- mark-all-checked [filters] - (update filters :type #(map (fn [m] (assoc m :checked? true)) %))) - -(defn- mark-checked [filters {:keys [type]} checked?] - (update filters :type #(map (fn [{:keys [id] :as m}] (if (= type id) (assoc m :checked? checked?) m)) %))) - -(defn- update-filters [db f] - (update-in db [:wallet.transactions :filters] f)) - -(handlers/register-handler-fx - :wallet.transactions/filter - (fn [{:keys [db]} [_ path checked?]] - {:db (update-filters db #(mark-checked % path checked?))})) - -(handlers/register-handler-fx - :wallet.transactions/filter-all - (fn [{:keys [db]} _] - {:db (update-filters db mark-all-checked)})) diff --git a/src/status_im/ui/screens/wallet/transactions/views.cljs b/src/status_im/ui/screens/wallet/transactions/views.cljs index c668a93c309..dc1aeba6bcc 100644 --- a/src/status_im/ui/screens/wallet/transactions/views.cljs +++ b/src/status_im/ui/screens/wallet/transactions/views.cljs @@ -1,196 +1,187 @@ (ns status-im.ui.screens.wallet.transactions.views - (: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.list.views :as list] [status-im.ui.components.react :as react] [status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.components.styles :as components.styles] - [status-im.ui.components.colors :as colors] [status-im.ui.components.toolbar.actions :as actions] [status-im.ui.components.toolbar.view :as toolbar] - [status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.screens.wallet.transactions.styles :as styles] - [status-im.utils.money :as money] - [status-im.utils.ethereum.tokens :as tokens] - [status-im.utils.ethereum.core :as ethereum] [status-im.ui.screens.wallet.utils :as wallet.utils] - [status-im.utils.utils :as utils])) + [status-im.utils.ethereum.core :as ethereum] + [status-im.utils.ethereum.tokens :as tokens] + [status-im.utils.money :as money]) + (:require-macros [status-im.utils.views :refer [defview letsubs]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TRANSACTION HISTORY +;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn history-action [filter?] +(defn history-action + [all-filters?] (cond-> {:icon :main-icons/filter :icon-opts {:accessibility-label :filters-button} :handler #(re-frame/dispatch [:navigate-to :wallet-transactions-filter])} - filter? (assoc-in [:icon-opts :overlay-style] styles/corner-dot))) - -(defn- all-checked? [filter-data] - (and (every? :checked? (:type filter-data)) - (every? :checked? (:tokens filter-data)))) + (not all-filters?) (assoc-in [:icon-opts :overlay-style] styles/corner-dot))) -(defn- toolbar-view [filter-data] +(defn- toolbar-view + [all-filters?] [toolbar/toolbar nil toolbar/default-nav-back [toolbar/content-title (i18n/label :t/transactions-history)] [toolbar/actions - [(history-action (not (all-checked? filter-data)))]]]) + [(history-action all-filters?)]]]) -(defn- inbound? [type] (= :inbound type)) -(defn- failed? [type] (= :failed type)) - -(defn- transaction-icon [k background-color color] - {:icon k +(defn- transaction-icon + [icon-key background-color color] + {:icon icon-key :icon-opts {:color color} :style (styles/transaction-icon-background background-color)}) -(defn- transaction-type->icon [k] +(defn- transaction-type->icon + [k] (case k - :inbound (transaction-icon :main-icons/arrow-left (colors/alpha colors/green 0.2) colors/green) - :outbound (transaction-icon :main-icons/arrow-right (colors/alpha colors/blue 0.1) colors/blue) - :failed (transaction-icon :main-icons/warning colors/gray-light colors/red) - (:postponed :pending) (transaction-icon :main-icons/arrow-right colors/gray-light colors/gray) + :inbound (transaction-icon :main-icons/arrow-left + (colors/alpha colors/green 0.2) + colors/green) + :outbound (transaction-icon :main-icons/arrow-right + (colors/alpha colors/blue 0.1) + colors/blue) + :failed (transaction-icon :main-icons/warning + colors/gray-light + colors/red) + :pending (transaction-icon :main-icons/arrow-right + colors/gray-light colors/gray) (throw (str "Unknown transaction type: " k)))) -(defn render-transaction [{:keys [hash from-contact to-contact to from type value time-formatted symbol]} - network all-tokens hide-details?] - (let [[label contact address - contact-accessibility-label - address-accessibility-label] (if (inbound? type) - [(i18n/label :t/from) from-contact from :sender-text :sender-address-text] - [(i18n/label :t/to) to-contact to :recipient-name-text :recipient-address-text]) - {:keys [decimals] :as token} (tokens/asset-for all-tokens (ethereum/network->chain-keyword network) symbol)] - [list/touchable-item #(when-not hide-details? (re-frame/dispatch [:show-transaction-details hash])) - [react/view {:accessibility-label :transaction-item} - [list/item - (when type - [list/item-icon (transaction-type->icon (keyword type))]) - [list/item-content - [react/view {:style styles/amount-time} - [react/nested-text {:style styles/tx-amount - :ellipsize-mode "tail" - :number-of-lines 1} - [{:accessibility-label :amount-text} - (-> value (money/internal->formatted symbol decimals) money/to-fixed str)] - " " - [{:accessibility-label :currency-text} - (wallet.utils/display-symbol token)]] - [react/text {:style styles/tx-time} - time-formatted]] - [react/view {:style styles/address-row} - [react/text {:style styles/address-label} - label] - (when contact - [react/text {:style styles/address-contact - :accessibility-label contact-accessibility-label} - contact]) - [react/text {:style styles/address-hash - :ellipsize-mode "middle" - :number-of-lines 1 - :accessibility-label address-accessibility-label} - address]]] - (when-not hide-details? - [list/item-icon {:icon :main-icons/next - :style {:margin-top 10} - :icon-opts (merge styles/forward - {:accessibility-label :show-transaction-button})}])]]])) - -(defn filtered-transaction? [transaction filter-data] - (:checked? (some #(when (= (:type transaction) (:id %)) %) (:type filter-data)))) +(defn render-transaction + [{:keys [label contact address contact-accessibility-label + address-accessibility-label currency-text amount-text + time-formatted on-touch-fn type]}] + [list/touchable-item on-touch-fn + [react/view {:accessibility-label :transaction-item} + [list/item + (when type + [list/item-icon (transaction-type->icon (keyword type))]) + [list/item-content + [react/view {:style styles/amount-time} + [react/nested-text {:style styles/tx-amount + :ellipsize-mode "tail" + :number-of-lines 1} + [{:accessibility-label :amount-text} + amount-text] + " " + [{:accessibility-label :currency-text} + currency-text]] + [react/text {:style styles/tx-time} + time-formatted]] + [react/view {:style styles/address-row} + [react/text {:style styles/address-label} + label] + (when contact + [react/text {:style styles/address-contact + :accessibility-label contact-accessibility-label} + contact]) + [react/text {:style styles/address-hash + :ellipsize-mode "middle" + :number-of-lines 1 + :accessibility-label address-accessibility-label} + address]]] + [list/item-icon {:icon :main-icons/next + :style {:margin-top 10} + :icon-opts (merge styles/forward + {:accessibility-label :show-transaction-button})}]]]]) -(defn update-transactions [m filter-data] - (update m :data (fn [v] (filter #(filtered-transaction? % filter-data) v)))) +(defn history-list + [transactions-history-sections] + [react/view components.styles/flex + [list/section-list {:sections transactions-history-sections + :key-fn :hash + :render-fn #(render-transaction %) + :empty-component + [react/i18n-text {:style styles/empty-text + :key :transactions-history-empty}] + :refreshing false}]]) -(defview history-list [& [hide-details?]] - (letsubs [transactions-history-list [:wallet.transactions/transactions-history-list] - filter-data [:wallet.transactions/filters] - network [:account/network] - all-tokens [:wallet/all-tokens]] - [react/view components.styles/flex - [list/section-list {:sections (map #(update-transactions % filter-data) transactions-history-list) - :key-fn :hash - :render-fn #(render-transaction % network all-tokens hide-details?) - :empty-component [react/i18n-text {:style styles/empty-text - :key :transactions-history-empty}] - :on-refresh #(re-frame/dispatch [:update-transactions]) - :refreshing false}]])) +(defview transactions + [] + (letsubs [{:keys [transaction-history-sections all-filters?]} + [:wallet.transactions.history/screen]] + [react/view styles/transactions-view + [status-bar/status-bar] + [toolbar-view all-filters?] + [history-list transaction-history-sections]])) -;; Filter history +;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TRANSACTION FILTERS +;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- item-filter [{:keys [icon checked? path]} content] +(defn- render-item-filter [{:keys [id label checked? on-touch]}] [react/view {:accessibility-label :filter-item} [list/list-item-with-checkbox {:checked? checked? - :on-value-change #(re-frame/dispatch [:wallet.transactions/filter path %])} + :on-value-change on-touch} [list/item - [list/item-icon icon] - content]]]) - -(defn- render-item-filter [{:keys [id label checked?]}] - (when id - [item-filter {:icon (transaction-type->icon id) :checked? checked? :path {:type id}} + [list/item-icon (transaction-type->icon id)] [list/item-content [list/item-primary-only {:accessibility-label :filter-name-text} - label]]])) - -(defn- wrap-filter-data [m] - [{:title (i18n/label :t/transactions-filter-type) - :key :type - :render-fn render-item-filter - :data (:type m)}]) + label]]]]]) (defview filter-history [] - (letsubs [filter-data [:wallet.transactions/filters]] + (letsubs [{:keys [filters all-filters? on-touch-select-all]} + [:wallet.transactions.filters/screen]] [react/view styles/filter-container [status-bar/status-bar {:type :modal-main}] [toolbar/toolbar {} - [toolbar/nav-clear-text {:accessibility-label :done-button} (i18n/label :t/done)] - [toolbar/content-title (i18n/label :t/transactions-filter-title)] - [toolbar/text-action {:handler #(re-frame/dispatch [:wallet.transactions/filter-all]) - :disabled? (all-checked? filter-data) - :accessibility-label :select-all-button} + [toolbar/nav-clear-text + {:accessibility-label :done-button} + (i18n/label :t/done)] + [toolbar/content-title + (i18n/label :t/transactions-filter-title)] + [toolbar/text-action + {:handler on-touch-select-all + :disabled? all-filters? + :accessibility-label :select-all-button} (i18n/label :t/transactions-filter-select-all)]] - [react/view {:style (merge {:background-color :white} components.styles/flex)} - [list/section-list {:sections (wrap-filter-data filter-data) - :key-fn (comp str :id)}]]])) + [react/view + {:style (merge {:background-color :white} components.styles/flex)} + [list/section-list {:sections [{:title + (i18n/label :t/transactions-filter-type) + :key :type + :render-fn render-item-filter + :data filters}] + :key-fn :id}]]])) -(defview transactions [] - (letsubs [filter-data [:wallet.transactions/filters]] - [react/view styles/transactions-view - [status-bar/status-bar] - [toolbar-view filter-data] - [history-list]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TRANSACTION DETAILS +;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- pretty-print-asset [symbol amount token] - (if amount - (if (= :ETH symbol) - (->> amount (money/wei-> :eth) money/to-fixed str) - (-> amount (money/token->unit (:decimals token)) money/to-fixed str)) - "...")) - -(defn details-header [network all-tokens {:keys [value date type symbol token]}] - (let [asset (tokens/asset-for all-tokens (ethereum/network->chain-keyword network) symbol)] - [react/view {:style styles/details-header} - [react/view {:style styles/details-header-icon} - (when type - [list/item-icon (transaction-type->icon type)])] - [react/view {:style styles/details-header-infos} - [react/nested-text {:style styles/details-header-value} - [{:accessibility-label :amount-text} - (pretty-print-asset symbol value token)] - " " - [{:accessibility-label :currency-text} - (wallet.utils/display-symbol asset)]] - [react/text {:style styles/details-header-date} date]]])) +(defn details-header + [date type amount-text currency-text] + [react/view {:style styles/details-header} + [react/view {:style styles/details-header-icon} + (when type + [list/item-icon (transaction-type->icon type)])] + [react/view {:style styles/details-header-infos} + [react/nested-text {:style styles/details-header-value} + [{:accessibility-label :amount-text} amount-text] + " " + [{:accessibility-label :currency-text} currency-text]] + [react/text {:style styles/details-header-date} date]]]) (defn progress-bar [progress failed?] [react/view {:style styles/progress-bar} [react/view {:style (styles/progress-bar-done progress failed?)}] [react/view {:style (styles/progress-bar-todo (- 100 progress) failed?)}]]) -(defn details-confirmations [confirmations confirmations-progress type] +(defn details-confirmations + [confirmations confirmations-progress failed?] [react/view {:style styles/details-block} - [progress-bar confirmations-progress (failed? type)] - (if (failed? type) + [progress-bar confirmations-progress failed?] + (if failed? [react/i18n-text {:style styles/details-failed :key :failed}] [react/text {:style styles/details-confirmations-count} @@ -216,10 +207,12 @@ [react/text (merge {:style styles/details-item-extra-value} extra-props) (str extra-value)]]]))) -(defn details-list [{:keys [block hash - from from-wallet from-contact - to to-wallet to-contact - gas-limit gas-price-gwei gas-price-eth gas-used cost nonce data]}] +(defn details-list + [{:keys [block hash + from from-wallet from-contact + to to-wallet to-contact + gas-limit gas-price-gwei gas-price-eth gas-used + cost nonce data]}] [react/view {:style styles/details-block} [details-list-row :t/block block] [details-list-row :t/hash hash] @@ -243,15 +236,16 @@ [details-list-row :t/data data]]) (defn details-action [hash url] - [(actions/opts [{:label (i18n/label :t/copy-transaction-hash) :action #(react/copy-to-clipboard hash)} - {:label (i18n/label :t/open-on-etherscan) :action #(.openURL react/linking url)}])]) + [(actions/opts [{:label (i18n/label :t/copy-transaction-hash) + :action #(react/copy-to-clipboard hash)} + {:label (i18n/label :t/open-on-etherscan) + :action #(.openURL react/linking url)}])]) (defview transaction-details [] - (letsubs [{:keys [hash url type] :as transaction} [:wallet.transactions/transaction-details] - confirmations [:wallet.transactions.details/confirmations] - confirmations-progress [:wallet.transactions.details/confirmations-progress] - network [:account/network] - all-tokens [:wallet/all-tokens]] + (letsubs [{:keys [hash url type confirmations confirmations-progress + date amount-text currency-text] + :as transaction} + [:wallet.transactions.details/screen]] [react/view {:style components.styles/flex} [status-bar/status-bar] [toolbar/toolbar {} @@ -259,8 +253,7 @@ [toolbar/content-title (i18n/label :t/transaction-details)] (when transaction [toolbar/actions (details-action hash url)])] [react/scroll-view {:style components.styles/main-container} - [details-header network all-tokens transaction] - [details-confirmations confirmations confirmations-progress type] + [details-header date type amount-text currency-text] + [details-confirmations confirmations confirmations-progress (= :failed type)] [react/view {:style styles/details-separator}] [details-list transaction]]])) - diff --git a/src/status_im/utils/ethereum/abi_spec.cljs b/src/status_im/utils/ethereum/abi_spec.cljs index fd671a1015d..2fe587909ea 100644 --- a/src/status_im/utils/ethereum/abi_spec.cljs +++ b/src/status_im/utils/ethereum/abi_spec.cljs @@ -28,6 +28,9 @@ (when x (subs (.fromUtf8 utils x) 2))) +(defn hex-to-boolean [x] + (= x "0x0")) + (defn bytes-to-hex [x] (when x (subs (.bytesToHex utils x) 2))) diff --git a/src/status_im/utils/ethereum/contracts.cljs b/src/status_im/utils/ethereum/contracts.cljs deleted file mode 100644 index 51003e7037e..00000000000 --- a/src/status_im/utils/ethereum/contracts.cljs +++ /dev/null @@ -1,77 +0,0 @@ -(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])) - -(def contracts - {:status/snt - {:address - {:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e" - :testnet "0xc55cf4b03948d7ebc8b9e8bad92643703811d162" - :rinkeby nil} - :methods - {:erc20/transfer - {:signature "transfer(address,uint256)" - :write? true}}} - :status/tribute-to-talk - {:address - {:mainnet nil - :testnet "0x3da3fc53e24707f36c5b4433b442e896c4955f0e" - :rinkeby nil} - :methods - {:get-manifest - {:signature "getManifest(address)" - :return-params ["bytes"]} - :set-manifest - {:signature "setManifest(bytes)" - :write? true}}} - :status/sticker-market - {:address - {:mainnet nil - :testnet "0x39d16CdB56b5a6a89e1A397A13Fe48034694316E" - :rinkeby nil} - :methods - {:pack-count - {:signature "packCount()" - :return-params ["uint256"]} - :pack-data - {:signature "getPackData(uint256)" - :return-params ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"]}}}}) - -(re-frame/reg-fx - ::call - (fn [{:keys [address data callback]}] - (ethereum/call {:to address - :data data} - callback))) - -(fx/defn call - [{:keys [db] :as cofx} - {:keys [contract method params callback on-result on-error details]}] - (let [chain-keyword (-> (get-in db [:account/account :networks (:network db)]) - ethereum/network->chain-keyword) - contract-address (get-in contracts [contract :address chain-keyword])] - (when contract-address - (let [{:keys [signature return-params write?]} - (get-in contracts [contract :methods method]) - data (abi-spec/encode signature params)] - (if write? - (wallet/open-sign-transaction-flow - cofx - (merge {:to contract-address - :data data - :id "approve" - :symbol :ETH - :method "eth_sendTransaction" - :amount (money/bignumber 0) - :on-result on-result - :on-error on-error} - details)) - {::call {:address contract-address - :data data - :callback #(callback (if (empty? return-params) - % - (abi-spec/decode % return-params)))}}))))) diff --git a/src/status_im/utils/ethereum/core.cljs b/src/status_im/utils/ethereum/core.cljs index 6e14acb5517..55cb8125413 100644 --- a/src/status_im/utils/ethereum/core.cljs +++ b/src/status_im/utils/ethereum/core.cljs @@ -1,12 +1,8 @@ (ns status-im.utils.ethereum.core (:require [clojure.string :as string] - [status-im.ethereum.decode :as decode] [status-im.js-dependencies :as dependencies] - [status-im.native-module.core :as status] [status-im.utils.ethereum.tokens :as tokens] - [status-im.utils.ethereum.abi-spec :as abi-spec] - [status-im.utils.money :as money] - [taoensso.timbre :as log])) + [status-im.utils.money :as money])) ;; IDs standardized in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids @@ -43,6 +39,10 @@ address (str hex-prefix address)))) +(defn current-address [db] + (-> (get-in db [:account/account :address]) + normalized-address)) + (defn naked-address [s] (when s (string/replace s hex-prefix ""))) @@ -65,139 +65,26 @@ network->chain-keyword name)) +(defn chain-keyword + [db] + (let [network-id (get-in db [:account/account :network]) + network (get-in db [:account/account :networks network-id])] + (network->chain-keyword network))) + (defn sha3 ([s] (.sha3 dependencies/Web3.prototype (str s))) ([s opts] (.sha3 dependencies/Web3.prototype (str s) (clj->js opts)))) -(defn hex->string [s] - (when s - (let [hex (.toString s)] - (loop [res "" i (if (string/starts-with? hex hex-prefix) 2 0)] - (if (and (< i (.-length hex))) - (recur - (if (= (.substr hex i 2) "00") - res - (str res (.fromCharCode js/String (js/parseInt (.substr hex i 2) 16)))) - (+ i 2)) - res))))) - -(defn hex->boolean [s] - (= s "0x0")) - -(defn boolean->hex [b] - (if b "0x0" "0x1")) - -(defn hex->int [s] - (if (= s hex-prefix) - 0 - (js/parseInt s 16))) - -(defn int->hex [i] - (.toHex dependencies/Web3.prototype i)) - -(defn hex->bignumber [s] - (money/bignumber (if (= s hex-prefix) 0 s))) - -(defn hex->address - "When hex value is 66 char in length (2 for 0x, 64 for - the 32 bytes used by abi-spec for an address), only keep - the part that constitute the address and normalize it," - [s] - (when (= 66 (count s)) - (normalized-address (subs s 26)))) - -(defn zero-pad-64 [s] - (str (apply str (drop (count s) (repeat 64 "0"))) s)) - -(defn string->hex [i] - (.fromAscii dependencies/Web3.prototype i)) - -(defn format-param [param] - (if (number? param) - (zero-pad-64 (str (hex->int param))) - (zero-pad-64 (subs param 2)))) - -(defn format-call-params [method-id & params] - (let [params (string/join (map format-param params))] - (str method-id params))) - -(defn- sig->method-id [signature] - (apply str (take 10 (sha3 signature)))) - -(defn call [params callback] - (status/call-private-rpc - (.stringify js/JSON (clj->js {:jsonrpc "2.0" - :id 1 - :method "eth_call" - :params [params "latest"]})) - (fn [response] - (if (string/blank? response) - (log/warn :web3-response-error) - (callback (get (js->clj (.parse js/JSON response)) "result")))))) - -(defn call-params [contract method-sig & params] - (let [data (apply format-call-params (sig->method-id method-sig) params)] - {:to contract :data data})) - -(defn send-transaction [web3 params cb] - (.sendTransaction (.-eth web3) (clj->js params) cb)) - (def default-transaction-gas (money/bignumber 21000)) -(defn gas-price [web3 cb] - (.getGasPrice (.-eth web3) cb)) - -(defn estimate-gas-web3 [web3 obj cb] - (try - (.estimateGas (.-eth web3) obj cb) - (catch :default _))) - (defn estimate-gas [symbol] (if (tokens/ethereum? symbol) default-transaction-gas ;; TODO(jeluard) Rely on estimateGas call (.times default-transaction-gas 5))) -(defn handle-error [error] - (log/info (.stringify js/JSON error))) - -(defn get-block-number [web3 cb] - (.getBlockNumber (.-eth web3) - (fn [error result] - (if-not error - (cb result) - (handle-error error))))) - -(defn get-block-info [web3 number cb] - (.getBlock (.-eth web3) number (fn [error result] - (if-not error - (cb (js->clj result :keywordize-keys true)) - (handle-error error))))) - -(defn get-transaction [transaction-hash callback] - (status/call-private-rpc - (.stringify js/JSON (clj->js {:jsonrpc "2.0" - :id 1 - :method "eth_getTransactionByHash" - :params [transaction-hash]})) - (fn [response] - (if (string/blank? response) - (log/warn :web3-response-error) - (callback (-> (.parse js/JSON response) - (js->clj :keywordize-keys true) - :result - (update :gasPrice decode/uint) - (update :value decode/uint) - (update :gas decode/uint))))))) - -(defn get-transaction-receipt [web3 number cb] - (.getTransactionReceipt (.-eth web3) number (fn [error result] - (if-not error - (cb (js->clj result :keywordize-keys true)) - (handle-error error))))) - (defn address= [address1 address2] (and address1 address2 (= (normalized-address address1) diff --git a/src/status_im/utils/ethereum/eip165.cljs b/src/status_im/utils/ethereum/eip165.cljs index 6785cb6da2b..4286ffdddef 100644 --- a/src/status_im/utils/ethereum/eip165.cljs +++ b/src/status_im/utils/ethereum/eip165.cljs @@ -1,13 +1,18 @@ (ns status-im.utils.ethereum.eip165 "Utility function related to [EIP165](https://eips.ethereum.org/EIPS/eip-165)" - (:require [status-im.utils.ethereum.core :as ethereum])) + (:require [status-im.ethereum.json-rpc :as json-rpc] + [status-im.utils.ethereum.abi-spec :as abi-spec])) (def supports-interface-hash "0x01ffc9a7") (def marker-hash "0xffffffff") -(defn supports-interface? [contract hash cb] - (ethereum/call (ethereum/call-params contract "supportsInterface(bytes4)" hash) - #(cb %))) +(defn supports-interface? + [contract hash cb] + (json-rpc/eth-call + {:contract contract + :method "supportsInterface(bytes4)" + :params [hash] + :on-success cb})) (defn supports? "Calls cb with true if `supportsInterface` is supported by this contract. @@ -16,9 +21,9 @@ (supports-interface? contract supports-interface-hash - #(if (true? (ethereum/hex->boolean %)) + #(if (true? (abi-spec/hex-to-boolean %)) (supports-interface? contract marker-hash (fn [response] - (cb (false? (ethereum/hex->boolean response))))) + (cb (false? (abi-spec/hex-to-boolean response))))) (cb false)))) diff --git a/src/status_im/utils/ethereum/ens.cljs b/src/status_im/utils/ethereum/ens.cljs index 3edc6c1db75..e008d443cac 100644 --- a/src/status_im/utils/ethereum/ens.cljs +++ b/src/status_im/utils/ethereum/ens.cljs @@ -6,8 +6,8 @@ " (:refer-clojure :exclude [name]) (:require [clojure.string :as string] - [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.abi-spec :as abi-spec])) + [status-im.ethereum.json-rpc :as json-rpc] + [status-im.utils.ethereum.core :as ethereum])) ;; this is the addresses of ens registries for the different networks (def ens-registries @@ -17,7 +17,7 @@ (def default-namehash "0000000000000000000000000000000000000000000000000000000000000000") (def default-address "0x0000000000000000000000000000000000000000") -(def default-key "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") +(def default-key "0x0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") (defn namehash [s] @@ -35,28 +35,37 @@ (defn resolver [registry ens-name cb] - (ethereum/call (ethereum/call-params registry - "resolver(bytes32)" - (namehash ens-name)) - (fn [address] - (let [address (ethereum/hex->address address)] - (cb (if (and address (not= address default-address)) address "")))))) + (json-rpc/eth-call + {:contract registry + :method "resolver(bytes32)" + :params [(namehash ens-name)] + :outputs ["address"] + :on-success + (fn [[address]] + (when-not (= address default-address) + (cb address)))})) (defn owner [registry ens-name cb] - (ethereum/call (ethereum/call-params registry - "owner(bytes32)" - (namehash ens-name)) - (fn [address] - (cb address)))) + (json-rpc/eth-call + {:contract registry + :method "owner(bytes32)" + :params [(namehash ens-name)] + :outputs ["address"] + :on-success + (fn [[address]] + (cb address))})) (defn ttl [registry ens-name cb] - (ethereum/call (ethereum/call-params registry - "ttl(bytes32)" - (namehash ens-name)) - (fn [ttl] - (cb (ethereum/hex->int ttl))))) + (json-rpc/eth-call + {:contract registry + :method "ttl(bytes32)" + :params [(namehash ens-name)] + :outputs ["uint256"] + :on-success + (fn [[ttl]] + (cb ttl))})) ;; Resolver contract ;; Resolver must implement EIP65 (supportsInterface). When interacting with an unknown resolver it's safer to rely on it. @@ -65,9 +74,14 @@ (defn addr [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver "addr(bytes32)" (namehash ens-name)) - (fn [address] - (cb (ethereum/hex->address address))))) + (json-rpc/eth-call + {:contract resolver + :method "addr(bytes32)" + :params [(namehash ens-name)] + :outputs ["address"] + :on-success + (fn [[address]] + (cb address))})) (def name-hash "0x691f3431") @@ -75,37 +89,41 @@ (defn name [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "name(bytes32)" - (namehash ens-name)) - (fn [address] - (cb (ethereum/hex->address address))))) + (json-rpc/eth-call + {:contract resolver + :method "name(bytes32)" + :params [(namehash ens-name)] + :outputs ["string"] + :on-success + (fn [[name]] + (cb name))})) (defn contenthash [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "contenthash(bytes32)" - (namehash ens-name)) - (fn [hash] - (cb (first (abi-spec/decode hash ["bytes"])))))) + (json-rpc/eth-call + {:contract resolver + :method "contenthash(bytes32)" + :params [(namehash ens-name)] + :on-success + (fn [hash] + (cb hash))})) (defn content [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "content(bytes32)" - (namehash ens-name)) - (fn [hash] - (cb hash)))) + (json-rpc/eth-call + {:contract resolver + :method "content(bytes32)" + :params [(namehash ens-name)] + :on-success + (fn [hash] + (cb hash))})) (def ABI-hash "0x2203ab56") (def pubkey-hash "0xc8690233") -(defn add-uncompressed-public-key-prefix - [key] - (when (and key - (not= "0x" key) - (not= default-key key)) - (str "0x04" (subs key 2)))) +(defn uncompressed-public-key + [x y] + (str "0x04" x y)) (defn is-valid-eth-name? [ens-name] @@ -115,12 +133,16 @@ (defn pubkey [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "pubkey(bytes32)" - (namehash ens-name)) - (fn [key] - (when-let [public-key (add-uncompressed-public-key-prefix key)] - (cb public-key))))) + (json-rpc/eth-call + {:contract resolver + :method "pubkey(bytes32)" + :params [(namehash ens-name)] + :outputs ["bytes32" "bytes32"] + :on-success + (fn [[x y]] + (when-let [public-key (uncompressed-public-key x y)] + (when-not (= public-key default-key) + (cb public-key))))})) (defn get-addr [registry ens-name cb] diff --git a/src/status_im/utils/ethereum/erc20.cljs b/src/status_im/utils/ethereum/erc20.cljs deleted file mode 100644 index 46021820616..00000000000 --- a/src/status_im/utils/ethereum/erc20.cljs +++ /dev/null @@ -1,174 +0,0 @@ -(ns status-im.utils.ethereum.erc20 - " - Helper functions to interact with [ERC20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md) smart contract - - Example - - Contract: https://ropsten.etherscan.io/address/0x29b5f6efad2ad701952dfde9f29c960b5d6199c5#readContract - Owner: https://ropsten.etherscan.io/token/0x29b5f6efad2ad701952dfde9f29c960b5d6199c5?a=0xa7cfd581060ec66414790691681732db249502bd - - With a running node on Ropsten: - (let [web3 (:web3 @re-frame.db/app-db) - contract \"0x29b5f6efad2ad701952dfde9f29c960b5d6199c5\" - address \"0xa7cfd581060ec66414790691681732db249502bd\"] - (erc20/balance-of web3 contract address println)) - - => 29166666 - " - (:require [clojure.string :as string] - [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.abi-spec :as abi-spec] - [status-im.native-module.core :as status] - [status-im.utils.security :as security] - [status-im.js-dependencies :as dependencies] - [status-im.utils.types :as types]) - (:refer-clojure :exclude [name symbol])) - -(def utils dependencies/web3-utils) - -(def snt-contracts - {:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e" - :testnet "0xc55cf4b03948d7ebc8b9e8bad92643703811d162" - :rinkeby nil}) - -(def abi - (clj->js - [{:constant true - :inputs [] - :name "name" - :outputs [{:name "" - :type "string"}] - :payable false - :stateMutability "view" - :type "function"} - {:constant true - :inputs [] - :name "symbol" - :outputs [{:name "" - :type "string"}] - :payable false - :stateMutability "view" - :type "function"} - {:constant true - :inputs [] - :name "decimals" - :outputs [{:name "" - :type "uint8"}] - :payable false - :stateMutability "view" - :type "function"} - {:constant true - :inputs [{:name "_who" - :type "address"}] - :name "balanceOf" - :outputs [{:name "" - :type "uint256"}] - :payable false - :stateMutability "view" - :type "function"} - {:constant true - :inputs [] - :name "totalSupply" - :outputs [{:name "" - :type "uint256"}], - :payable false - :stateMutability "view" - :type "function"} - {:constant false - :inputs [{:name "_to" - :type "address"} - {:name "_value" - :type "uint256"}] - :name "transfer" - :outputs [{:name "" - :type "bool"}], - :payable false - :stateMutability "nonpayable" - :type "function"} - {:anonymous false - :inputs [{:indexed true - :name "from" - :type "address"}, - {:indexed true - :name "to" - :type "address"}, - {:indexed false - :name "value" - :type "uint256"}] - :name "Transfer" - :type "event"}])) - -(defn get-instance* [web3 contract] - (.at (.contract (.-eth web3) abi) contract)) - -(def get-instance - (memoize get-instance*)) - -(defn name [web3 contract cb] - (.name (get-instance web3 contract) cb)) - -(defn symbol [web3 contract cb] - (.symbol (get-instance web3 contract) cb)) - -(defn decimals [web3 contract cb] - (.decimals (get-instance web3 contract) cb)) - -(defn total-supply [web3 contract cb] - (.totalSupply (get-instance web3 contract) cb)) - -(defn balance-of [web3 contract address cb] - (.balanceOf (get-instance web3 contract) address cb)) - -(defn transfer [contract from to value gas gas-price masked-password on-completed] - (status/send-transaction (types/clj->json - (merge (ethereum/call-params contract "transfer(address,uint256)" to value) - {:from from - :gas gas - :gasPrice gas-price})) - (security/safe-unmask-data masked-password) - on-completed)) - -(defn transfer-from [web3 contract from-address to-address value cb] - (ethereum/call (ethereum/call-params contract - "transferFrom(address,address,uint256)" - (ethereum/normalized-address from-address) - (ethereum/normalized-address to-address) - (ethereum/int->hex value)) - #(cb (ethereum/hex->boolean %)))) - -(defn approve [web3 contract address value cb] - (ethereum/call (ethereum/call-params contract - "approve(address,uint256)" - (ethereum/normalized-address address) - (ethereum/int->hex value)) - #(cb (ethereum/hex->boolean %)))) - -(defn allowance [web3 contract owner-address spender-address cb] - (ethereum/call (ethereum/call-params contract - "allowance(address,address)" - (ethereum/normalized-address owner-address) - (ethereum/normalized-address spender-address)) - #(cb (ethereum/hex->bignumber %)))) - -(defn is-transfer? - [input-data] - (string/starts-with? input-data "0xa9059cbb")) - -(defn is-snt-contract? - [contract] - ((into #{} (vals snt-contracts)) contract)) - -(defn get-transaction - "only supports SNT for now" - [transaction-hash callback] - (ethereum/get-transaction - transaction-hash - (fn [{:keys [to input] :as result}] - (when (and result - (is-snt-contract? to) - (is-transfer? input)) - (let [[recipient snt-value] - (abi-spec/decode (subs input 10) ["address" "uint"])] - (callback (assoc result - :recipient recipient - :snt-value snt-value))))))) diff --git a/src/status_im/utils/ethereum/erc721.cljs b/src/status_im/utils/ethereum/erc721.cljs index 3dc5d402992..3e05685c3f8 100644 --- a/src/status_im/utils/ethereum/erc721.cljs +++ b/src/status_im/utils/ethereum/erc721.cljs @@ -2,19 +2,22 @@ " Helper functions to interact with [ERC721](https://eips.ethereum.org/EIPS/eip-721) smart contract " - (:require [status-im.utils.ethereum.core :as ethereum])) + (:require [status-im.ethereum.json-rpc :as json-rpc])) -(defn token-of-owner-by-index [contract address index cb] - (ethereum/call (ethereum/call-params - contract - "tokenOfOwnerByIndex(address,uint256)" - (ethereum/normalized-address address) - (ethereum/int->hex index)) - #(cb (ethereum/hex->bignumber %)))) +(defn token-of-owner-by-index + [contract address index cb] + (json-rpc/eth-call + {:contract contract + :method "tokenOfOwnerByIndex(address,uint256)" + :params [address index] + :outputs ["uint256"] + :on-success (fn [[token]] (cb token))})) -(defn token-uri [contract tokenId cb] - (ethereum/call (ethereum/call-params - contract - "tokenURI(uint256)" - (ethereum/int->hex tokenId)) - #(cb (ethereum/hex->string %)))) +(defn token-uri + [contract tokenId cb] + (json-rpc/eth-call + {:contract contract + :method "tokenURI(uint256)" + :params [tokenId] + :outputs ["string"] + :on-success (fn [[uri]] (cb uri))})) diff --git a/src/status_im/utils/ethereum/resolver.cljs b/src/status_im/utils/ethereum/resolver.cljs index 291409f6acc..18357ed8453 100644 --- a/src/status_im/utils/ethereum/resolver.cljs +++ b/src/status_im/utils/ethereum/resolver.cljs @@ -3,7 +3,6 @@ (:refer-clojure :exclude [name])) (def default-hash "0x0000000000000000000000000000000000000000000000000000000000000000") - (defn contenthash [registry ens-name cb] (ens/resolver registry ens-name diff --git a/src/status_im/utils/ethereum/stickers.cljs b/src/status_im/utils/ethereum/stickers.cljs deleted file mode 100644 index fbd39a49b27..00000000000 --- a/src/status_im/utils/ethereum/stickers.cljs +++ /dev/null @@ -1,34 +0,0 @@ -(ns status-im.utils.ethereum.stickers - (:require [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.abi-spec :as abi-spec])) - -(def contracts - {:mainnet nil - :testnet "0x39d16CdB56b5a6a89e1A397A13Fe48034694316E" - :rinkeby nil}) - -(defn pack-count - "Returns number of packs rigestered in the contract" - [contract cb] - (ethereum/call (ethereum/call-params contract "packCount()") - (fn [count] (cb (ethereum/hex->int count))))) - -(defn pack-data - "Returns vector of pack data parameters by pack id: [category owner mintable timestamp price contenthash]" - [contract pack-id cb] - (ethereum/call (ethereum/call-params contract "getPackData(uint256)" (ethereum/int->hex pack-id)) - (fn [data] - (cb (abi-spec/decode data ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"]))))) - -(defn owned-tokens - "Returns vector of owned tokens ids in the contract by address" - [contract address cb] - (ethereum/call (ethereum/call-params contract "tokensOwnedBy(address)" (ethereum/normalized-address address)) - (fn [data] - (cb (first (abi-spec/decode data ["uint256[]"])))))) - -(defn token-pack-id - "Returns pack id in the contract by token id" - [contract token cb] - (ethereum/call (ethereum/call-params contract "tokenPackId(uint256)" (ethereum/int->hex token)) - (fn [data] (cb (ethereum/hex->int data))))) diff --git a/src/status_im/utils/http.cljs b/src/status_im/utils/http.cljs index 8d194ef3f03..bb19e3752e6 100644 --- a/src/status_im/utils/http.cljs +++ b/src/status_im/utils/http.cljs @@ -32,7 +32,7 @@ :body body})))))) (.catch (or on-error (fn [error] - (utils/show-popup "Error" (str error)))))))) + (utils/show-popup "Error" url (str error)))))))) (defn post "Performs an HTTP POST request" @@ -72,7 +72,7 @@ (.catch (fn [error] (if on-error (on-error {:response-body error}) - (utils/show-popup "Error" (str error)))))))) + (utils/show-popup "Error" url (str error)))))))) (defn raw-get "Performs an HTTP GET request and returns raw results :status :headers :body." @@ -94,7 +94,7 @@ :body body})))))) (.catch (or on-error (fn [error] - (utils/show-popup "Error" (str error)))))))) + (utils/show-popup "Error" url (str error)))))))) (defn get "Performs an HTTP GET request" @@ -129,7 +129,7 @@ :else false))) (.catch (or on-error (fn [error] - (utils/show-popup "Error" (str error)))))))) + (utils/show-popup "Error" url (str error)))))))) (defn normalize-url [url] (str (when (and (string? url) (not (re-find #"^[a-zA-Z-_]+:/" url))) "http://") url)) diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs new file mode 100644 index 00000000000..afd1e8a0224 --- /dev/null +++ b/src/status_im/wallet/core.cljs @@ -0,0 +1,585 @@ +(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.ethereum.json-rpc :as json-rpc] + [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.abi-spec :as abi-spec] + [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] + [status-im.utils.prices :as prices] + [status-im.utils.utils :as utils.utils] + [taoensso.timbre :as log])) + +(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 + :wallet/get-balance + (fn [{:keys [account-id on-success on-error]}] + (json-rpc/call + {:method "eth_getBalance" + :params [account-id "latest"] + :on-success on-success + :on-error on-error}))) + +;; TODO(oskarth): At some point we want to get list of relevant +;; assets to get prices for +(re-frame/reg-fx + :wallet/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 + :wallet/update-gas-price + (fn [{:keys [success-event edit?]}] + (json-rpc/call + {:method "eth_gasPrice" + :on-success + #(re-frame/dispatch [success-event % edit?])}))) + +(re-frame/reg-fx + :wallet/update-estimated-gas + (fn [{:keys [obj success-event]}] + (json-rpc/call + {:method "eth_estimateGas" + :params [obj] + :on-success + #(re-frame/dispatch [success-event %])}))) + +(defn- validate-token-name! + [{:keys [address symbol name]}] + (json-rpc/eth-call + {:contract address + :method "name()" + :outputs ["string"] + :on-success + (fn [[contract-name]] + (when (and (not (empty? contract-name)) + (not= name contract-name)) + (let [message (i18n/label :t/token-auto-validate-name-error + {:symbol symbol + :expected name + :actual contract-name + :address address})] + (log/warn message) + (utils.utils/show-popup (i18n/label :t/warning) message))))})) + +(defn- validate-token-symbol! + [{:keys [address symbol]}] + (json-rpc/eth-call + {:contract address + :method "symbol()" + :outputs ["string"] + :on-success + (fn [[contract-symbol]] + ;;NOTE(goranjovic): skipping check if field not set in contract + (when (and (not (empty? contract-symbol)) + (not= (clojure.core/name symbol) contract-symbol)) + (let [message (i18n/label :t/token-auto-validate-symbol-error + {:symbol symbol + :expected (clojure.core/name symbol) + :actual contract-symbol + :address address})] + (log/warn message) + (utils.utils/show-popup (i18n/label :t/warning) message))))})) + +(defn- validate-token-decimals! + [{:keys [address symbol decimals nft?]}] + (when-not nft? + (json-rpc/eth-call + {:contract address + :method "decimals()" + :outputs ["uint256"] + :on-success + (fn [[contract-decimals]] + (when (and (not (nil? contract-decimals)) + (not= decimals contract-decimals)) + (let [message (i18n/label :t/token-auto-validate-decimals-error + {:symbol symbol + :expected decimals + :actual contract-decimals + :address address})] + (log/warn message) + (utils.utils/show-popup (i18n/label :t/warning) message))))}))) + +(re-frame/reg-fx + :wallet/validate-tokens + (fn [tokens] + (doseq [token tokens] + (validate-token-decimals! token) + (validate-token-symbol! token) + (validate-token-name! token)))) + +(re-frame/reg-fx + :wallet/get-tokens-balance + (fn [{:keys [wallet-address tokens on-success on-error]}] + (doseq [{:keys [address symbol]} tokens] + (json-rpc/eth-call + {:contract address + :method "balanceOf(address)" + :params [wallet-address] + :outputs ["uint256"] + :on-success + (fn [[balance]] + (on-success symbol (money/bignumber balance))) + :on-error #(on-error symbol %)})))) + +(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 (str "0x" (abi-spec/number-to-hex amount)) + :gas (str "0x" (abi-spec/number-to-hex gas)) + :gasPrice (str "0x" (abi-spec/number-to-hex gas-price))} + data + (assoc :data data) + nonce + (assoc :nonce nonce))) + +(defn normalize-sign-message-params + "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" + [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 :timestamp (str now) + :type :pending + :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])] + (log/warn :wallet/transaction-error + :code code + :message message) + (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 [custom-tokens (get-in db [:account/account :settings :wallet :custom-tokens]) + chain (ethereum/chain-keyword db) + 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 (get tokens/all-default-tokens chain)}))))) + +(fx/defn update-balances + [{{:keys [network-status :wallet/all-tokens] + {:keys [address settings]} :account/account :as db} :db :as cofx}] + (let [normalized-address (ethereum/normalized-address address) + chain (ethereum/chain-keyword db) + assets (get-in settings [:wallet :visible-tokens chain]) + tokens (->> (tokens/tokens-for all-tokens chain) + (remove #(or (:hidden? %))))] + (when (not= network-status :offline) + (fx/merge + cofx + {:wallet/get-balance + {:account-id normalized-address + :on-success #(re-frame/dispatch + [:wallet.callback/update-balance-success %]) + :on-error #(re-frame/dispatch + [:wallet.callback/update-balance-fail %])} + + :wallet/get-tokens-balance + {:wallet-address normalized-address + :tokens tokens + :on-success + (fn [symbol balance] + (if (and assets + (assets symbol)) + (re-frame/dispatch + [:wallet.callback/update-token-balance-success symbol balance]) + ;; NOTE: when there it is not a visible assets + ;; we make an initialization round + (when (> balance 0) + (re-frame/dispatch + [:wallet/token-found symbol balance])))) + :on-error + (fn [symbol error] + (re-frame/dispatch + [:wallet.callback/update-token-balance-fail symbol error]))} + + :db + (-> db + (clear-error-message :balance-update) + (assoc-in [:wallet :balance-loading?] true))} + (when-not assets + (accounts.update/update-settings + (assoc-in settings + [:wallet :visible-tokens chain] + #{}) + {})))))) + +(fx/defn update-prices + [{{:keys [network network-status :wallet/all-tokens] + {:keys [address settings networks]} :account/account :as db} :db}] + (let [chain (ethereum/chain-keyword db) + mainnet? (= :mainnet chain) + assets (get-in settings [:wallet :visible-tokens chain]) + tokens (tokens-symbols assets all-tokens chain) + currency-id (or (get-in settings [:wallet :currency]) :usd) + currency (get constants/currencies currency-id)] + (when (not= network-status :offline) + {:wallet/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) + (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 [(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)]]})) + +(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 on-update-prices-success + [{: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] (money/bignumber balance)) + (assoc-in [:wallet :balance-loading?] false))}) + +(fx/defn update-token-balance + [{:keys [db]} symbol balance] + {:db (-> db + (assoc-in [:wallet :balance symbol] (money/bignumber 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))) + +(fx/defn eth-transaction-call + [{:keys [db] :as cofx} + {:keys [contract method params on-success on-error details] :as transaction}] + (let [current-address (ethereum/current-address db) + transaction (merge {:to contract + :from current-address + :data (abi-spec/encode method params) + :id "approve" + :symbol :ETH + :method "eth_sendTransaction" + :amount (money/bignumber 0) + :on-success on-success + :on-error on-error} + details) + 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 + {:db (-> db + (assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true) + (assoc-in [:wallet :send-transaction] + transaction)) + :wallet/update-estimated-gas + {:obj (select-keys transaction [:to :from :data]) + :success-event :wallet/update-estimated-gas-success} + + :wallet/update-gas-price + {:success-event :wallet/update-gas-price-success + :edit? false}} + (navigation/navigate-to-cofx go-to-view-id {})))) diff --git a/src/status_im/wallet/custom_tokens/core.cljs b/src/status_im/wallet/custom_tokens/core.cljs index 1ef3793436c..7abb66fc4e2 100644 --- a/src/status_im/wallet/custom_tokens/core.cljs +++ b/src/status_im/wallet/custom_tokens/core.cljs @@ -1,136 +1,210 @@ (ns status-im.wallet.custom-tokens.core - (:require-macros [status-im.utils.views :refer [defview letsubs]]) - (:require [re-frame.core :as re-frame] + (:require [clojure.string :as string] + [re-frame.core :as re-frame] + [status-im.ethereum.decode :as decode] + [status-im.ethereum.json-rpc :as json-rpc] [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.money :as money] [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 (fn [contract] - (ethereum/call - (ethereum/call-params contract "decimals()") - #(re-frame/dispatch [:wallet.custom-token/decimals-result %])))) + (json-rpc/eth-call + {:contract contract + :method "decimals()" + :outputs ["uint256"] + :on-success + (fn [[contract-decimals]] + (re-frame/dispatch [:wallet.custom-token/decimals-result + contract-decimals]))}))) (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 %])))) + (json-rpc/eth-call + {:contract contract + :method "symbol()" + :outputs ["string"] + :on-success + (fn [[contract-symbol]] + (re-frame/dispatch [:wallet.custom-token/symbol-result + contract + contract-symbol]))}))) (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 %])))) + (fn [[contract wallet-address]] + (json-rpc/eth-call + {:contract contract + :method "balanceOf(address)" + :params [wallet-address] + :outputs ["uint256"] + :on-success + (fn [[balance]] + (re-frame/dispatch [:wallet.custom-token/balance-result + contract + (money/bignumber balance)]))}))) (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 %])))) + (json-rpc/eth-call + {:contract contract + :method "name()" + :outputs ["string"] + :on-success + (fn [[contract-name]] + (re-frame/dispatch [:wallet.custom-token/name-result + contract + contract-name]))}))) (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 %])))) + (json-rpc/eth-call + {:contract contract + :method "totalSupply()" + :outputs ["uint256"] + :on-success + (fn [[contract-total-supply]] + (re-frame/dispatch [:wallet.custom-token/total-supply-result + contract + (money/bignumber contract-total-supply)]))}))) (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 %)])))) + (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] +(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] + (some #(= field-value (get % field-key)) + (vals (get all-tokens chain-key))))) + +(fx/defn total-supply-result + [{:keys [db]} contract total-supply] + (if (money/valid? total-supply) + {:wallet.custom-token/get-balance + [contract (ethereum/current-address db)]} + {: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] +(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}) + {: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 (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 token-symbol] + (let [symbol-exists? (field-exists? db :symbol (keyword token-symbol))] {:db - (update db :wallet/custom-token-screen merge + (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)] +(fx/defn name-result + [{:keys [db]} contract token-name] + (let [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))) + (i18n/label :t/you-already-have-an-asset + {:value token-name}))}) + :wallet.custom-token/get-symbol contract})) + +(fx/defn balance-result + [{:keys [db]} contract balance] + (if (money/valid? balance) + {:db (assoc-in db + [:wallet/custom-token-screen :balance] + (str balance)) :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-custom-token [{:keys [db] :as cofx}] + {:db (update db + :wallet/custom-token-screen + merge + {:in-progress? nil + :error (i18n/label :t/wrong-contract)})})) + +(fx/defn add-custom-token + [{: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 remove-custom-token [{:keys [db] :as cofx} {:keys [address] :as token}] + 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) + (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] +(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 + :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}))})} + (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)})) \ No newline at end of file + :decimals {:db (assoc-in db + [:wallet/custom-token-screen :decimals] + value)})) diff --git a/src/status_im/ui/screens/wallet/db.cljs b/src/status_im/wallet/db.cljs similarity index 86% rename from src/status_im/ui/screens/wallet/db.cljs rename to src/status_im/wallet/db.cljs index adeb3216057..364a421bbb9 100644 --- a/src/status_im/ui/screens/wallet/db.cljs +++ b/src/status_im/wallet/db.cljs @@ -1,4 +1,4 @@ -(ns status-im.ui.screens.wallet.db +(ns status-im.wallet.db (:require [cljs.spec.alpha :as spec] [status-im.i18n :as i18n] status-im.ui.screens.wallet.request.db @@ -22,6 +22,7 @@ (spec/def :wallet/visible-tokens any?) (spec/def :wallet/currency any?) (spec/def :wallet/balance any?) +(spec/def :wallet/filters set?) (spec/def :wallet/wallet (spec/keys :opt-un [:wallet/send-transaction :wallet/request-transaction @@ -30,6 +31,7 @@ :wallet/errors :wallet/transactions :wallet/edit + :wallet/filters :wallet/current-tab :wallet/current-transaction :wallet/modal-history? @@ -58,3 +60,15 @@ :else {:value value})))) + +(def default-wallet-filters + #{:inbound :outbound :pending :failed}) + +(def default-wallet + {:filters default-wallet-filters}) + +(defn get-confirmations + [{:keys [block]} current-block] + (if (and current-block block) + (inc (- current-block block)) + 0)) diff --git a/src/status_im/web3/core.cljs b/src/status_im/web3/core.cljs index 25f5e9177ec..514e81ac98f 100644 --- a/src/status_im/web3/core.cljs +++ b/src/status_im/web3/core.cljs @@ -39,13 +39,6 @@ (fn [error sync] (re-frame/dispatch [:web3.callback/get-syncing-success error sync]))))) -(defn get-block-number-fx [web3] - (when web3 - (.getBlockNumber - (.-eth web3) - (fn [error block-number] - (re-frame/dispatch [:web3.callback/get-block-number error block-number]))))) - (defn set-default-account [web3 address] (set! (.-defaultAccount (.-eth web3)) diff --git a/src/status_im/web3/events.cljs b/src/status_im/web3/events.cljs index 332446f0cb6..b6b7ae51614 100644 --- a/src/status_im/web3/events.cljs +++ b/src/status_im/web3/events.cljs @@ -13,10 +13,6 @@ :web3/get-syncing web3/get-syncing) -(re-frame/reg-fx - :web3/get-block-number - web3/get-block-number-fx) - (re-frame/reg-fx :web3/set-default-account (fn [[web3 address]] diff --git a/test/cljs/status_im/test/models/wallet.cljs b/test/cljs/status_im/test/models/wallet.cljs index b9872d61dfb..f36d1bb655b 100644 --- a/test/cljs/status_im/test/models/wallet.cljs +++ b/test/cljs/status_im/test/models/wallet.cljs @@ -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" diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 85a88f04c8c..4d7bb92f8e6 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -12,7 +12,6 @@ [status-im.test.wallet.subs] [status-im.test.wallet.transactions] [status-im.test.wallet.transactions.subs] - [status-im.test.wallet.transactions.views] [status-im.test.mailserver.core] [status-im.test.fleet.core] [status-im.test.group-chats.core] @@ -99,7 +98,6 @@ 'status-im.test.wallet.subs 'status-im.test.wallet.transactions 'status-im.test.wallet.transactions.subs - 'status-im.test.wallet.transactions.views 'status-im.test.chat.models.loading 'status-im.test.chat.models.input 'status-im.test.chat.models.message diff --git a/test/cljs/status_im/test/sign_in/flow.cljs b/test/cljs/status_im/test/sign_in/flow.cljs index 065511c9456..44a3f5dac0e 100644 --- a/test/cljs/status_im/test/sign_in/flow.cljs +++ b/test/cljs/status_im/test/sign_in/flow.cljs @@ -2,11 +2,11 @@ "The main purpose of these tests is to signal that some steps of the sign in flow has been changed. Such changes should be reflected in both these tests and documents which describe the whole \"sign in\" flow." - (:require [cljs.test :refer-macros [deftest is are testing]] + (:require [cljs.test :refer-macros [deftest is testing]] [status-im.accounts.login.core :as login.core] [status-im.events :as events] - [status-im.test.sign-in.data :as data] - [status-im.signals.core :as signals])) + [status-im.signals.core :as signals] + [status-im.test.sign-in.data :as data])) (deftest on-password-input-submitted (testing @@ -199,13 +199,11 @@ (set (:utils/dispatch-later efx))))) (testing "Check the rest of effects." (is (contains? efx :web3/set-default-account)) - (is (contains? efx :web3/get-block-number)) (is (contains? efx :web3/fetch-node-version)) - (is (contains? efx :get-balance)) (is (contains? efx :web3/get-syncing)) - (is (contains? efx :get-tokens-balance)) - (is (contains? efx :get-prices)) - (is (contains? efx :status-im.models.transactions/start-sync-transactions)))))) + (is (contains? efx :wallet/get-balance)) + (is (contains? efx :wallet/get-tokens-balance)) + (is (contains? efx :wallet/get-prices)))))) (deftest login-failed (testing diff --git a/test/cljs/status_im/test/ui/screens/currency_settings/models.cljs b/test/cljs/status_im/test/ui/screens/currency_settings/models.cljs index db40e6f114a..3ff599bbb54 100644 --- a/test/cljs/status_im/test/ui/screens/currency_settings/models.cljs +++ b/test/cljs/status_im/test/ui/screens/currency_settings/models.cljs @@ -11,7 +11,7 @@ (deftest set-currency (let [cofx (models/set-currency {:db {:account/account {:settings {:wallet {}}}}} :usd)] - (is (= [:db :get-balance :get-tokens-balance :get-prices :data-store/base-tx] (keys cofx))) + (is (= [:db :wallet/get-prices :data-store/base-tx] (keys cofx))) (is (= :usd (get-in cofx [:db :account/account :settings :wallet :currency])))) (is (= :jpy (get-in (models/set-currency {:db {:account/account {:settings {:wallet {}}}}} :jpy) [:db :account/account :settings :wallet :currency])))) diff --git a/test/cljs/status_im/test/ui/screens/wallet/db.cljs b/test/cljs/status_im/test/ui/screens/wallet/db.cljs index 29fb2ac34d7..64e9e15dfcd 100644 --- a/test/cljs/status_im/test/ui/screens/wallet/db.cljs +++ b/test/cljs/status_im/test/ui/screens/wallet/db.cljs @@ -1,6 +1,6 @@ (ns status-im.test.ui.screens.wallet.db (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.ui.screens.wallet.db :as wallet.db] + [status-im.wallet.db :as wallet.db] [status-im.utils.money :as money] [status-im.i18n :as i18n])) diff --git a/test/cljs/status_im/test/utils/ethereum/core.cljs b/test/cljs/status_im/test/utils/ethereum/core.cljs index 733ed8baeb3..629ceb6070e 100644 --- a/test/cljs/status_im/test/utils/ethereum/core.cljs +++ b/test/cljs/status_im/test/utils/ethereum/core.cljs @@ -1,17 +1,9 @@ (ns status-im.test.utils.ethereum.core - (:require [cljs.test :refer-macros [deftest is testing]] + (:require [cljs.test :refer-macros [deftest is]] [status-im.utils.ethereum.core :as ethereum])) -(deftest call-params - (testing "ERC20 balance-of params" - (let [contract "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5" - address "0xa7cfd581060ec66414790691681732db249502bd"] - (is (= (ethereum/call-params contract "balanceOf(address)" address) - {:to "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5" - :data "0x70a08231000000000000000000000000a7cfd581060ec66414790691681732db249502bd"}))))) - (deftest chain-id->chain-keyword (is (= (ethereum/chain-id->chain-keyword 1) :mainnet)) (is (= (ethereum/chain-id->chain-keyword 3) :testnet)) (is (= (ethereum/chain-id->chain-keyword 4) :rinkeby)) - (is (= (ethereum/chain-id->chain-keyword 5777) :custom))) \ No newline at end of file + (is (= (ethereum/chain-id->chain-keyword 5777) :custom))) diff --git a/test/cljs/status_im/test/wallet/transactions.cljs b/test/cljs/status_im/test/wallet/transactions.cljs index 789b788b327..37941cc94b3 100644 --- a/test/cljs/status_im/test/wallet/transactions.cljs +++ b/test/cljs/status_im/test/wallet/transactions.cljs @@ -1,166 +1,8 @@ (ns status-im.test.wallet.transactions - (:require [cljs.test :refer-macros [deftest is testing async run-tests]] - [cljs.core.async.impl.protocols :refer [closed?]] - [status-im.utils.datetime :as time] - [status-im.utils.http :as http] - [status-im.models.transactions :as transactions] - [goog.Uri :as goog-uri])) - -(deftest have-unconfirmed-transactions - (is (transactions/have-unconfirmed-transactions? - [{:confirmations "0"}])) - (is (transactions/have-unconfirmed-transactions? - [{:confirmations "11"}])) - (is (transactions/have-unconfirmed-transactions? - [{:confirmations "200"} - {:confirmations "0"}])) - (is (not (transactions/have-unconfirmed-transactions? - [{:confirmations "12"}])))) - -(deftest chat-map->transaction-ids - (is (= #{} (transactions/chat-map->transaction-ids "testnet_rpc" {}))) - (is (= #{"a" "b" "c" "d"} - (transactions/chat-map->transaction-ids - "testnet_rpc" - {:a {:messages {1 {:content-type "command" - :content {:params {:tx-hash "a" - :network "testnet"}}}}} - :b {:messages {1 {:content-type "command" - :content {:params {:tx-hash "b" - :network "testnet"}}}}} - :c {:messages {1 {:content-type "command" - :content {:params {:tx-hash "c" - :network "testnet"}}} - 2 {:content-type "command" - :content {:params {:tx-hash "d" - :network "testnet"}}}}}}))) - - (is (= #{"a" "b" "c" "d" "e"} - (transactions/chat-map->transaction-ids - "testnet" - {:aa {:messages {1 {:content-type "command" - :content {:params {:tx-hash "a" - :network "testnet"}}}}} - :bb {:messages {1 {:content-type "command" - :content {:params {:tx-hash "b" - :network "testnet"}}}}} - :cc {:messages {1 {:content-type "command" - :content {:params {:tx-hash "c" - :network "testnet"}}} - 2 {:content-type "command" - :content {:params {:tx-hash "d" - :network "testnet"}}} - 3 {:content-type "command" - :content {:params {:tx-hash "e" - :network "testnet"}}}}}}))) - (is (= #{"b"} - (transactions/chat-map->transaction-ids - "testnet_rpc" - {:aa {:public? true - :messages {1 {:content-type "command" - :content {:params {:tx-hash "a" - :network "testnet"}}}}} - :bb {:messages {1 {:content-type "command" - :content {:params {:tx-hash "b" - :network "testnet"}}}}} - :cc {:messages {1 {:content {:params {:tx-hash "c" - :network "testnet"}}} - 2 {:content-type "command"}}}})))) - -;; The following tests are fantastic for developing the async-periodic-exec -;; but dismal for CI because of their probablistic nature -#_(deftest async-periodic-exec - (testing "work-fn is executed and can be stopeed" - (let [executor (atom nil) - state (atom 0)] - (reset! executor - (transactions/async-periodic-exec - (fn [done-fn] - (swap! state inc) - (done-fn)) - 100 - 500)) - (async test-done - (js/setTimeout - (fn [] - (is (> 6 @state 2)) - (transactions/async-periodic-stop! @executor) - (let [st @state] - (js/setTimeout - #(do - (is (= st @state)) - (is (closed? @executor)) - (test-done)) - 500))) - 500))))) - -#_(deftest async-periodic-exec-error-in-job - (testing "error thrown in job is caught and loop continues" - (let [executor (atom nil) - state (atom 0)] - (reset! executor - (transactions/async-periodic-exec - (fn [done-fn] - (swap! state inc) - (throw (ex-info "Throwing this on purpose in error-in-job test" {}))) - 10 - 100)) - (async test-done - (js/setTimeout - (fn [] - (is (> @state 1)) - (transactions/async-periodic-stop! @executor) - (let [st @state] - (js/setTimeout - #(do - (is (= st @state)) - (is (closed? @executor)) - (test-done)) - 500))) - 1000))))) - -#_(deftest async-periodic-exec-job-takes-longer - (testing "job takes longer than expected, executor timeout but task side-effects are still applied" - (let [executor (atom nil) - state (atom 0)] - (reset! executor - (transactions/async-periodic-exec - (fn [done-fn] (js/setTimeout #(swap! state inc) 100)) - 10 - 1)) - (async test-done - (js/setTimeout - (fn [] - (transactions/async-periodic-stop! @executor) - (js/setTimeout - #(do (is (< 3 @state)) - (test-done)) - 500)) - 500))))) - -#_(deftest async-periodic-exec-stop-early - (testing "stopping early prevents any executions" - (let [executor (atom nil) - state (atom 0)] - (reset! executor - (transactions/async-periodic-exec - (fn [done-fn] - (swap! state inc) - (done-fn)) - 100 - 100)) - (async test-done - (js/setTimeout - (fn [] - (is (zero? @state)) - (transactions/async-periodic-stop! @executor) - (let [st @state] - (js/setTimeout - (fn [] - (is (zero? @state)) - (test-done)) - 500))) - 50))))) + (:require [cljs.test :refer-macros [deftest is]] + [goog.Uri :as goog-uri] + [status-im.ethereum.transactions.etherscan :as transactions] + [status-im.utils.http :as http])) (defn- uri-query-data [uri] (let [uri' (goog-uri/parse uri) @@ -196,7 +38,7 @@ :sort "desc", :apikey "DMSI4UAAKUBVGCDMVP3H2STAMSAUV7BYFI", :q "json"}} - (uri-query-data (transactions/get-transaction-url :mainnet "asdfasdf")))) + (uri-query-data (transactions/get-transaction-url :mainnet "0xasdfasdf")))) (is (= {:scheme "https", :domain "api-rinkeby.etherscan.io", :path "/api", @@ -209,28 +51,29 @@ :sort "desc", :apikey "DMSI4UAAKUBVGCDMVP3H2STAMSAUV7BYFI", :q "json"}} - (uri-query-data (transactions/get-transaction-url :rinkeby "asdfasdfg")))) - (let [uri (-> (transactions/get-transaction-url :testnet "asdfasdfgg") + (uri-query-data (transactions/get-transaction-url :rinkeby "0xasdfasdfg")))) + (let [uri (-> (transactions/get-transaction-url :testnet "0xasdfasdfgg") uri-query-data)] (is (= "api-ropsten.etherscan.io" (:domain uri))) (is (= "0xasdfasdfgg" (-> uri :query :address)))) - (is (thrown? js/Error (transactions/get-transaction-url nil "asdfasdfg")))) + (is (thrown? js/Error (transactions/get-transaction-url nil "0xasdfasdfg")))) (declare mock-etherscan-success-response mock-etherscan-error-response mock-etherscan-empty-response) -(deftest etherscan-transactions +(deftest etherscan-history (let [ky-set #{:block :hash :symbol :gas-price :value :gas-limit :type - :confirmations :gas-used :from :timestamp :nonce :to :data}] + :gas-used :from :timestamp :nonce :to :data}] (with-redefs [http/get (fn [url success-fn error-fn] (success-fn mock-etherscan-success-response))] (let [result (atom nil)] - (transactions/etherscan-transactions + (transactions/etherscan-history :mainnet - "asdfasdf" + "0xasdfasdf" #(reset! result %) - (fn [er])) + (fn [er]) + false) (doseq [[tx-hash tx-map] @result] (is (string? tx-hash)) (is (= tx-hash (:hash tx-map))) @@ -245,25 +88,25 @@ (with-redefs [http/get (fn [url success-fn error-fn] (success-fn mock-etherscan-empty-response))] (let [result (atom nil)] - (transactions/etherscan-transactions + (transactions/etherscan-history :mainnet - "asdfasdf" + "0xasdfasdf" #(reset! result %) - (fn [er])) + (fn [er]) + false) (is (= {} @result)))) (with-redefs [http/get (fn [url success-fn error-fn] (success-fn mock-etherscan-error-response))] (let [result (atom nil)] - (transactions/etherscan-transactions + (transactions/etherscan-history :mainnet - "asdfasdf" + "0xasdfasdf" #(reset! result %) - (fn [er])) + (fn [er]) + false) (is (= {} @result))))) -#_(run-tests) - (def mock-etherscan-error-response "{\"status\":\"0\",\"message\":\"NOTOK\",\"result\":\"Error!\"}") diff --git a/test/cljs/status_im/test/wallet/transactions/views.cljs b/test/cljs/status_im/test/wallet/transactions/views.cljs deleted file mode 100644 index 5bce9ad1881..00000000000 --- a/test/cljs/status_im/test/wallet/transactions/views.cljs +++ /dev/null @@ -1,12 +0,0 @@ -(ns status-im.test.wallet.transactions.views - (:require [cljs.test :refer [deftest is testing]] - [status-im.ui.screens.wallet.transactions.views :as views])) - -(deftest filtered-transaction? - (is (not (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :outbound :checked? true}]})))) - (is (not (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :inbound :checked? false}]})))) - (is (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :inbound :checked? true}]}))) - (is (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :outbound :checked? true} {:id :inbound :checked? true}]})))) - -(deftest update-transactions - (is (= {:data '()} (views/update-transactions {:data {:type :inbound}} {:type [{:id :outbound :checked? true}]})))) \ No newline at end of file