From 1b6419b720aa4e368f4a9064a59864e155f92b55 Mon Sep 17 00:00:00 2001 From: yenda Date: Sun, 19 May 2019 21:47:54 +0200 Subject: [PATCH] [refactoring] remove web3, clean up wallet effects - introduce json-rpc namespace, which provides `call` and `eth-call`, a generic way of calling a json-rpc method taking care of conversions and error handling - remove web3 usage from wallet - clean up effects, reducing the amount of computations when login in --- src/status_im/accounts/login/core.cljs | 5 +- .../chat/commands/impl/transactions.cljs | 6 +- src/status_im/ethereum/contracts.cljs | 61 +++ src/status_im/ethereum/json_rpc.cljs | 74 +++ src/status_im/ethereum/subscriptions.cljs | 51 +-- src/status_im/ethereum/transactions/core.cljs | 168 +++---- src/status_im/events.cljs | 22 +- src/status_im/node/core.cljs | 5 - src/status_im/protocol/core.cljs | 1 - src/status_im/stickers/core.cljs | 8 +- src/status_im/subs.cljs | 8 +- src/status_im/tribute_to_talk/core.cljs | 2 +- .../ui/screens/currency_settings/models.cljs | 2 +- src/status_im/ui/screens/db.cljs | 2 - .../wallet/choose_recipient/events.cljs | 6 +- .../ui/screens/wallet/send/events.cljs | 19 +- .../ui/screens/wallet/settings/views.cljs | 2 +- src/status_im/utils/ethereum/contracts.cljs | 77 ---- src/status_im/utils/ethereum/core.cljs | 75 +-- src/status_im/utils/ethereum/erc20.cljs | 174 ------- src/status_im/wallet/core.cljs | 429 +++++++++--------- src/status_im/web3/core.cljs | 7 - src/status_im/web3/events.cljs | 4 - test/cljs/status_im/test/sign_in/flow.cljs | 7 +- .../ui/screens/currency_settings/models.cljs | 2 +- 25 files changed, 501 insertions(+), 716 deletions(-) create mode 100644 src/status_im/ethereum/contracts.cljs create mode 100644 src/status_im/ethereum/json_rpc.cljs delete mode 100644 src/status_im/utils/ethereum/contracts.cljs delete mode 100644 src/status_im/utils/ethereum/erc20.cljs diff --git a/src/status_im/accounts/login/core.cljs b/src/status_im/accounts/login/core.cljs index 44882bd2d69d..5a3d1d6f3263 100644 --- a/src/status_im/accounts/login/core.cljs +++ b/src/status_im/accounts/login/core.cljs @@ -85,9 +85,10 @@ (fx/defn initialize-wallet [cofx] (fx/merge cofx (wallet/initialize-tokens) + (wallet/update-balances) + (wallet/update-prices) (transactions/initialize) - (ethereum.subscriptions/initialize) - (wallet/update-wallet))) + (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/chat/commands/impl/transactions.cljs b/src/status_im/chat/commands/impl/transactions.cljs index fdd73ac25e82..160fefe46990 100644 --- a/src/status_im/chat/commands/impl/transactions.cljs +++ b/src/status_im/chat/commands/impl/transactions.cljs @@ -333,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/ethereum/contracts.cljs b/src/status_im/ethereum/contracts.cljs new file mode 100644 index 000000000000..517ca76fbbf0 --- /dev/null +++ b/src/status_im/ethereum/contracts.cljs @@ -0,0 +1,61 @@ +(ns status-im.ethereum.contracts + (:require [re-frame.core :as re-frame] + [status-im.utils.ethereum.abi-spec :as abi-spec] + [status-im.utils.ethereum.core :as ethereum] + [status-im.utils.fx :as fx] + [status-im.utils.money :as money] + [status-im.wallet.core :as wallet])) + +(def contracts + {:status/tribute-to-talk + {:address + {:mainnet nil + :testnet "0x3da3fc53e24707f36c5b4433b442e896c4955f0e" + :rinkeby nil} + :methods + {:get-manifest + {:signature "getManifest(address)" + :outputs ["bytes"]} + :set-manifest + {:signature "setManifest(bytes)" + :write? true}}}}) + +(re-frame/reg-fx + ::call + (fn [{:keys [address data callback]}] + (ethereum/call {:to address + :data data} + callback))) + +(defn get-contract-address + [db contract] + (let [chain-keyword (-> (get-in db [:account/account :networks (:network db)]) + ethereum/network->chain-keyword)] + (get-in contracts [contract :address chain-keyword]))) + +(fx/defn call + [{:keys [db] :as cofx} + {:keys [contract contract-address method params + callback on-result on-error details]}] + (when-let [contract-address (or contract-address + (get-contract-address db contract))] + (let [{:keys [signature outputs 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? outputs) + % + (abi-spec/decode % outputs)))}})))) diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs new file mode 100644 index 000000000000..38a83c3620bd --- /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/error :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 842f86717024..ac25fa915ffe 100644 --- a/src/status_im/ethereum/subscriptions.cljs +++ b/src/status_im/ethereum/subscriptions.cljs @@ -8,7 +8,8 @@ [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.fx :as fx] [status-im.utils.types :as types] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.ethereum.json-rpc :as json-rpc])) (fx/defn handle-signal [cofx {:keys [subscription_id data] :as event}] @@ -56,33 +57,14 @@ ;; from etherscan (transactions/initialize)))))) -(defn subscribe-signal - [filter params callback] - (status/call-private-rpc - (types/clj->json {: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]))))))) - (defn new-token-transaction-filter [{:keys [chain-tokens from to] :as args}] - (subscribe-signal - "eth_newFilter" - [{:fromBlock "latest" - :toBlock "latest" - :topics [constants/event-transfer-hash from to]}] - (transactions/inbound-token-transfer-handler chain-tokens))) + (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)})) (re-frame/reg-fx :ethereum.subscriptions/token-transactions @@ -95,13 +77,16 @@ (defn new-block-filter [] - (subscribe-signal - "eth_newBlockFilter" [] - (fn [[block-hash]] - (transactions/get-block-by-hash - block-hash - (fn [block] - (re-frame/dispatch [:ethereum.signal/new-block block])))))) + (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 diff --git a/src/status_im/ethereum/transactions/core.cljs b/src/status_im/ethereum/transactions/core.cljs index fe9abff8756b..3ad90681631e 100644 --- a/src/status_im/ethereum/transactions/core.cljs +++ b/src/status_im/ethereum/transactions/core.cljs @@ -1,68 +1,22 @@ (ns status-im.ethereum.transactions.core - (:require [clojure.string :as string] - [re-frame.core :as re-frame] + (: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.native-module.core :as status] - [status-im.utils.ethereum.core :as ethereum] [status-im.utils.fx :as fx] - [status-im.utils.types :as types] - [status-im.wallet.core :as wallet] - [taoensso.timbre :as log])) + [status-im.utils.money :as money] + [status-im.wallet.core :as wallet])) (def confirmations-count-threshold 12) -(defn get-block-by-hash - [block-hash callback] - (status/call-private-rpc - (types/clj->json {:jsonrpc "2.0" - :id 1 - :method "eth_getBlockByHash" - :params [block-hash true]}) - (fn [response] - (if (string/blank? response) - (log/warn :web3-response-error) - (callback (-> (.parse js/JSON response) - (js->clj :keywordize-keys true) - :result - (update :number decode/uint) - (update :timestamp decode/uint))))))) - -(defn get-transaction-by-hash - [transaction-hash callback] - (status/call-private-rpc - (types/clj->json {: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)))))) - -(defn get-transaction-receipt [transaction-hash callback] - (status/call-private-rpc - (types/clj->json {:jsonrpc "2.0" - :id 1 - :method "eth_getTransactionReceipt" - :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)))))) - (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- remove-padding [address] + {:pre [(string? address)]} + (str "0x" (subs address 26))) (def default-erc20-token {:symbol :ERC20 @@ -80,7 +34,7 @@ :symbol symbol :from (remove-padding from) :to (remove-padding to) - :value (ethereum/hex->bignumber data) + :value (money/bignumber data) :type direction :token token :error? false @@ -96,31 +50,33 @@ [chain-tokens {:keys [number timestamp]} {:keys [transfer direction hash gasPrice value gas from input nonce to] :as transaction}] - (get-transaction-receipt - hash - (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))}))]))))) + (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 @@ -153,28 +109,34 @@ ;; 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] - (get-block-by-hash - block-hash - (fn [{:keys [timestamp number]}] - (let [timestamp (str (* timestamp 1000))] - (doseq [{:keys [hash] :as transfer} block-transfers] - (get-transaction-by-hash - hash - (fn [{:keys [gasPrice gas input nonce]}] - (get-transaction-receipt - hash - (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))))]))))))))))))) + (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 @@ -184,7 +146,7 @@ [{:keys [db] :as cofx} {:keys [hash] :as transaction}] (fx/merge cofx {:db (assoc-in db [:wallet :transactions hash] transaction)} - wallet/update-wallet)) + wallet/update-balances)) (fx/defn handle-history [{:keys [db] :as cofx} transactions] @@ -192,7 +154,7 @@ {:db (update-in db [:wallet :transactions] #(merge transactions %))} - wallet/update-wallet)) + wallet/update-balances)) (fx/defn handle-token-history [{:keys [db]} transactions] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index c7238bff1c00..e9dc0d3e3ec6 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -974,11 +974,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 @@ -2138,7 +2133,7 @@ (handlers/register-handler-fx :wallet.ui/pull-to-refresh (fn [cofx _] - (wallet/update-wallet cofx))) + (wallet/update-prices cofx))) (handlers/register-handler-fx :wallet.transactions/add-filter @@ -2166,11 +2161,6 @@ (fn [cofx [_ symbol balance]] (wallet/configure-token-balance-and-visibility cofx symbol balance))) -(handlers/register-handler-fx - :TODO.remove/update-wallet - (fn [cofx _] - (wallet/update-wallet cofx))) - (handlers/register-handler-fx :wallet.settings.ui/navigate-back-pressed (fn [cofx [_ on-close]] @@ -2178,7 +2168,7 @@ (when on-close {:dispatch on-close}) (navigation/navigate-back) - (wallet/update-wallet)))) + (wallet/update-balances)))) (handlers/register-handler-fx :wallet.callback/update-balance-success @@ -2203,7 +2193,7 @@ (handlers/register-handler-fx :wallet.callback/update-prices-success (fn [cofx [_ prices]] - (wallet/update-prices cofx prices))) + (wallet/on-update-prices-success cofx prices))) (handlers/register-handler-fx :wallet.callback/update-prices-fail @@ -2228,9 +2218,9 @@ (handlers/register-handler-fx :TODO.remove/update-estimated-gas (fn [{:keys [db]} [_ obj]] - {:update-estimated-gas {:web3 (:web3 db) - :obj obj - :success-event :wallet/update-estimated-gas-success}})) + {:wallet/update-estimated-gas + {:obj obj + :success-event :wallet/update-estimated-gas-success}})) (handlers/register-handler-fx :wallet/update-estimated-gas-success diff --git a/src/status_im/node/core.cljs b/src/status_im/node/core.cljs index 2f16f4513fad..71d1212ca412 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 4d28b482a630..35f6630383bb 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/stickers/core.cljs b/src/status_im/stickers/core.cljs index 3039e2a4717f..8d83f36890e6 100644 --- a/src/status_im/stickers/core.cljs +++ b/src/status_im/stickers/core.cljs @@ -6,7 +6,6 @@ [status-im.ui.screens.navigation :as navigation] [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.stickers :as ethereum.stickers] [status-im.utils.fx :as fx] [status-im.utils.money :as money] @@ -74,13 +73,18 @@ (when on-result {:on-result on-result}) tx)) +(def snt-contracts + {:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e" + :testnet "0xc55cf4b03948d7ebc8b9e8bad92643703811d162" + :rinkeby nil}) + (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) + tx-object {:to (get snt-contracts chain) :data (abi-spec/encode "approveAndCall(address,uint256,bytes)" [stickers-contract price data])}] (wallet/open-modal-wallet-for-transaction db diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 7c86025c2966..626741af7835 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -1272,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)] diff --git a/src/status_im/tribute_to_talk/core.cljs b/src/status_im/tribute_to_talk/core.cljs index b33e26deddfb..50afcd941ae5 100644 --- a/src/status_im/tribute_to_talk/core.cljs +++ b/src/status_im/tribute_to_talk/core.cljs @@ -4,11 +4,11 @@ [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.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] [taoensso.timbre :as log])) diff --git a/src/status_im/ui/screens/currency_settings/models.cljs b/src/status_im/ui/screens/currency_settings/models.cljs index e39200bed411..2bf4462eb14c 100644 --- a/src/status_im/ui/screens/currency_settings/models.cljs +++ b/src/status_im/ui/screens/currency_settings/models.cljs @@ -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 6de9065be55f..bd666de8cfc0 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -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 @@ -234,7 +233,6 @@ :node/restart? :node/address :node/chain-sync-state - :node/latest-block-number :universal-links/url :push-notifications/stored :browser/browsers 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 72bd021dcb96..1b32c54734b7 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/send/events.cljs b/src/status_im/ui/screens/wallet/send/events.cljs index 53e753969f49..51ac18a92459 100644 --- a/src/status_im/ui/screens/wallet/send/events.cljs +++ b/src/status_im/ui/screens/wallet/send/events.cljs @@ -7,7 +7,6 @@ [status-im.transport.utils :as transport.utils] [status-im.ui.screens.navigation :as navigation] [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.erc20 :as erc20] [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.fx :as fx] [status-im.utils.handlers :as handlers] @@ -25,9 +24,17 @@ (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 + (merge (ethereum/call-params contract "transfer(address,uint256)" to value) + {:from from + :gas gas + :gasPrice gasPrice})) + (security/safe-unmask-data masked-password) + on-completed))) (re-frame/reg-fx ::send-transaction @@ -263,9 +270,9 @@ (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))) diff --git a/src/status_im/ui/screens/wallet/settings/views.cljs b/src/status_im/ui/screens/wallet/settings/views.cljs index a1f6f9fb5fa2..124da8d474c5 100644 --- a/src/status_im/ui/screens/wallet/settings/views.cljs +++ b/src/status_im/ui/screens/wallet/settings/views.cljs @@ -48,7 +48,7 @@ [action-button/action-button {:label (i18n/label :t/add-custom-token) :icon :main-icons/add :icon-opts {:color :blue} - :on-press #(re-frame/dispatch [:navigate-to :wallet-add-custom-token])}] + :on-press #(re-frame/dispatch [:wallet.settings.ui/navigate-back-pressed])}] [list/section-list {:sections (concat (when (seq custom-tokens) diff --git a/src/status_im/utils/ethereum/contracts.cljs b/src/status_im/utils/ethereum/contracts.cljs deleted file mode 100644 index 8cecd9f30a2d..000000000000 --- 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.utils.ethereum.abi-spec :as abi-spec] - [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.fx :as fx] - [status-im.utils.money :as money] - [status-im.wallet.core :as wallet])) - -(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 6e14acb55177..6d719a78c20c 100644 --- a/src/status_im/utils/ethereum/core.cljs +++ b/src/status_im/utils/ethereum/core.cljs @@ -1,12 +1,9 @@ (ns status-im.utils.ethereum.core (:require [clojure.string :as string] - [status-im.ethereum.decode :as decode] + [status-im.ethereum.json-rpc :as json-rpc] [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 @@ -65,6 +62,12 @@ 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))) @@ -127,77 +130,23 @@ (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")))))) + (json-rpc/call + {:method "eth_call" + :params [params "latest"] + :on-success callback})) (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/erc20.cljs b/src/status_im/utils/ethereum/erc20.cljs deleted file mode 100644 index 460218206167..000000000000 --- 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/wallet/core.cljs b/src/status_im/wallet/core.cljs index 9d89fbd367a9..62db8de2145b 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -3,13 +3,13 @@ [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.core :as ethereum] - [status-im.utils.ethereum.erc20 :as erc20] [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.fx :as fx] [status-im.utils.hex :as utils.hex] @@ -18,30 +18,6 @@ [status-im.utils.utils :as utils.utils] [taoensso.timbre :as log])) -(defn get-balance [{:keys [web3 account-id on-success on-error]}] - (if (and web3 account-id) - (.getBalance - (.-eth web3) - account-id - (fn [err resp] - (if-not err - (on-success resp) - (on-error err)))) - (on-error "web3 or account-id not available"))) - -(defn get-token-balance - [{:keys [web3 contract account-id on-success on-error]}] - (if (and web3 contract account-id) - (erc20/balance-of - web3 - contract - (ethereum/normalized-address account-id) - (fn [err resp] - (if-not err - (on-success resp) - (on-error err)))) - (on-error "web3, contract or account-id not available"))) - (defn assoc-error-message [db error-type err] (assoc-in db [:wallet :errors error-type] (or err :unknown-error))) @@ -83,27 +59,18 @@ ;; 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 + :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 - :get-prices + :wallet/get-prices (fn [{:keys [from to mainnet? success-event error-event chaos-mode?]}] (prices/get-prices from to @@ -113,91 +80,99 @@ 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?])))) + :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 - :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)))))) + :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 [{:keys [web3 tokens]}] + (fn [tokens] (doseq [token tokens] - (validate-token-decimals! web3 token) - (validate-token-symbol! web3 token) - (validate-token-name! web3 token)))) + (validate-token-decimals! token) + (validate-token-symbol! token) + (validate-token-name! token)))) (re-frame/reg-fx - :wallet/check-all-known-tokens-balance - (fn [{:keys [web3 contracts account]}] - (doseq [{:keys [address symbol]} contracts] - ;;TODO(goranjovic): move `get-token-balance` function to wallet models - (get-token-balance {:web3 web3 - :contract address - :account-id (:address account) - :on-error #(re-frame/dispatch [:update-token-balance-fail symbol %]) - :on-success #(when (> % 0) - (re-frame/dispatch [:wallet/token-found symbol %]))})))) - -(fx/defn wallet-autoconfig-tokens - [{:keys [db] :as cofx}] - (let [{:keys [account/account web3 network-status] :wallet/keys [all-tokens]} db - network (get (:networks account) (:network account)) - chain (ethereum/network->chain-keyword network) - contracts (->> (tokens/tokens-for all-tokens chain) - (remove :hidden?)) - settings (:settings account) - assets (get-in settings [:wallet :visible-tokens chain])] - (when-not (or (= network-status :offline) - assets) - (let [new-settings (assoc-in settings - [:wallet :visible-tokens chain] - #{})] - (fx/merge cofx - {:wallet/check-all-known-tokens-balance {:web3 web3 - :contracts contracts - :account account}} - (accounts.update/update-settings new-settings {})))))) + :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)) @@ -277,7 +252,8 @@ (assoc :nonce nonce)))) ;; SEND TRANSACTION -> RPC TRANSACTION -(defn prepare-send-transaction [from {:keys [amount to gas gas-price data nonce]}] +(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) @@ -288,9 +264,10 @@ 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] +(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) @@ -300,23 +277,29 @@ [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]} +(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])) + (assoc :dispatch (if keycard? + [:navigate-to :browser] + [:navigate-back])) (= method constants/web3-send-transaction) (assoc :dispatch [:navigate-to-clean :wallet-transaction-sent-modal]))) @@ -350,6 +333,9 @@ (fx/defn handle-transaction-error [{:keys [db] :as cofx} {:keys [code message]}] (let [{:keys [on-error]} (get-in db [:wallet :send-transaction])] + (log/error :wallet/transaction-error + :code code + :message message) (case code ;;WRONG PASSWORD constants/send-transaction-err-decrypt @@ -368,75 +354,111 @@ (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))))) +(defn tokens-symbols + [visible-token-symbols all-tokens chain] + (set/difference (set visible-token-symbols) + (set (map :symbol (tokens/nfts-for all-tokens chain))))) (fx/defn initialize-tokens [{:keys [db] :as cofx}] - (let [network-id (get-in db [:account/account :network]) - network (get-in db [:account/account :networks network-id]) - custom-tokens (get-in db [:account/account :settings :wallet :custom-tokens]) - chain (ethereum/network->chain-keyword network) + (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 {:web3 (:web3 db) - :tokens (get tokens/all-default-tokens chain)}})) - wallet-autoconfig-tokens))) - -(fx/defn update-wallet - [{{:keys [web3 network network-status] - {:keys [address settings]} :account/account :as db} :db}] - (let [all-tokens (:wallet/all-tokens db) - network (get-in db [:account/account :networks network]) - chain (ethereum/network->chain-keyword network) + (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? %) + (:nft? %))) + (filter #((or assets identity) (:symbol %))))] + (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 assets + (re-frame/dispatch + [:wallet.callback/update-token-balance-success symbol balance]) + ;; NOTE: when there is no visible assets set, + ;; 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 (get-in settings [:wallet :visible-tokens chain]) all-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) - {:get-balance {:web3 web3 - :account-id address - :success-event :wallet.callback/update-balance-success - :error-event :wallet.callback/update-balance-fail} - :get-tokens-balance {:web3 web3 - :account-id address - :symbols assets - :chain chain - :all-tokens all-tokens - :success-event :wallet.callback/update-token-balance-success - :error-event :wallet.callback/update-token-balance-fail} - :get-prices {:from (if mainnet? - (conj tokens "ETH") - [(-> (tokens/native-currency chain) - (wallet.utils/exchange-symbol))]) - :to [(:code currency)] - :mainnet? mainnet? - :success-event :wallet.callback/update-prices-success - :error-event :wallet.callback/update-prices-fail - :chaos-mode? (:chaos-mode? settings)} - :db (-> db - (clear-error-message :prices-update) - (clear-error-message :balance-update) - (assoc-in [:wallet :balance-loading?] true) - (assoc :prices-loading? true))}))) - -(defn open-modal-wallet-for-transaction [db transaction tx-object] + {: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 [[:TODO.remove/update-wallet] - (when-not gas + :dispatch-n [(when-not gas [:TODO.remove/update-estimated-gas tx-object]) (when-not gas-price [:wallet/update-gas-price]) @@ -444,9 +466,10 @@ (if wallet-set-up-passed? :wallet-send-modal-stack :wallet-send-modal-stack-with-onboarding)]]})) -2 + (fx/defn open-sign-transaction-flow - [{:keys [db] :as cofx} {:keys [gas gas-price] :as transaction}] + [{: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)] @@ -458,17 +481,14 @@ (assoc-in [:wallet :send-transaction :original-gas] gas))} (not gas) - (assoc :update-estimated-gas - {:web3 (:web3 db) - :obj (select-keys transaction [:to :data]) + (assoc :wallet/update-estimated-gas + {: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 + (assoc :wallet/update-gas-price + {: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 @@ -482,7 +502,7 @@ (conj (or ids #{}) id) (disj ids id))) -(fx/defn update-prices +(fx/defn on-update-prices-success [{:keys [db]} prices] {:db (assoc db :prices prices @@ -491,13 +511,13 @@ (fx/defn update-balance [{:keys [db]} balance] {:db (-> db - (assoc-in [:wallet :balance :ETH] balance) + (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] balance) + (assoc-in [:wallet :balance symbol] (money/bignumber balance)) (assoc-in [:wallet :balance-loading?] false))}) (fx/defn update-gas-price @@ -543,7 +563,8 @@ new-settings (assoc-in settings [:wallet :custom-tokens chain address] token)] (accounts.update/update-settings cofx new-settings {}))) -(fx/defn configure-token-balance-and-visibility [cofx symbol balance] +(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 diff --git a/src/status_im/web3/core.cljs b/src/status_im/web3/core.cljs index 25f5e9177ec8..514e81ac98f1 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 332446f0cb6a..b6b7ae51614f 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/sign_in/flow.cljs b/test/cljs/status_im/test/sign_in/flow.cljs index 7741d4f08716..44a3f5dac0e9 100644 --- a/test/cljs/status_im/test/sign_in/flow.cljs +++ b/test/cljs/status_im/test/sign_in/flow.cljs @@ -199,12 +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 :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 db40e6f114a4..3ff599bbb543 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]))))