diff --git a/src/status_im/ethereum/encode.cljs b/src/status_im/ethereum/encode.cljs new file mode 100644 index 0000000000..911e8beaf8 --- /dev/null +++ b/src/status_im/ethereum/encode.cljs @@ -0,0 +1,8 @@ +(ns status-im.ethereum.encode + (:require [status-im.js-dependencies :as dependencies])) + +(defn utils [] (dependencies/web3-utils)) + +(defn uint + [x] + (.numberToHex (utils) x)) diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index fe21dc6e58..b48a745678 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -35,7 +35,8 @@ "status_joinPublicChat" {} "status_chats" {} "status_startOneOnOneChat" {} - "status_removeChat" {}}) + "status_removeChat" {} + "wallet_getTransfers" {}}) (defn call [{:keys [method params on-success on-error]}] diff --git a/src/status_im/ethereum/subscriptions.cljs b/src/status_im/ethereum/subscriptions.cljs index 048ebd6b8f..252e061f8f 100644 --- a/src/status_im/ethereum/subscriptions.cljs +++ b/src/status_im/ethereum/subscriptions.cljs @@ -4,6 +4,7 @@ [status-im.ethereum.core :as ethereum] [status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.tokens :as tokens] + [status-im.wallet.db :as wallet] [status-im.ethereum.transactions.core :as transactions] [status-im.utils.fx :as fx] [taoensso.timbre :as log])) @@ -22,82 +23,37 @@ [{: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] :as cofx} {:keys [number transactions] :as block}] - (when number - (let [{:keys [:wallet/all-tokens :ethereum/current-block]} db - chain (ethereum/chain-keyword db) - chain-tokens (into {} (map (juxt :address identity) - (tokens/tokens-for all-tokens chain))) - wallet-address (ethereum/current-address db) - 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)}} - (transactions/check-watched-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)})) - -(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 - [] - (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 - new-block-filter) - -(fx/defn initialize - [{:keys [db] :as cofx}] + [{:keys [db] :as cofx} historical? block-number accounts] (let [{:keys [:wallet/all-tokens]} db chain (ethereum/chain-keyword db) chain-tokens (into {} (map (juxt :address identity) - (tokens/tokens-for all-tokens chain))) - normalized-address (ethereum/current-address db) - padded-address (transactions/add-padding normalized-address)] - {:ethereum.subscriptions/new-block nil - :ethereum.subscriptions/token-transactions - {:chain-tokens chain-tokens - :address padded-address}})) + (tokens/tokens-for all-tokens chain)))] + (fx/merge cofx + (cond-> {} + (not historical?) + (assoc :db (assoc db [:ethereum/current-block] block-number)) + + (not-empty accounts) + (assoc ::transactions/get-transfers {:chain-tokens chain-tokens + :from-block block-number})) + (transactions/check-watched-transactions)))) + +(fx/defn reorg + [{:keys [db] :as cofx} block-number accounts] + (let [{:keys [:wallet/all-tokens]} db + chain (ethereum/chain-keyword db) + chain-tokens (into {} (map (juxt :address identity) + (tokens/tokens-for all-tokens chain)))] + {:db (update-in db [:wallet :transactions] + wallet/remove-transactions-since-block block-number) + ::transactions/get-transfers {:chain-tokens chain-tokens + :from-block block-number}})) + +(fx/defn new-wallet-event + [{:keys [db] :as cofx} {:keys [type blockNumber accounts] :as event}] + (case type + "newblock" (new-block cofx false blockNumber accounts) + "history" (new-block cofx true blockNumber accounts) + "reorg" (reorg cofx blockNumber accounts) + (log/warn ::unknown-wallet-event :type type :event event))) diff --git a/src/status_im/ethereum/transactions/core.cljs b/src/status_im/ethereum/transactions/core.cljs index e89b9a6c4c..6d367611e0 100644 --- a/src/status_im/ethereum/transactions/core.cljs +++ b/src/status_im/ethereum/transactions/core.cljs @@ -2,14 +2,27 @@ (:require [re-frame.core :as re-frame] [status-im.constants :as constants] [status-im.ethereum.decode :as decode] + [status-im.ethereum.encode :as encode] [status-im.ethereum.json-rpc :as json-rpc] - [status-im.ethereum.transactions.etherscan :as transactions.etherscan] + [status-im.ethereum.core :as ethereum] + [status-im.ethereum.tokens :as tokens] [status-im.utils.fx :as fx] [status-im.utils.money :as money] [status-im.wallet.core :as wallet])) (def confirmations-count-threshold 12) +(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))))) + (defn add-padding [address] {:pre [(string? address)]} (str "0x000000000000000000000000" (subs address 2))) @@ -23,120 +36,61 @@ :decimals 18 :name "ERC20"}) +(defn direction + [address to] + (if (= address to) + :inbound + :outbound)) + (defn- parse-token-transfer - [chain-tokens direction transfer] + [chain-tokens 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))))) + {:hash transactionHash + :symbol symbol + :from (remove-padding from) + :to (remove-padding to) + :value (money/bignumber data) + :type (direction address to) + :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}))) -(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))}))])))})) +(defn enrich-transfer + [chain-tokens transfer] + (let [{:keys [address blockNumber timestamp type transaction receipt from]} transfer + {:keys [hash gasPrice value gas input nonce to]} transaction + {:keys [gasUsed logs]} receipt + erc20? (= type "erc20")] + (merge {:block (str (decode/uint blockNumber)) + :timestamp (* (decode/uint 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 erc20? + (parse-token-transfer chain-tokens (first logs)) + ;; this is not a ERC20 token transaction + {:hash hash + :symbol :ETH + :from from + :to to + :type (direction address to) + :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))))]))}))}))))}))))) +(defn enrich-transfers + [chain-tokens transfers] + (mapv (fn [transfer] + (enrich-transfer chain-tokens transfer)) + transfers)) ;; ----------------------------------------------- ;; transactions api @@ -172,17 +126,24 @@ (let [watched-transactions (select-keys (get-in db [:wallet :transactions]) (keys (get db :ethereum/watched-transactions)))] - (apply fx/merge cofx + (apply fx/merge + cofx (map (fn [[_ transaction]] (check-transaction transaction)) watched-transactions)))) -(fx/defn new - [{:keys [db] :as cofx} {:keys [hash] :as transaction}] +(fx/defn add-transfer + [{:keys [db] :as cofx} {:keys [hash] :as transfer}] (fx/merge cofx - {:db (assoc-in db [:wallet :transactions hash] transaction)} - (check-transaction transaction) - wallet/update-balances)) + {:db (assoc-in db [:wallet :transactions hash] transfer)} + (check-transaction transfer))) + +(fx/defn new-transfers + {:events [::new-transfers]} + [{:keys [db] :as cofx} transfers] + (let [add-transfers-fx (map add-transfer transfers)] + (apply fx/merge cofx (conj add-transfers-fx + wallet/update-balances)))) (fx/defn handle-history [{:keys [db] :as cofx} transactions] @@ -198,6 +159,23 @@ [:wallet :transactions] merge transactions)}) +(re-frame/reg-fx + ::get-transfers + (fn [{:keys [chain-tokens from-block to-block] + :or {from-block "0" + to-block nil}}] + ;; start inbound token transaction subscriptions + ;; outbound token transactions are already caught in new blocks filter + (json-rpc/call + {:method "wallet_getTransfers" + :params [(encode/uint from-block) (encode/uint to-block)] + :on-success #(re-frame/dispatch + [::new-transfers (enrich-transfers chain-tokens %)])}))) + (fx/defn initialize - [cofx] - (transactions.etherscan/fetch-history cofx)) + [{:keys [db] :as cofx}] + (let [{:keys [:wallet/all-tokens]} db + chain (ethereum/chain-keyword db) + chain-tokens (into {} (map (juxt :address identity) + (tokens/tokens-for all-tokens chain)))] + {::get-transfers {:chain-tokens chain-tokens}})) diff --git a/src/status_im/ethereum/transactions/etherscan.cljs b/src/status_im/ethereum/transactions/etherscan.cljs deleted file mode 100644 index 82605644b7..0000000000 --- a/src/status_im/ethereum/transactions/etherscan.cljs +++ /dev/null @@ -1,191 +0,0 @@ -(ns status-im.ethereum.transactions.etherscan - (:require [re-frame.core :as re-frame] - [status-im.ethereum.core :as ethereum] - [status-im.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 [:multiaccount :wallet/all-tokens]} db - chain (ethereum/chain-keyword db) - chain-tokens (into {} (map (juxt :address identity) - (tokens/tokens-for all-tokens chain))) - chaos-mode? (get-in multiaccount [:settings :chaos-mode?]) - normalized-address (ethereum/current-address db)] - #: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 110fbc1420..d079280a12 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -1835,11 +1835,6 @@ (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 @@ -1856,11 +1851,6 @@ (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 diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index d2d84df730..23f70909f6 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -88,8 +88,7 @@ (wallet/initialize-tokens) (wallet/update-balances) (wallet/update-prices) - (transactions/initialize) - (ethereum.subscriptions/initialize))) + (transactions/initialize))) (fx/defn user-login-without-creating-db {:events [:multiaccounts.login.ui/password-input-submitted]} diff --git a/src/status_im/node/core.cljs b/src/status_im/node/core.cljs index dd709fbd90..7b6016fd4f 100644 --- a/src/status_im/node/core.cljs +++ b/src/status_im/node/core.cljs @@ -120,6 +120,7 @@ :LightClient true :MinimumPoW 0.001 :EnableNTPSync true} + :WalletConfig {:Enabled true} :ShhextConfig {:BackupDisabledDataDir (utils.platform/no-backup-directory) :InstallationID installation-id :MaxMessageDeliveryAttempts config/max-message-delivery-attempts diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 1e91fa20a1..42db5792d8 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -83,4 +83,5 @@ "status.chats.did-change" (chat.loading/load-chats-from-rpc cofx) "whisper.filter.added" (transport.filters/handle-negotiated-filter cofx event) "messages.new" (transport.message/receive-messages cofx event) + "wallet" (ethereum.subscriptions/new-wallet-event cofx event) (log/debug "Event " type " not handled" event)))) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 21ce0233c0..68389374d0 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -14,7 +14,6 @@ [status-im.ethereum.stateofus :as stateofus] [status-im.ethereum.tokens :as tokens] [status-im.ethereum.transactions.core :as transactions] - [status-im.ethereum.transactions.etherscan :as transactions.etherscan] [status-im.fleet.core :as fleet] [status-im.group-chats.db :as group-chats.db] [status-im.i18n :as i18n] @@ -1166,12 +1165,12 @@ :<- [:contacts/contacts-by-address] :<- [:ethereum/native-currency] (fn [[transactions contacts native-currency]] - (reduce-kv (fn [acc hash transaction] - (assoc acc - hash - (enrich-transaction transaction contacts native-currency))) - {} - transactions))) + (reduce (fn [acc [hash transaction]] + (assoc acc + hash + (enrich-transaction transaction contacts native-currency))) + {} + transactions))) (re-frame/reg-sub :wallet.transactions/all-filters? @@ -1296,7 +1295,7 @@ (money/wei->str :eth (money/fee-value gas-used gas-price) native-currency-text)) - :url (transactions.etherscan/get-transaction-details-url + :url (transactions/get-transaction-details-url chain-keyword hash)})))))) diff --git a/src/status_im/utils/priority_map.cljs b/src/status_im/utils/priority_map.cljs index adfa162f35..7903406700 100644 --- a/src/status_im/utils/priority_map.cljs +++ b/src/status_im/utils/priority_map.cljs @@ -230,3 +230,6 @@ (def empty-message-map (priority-map-keyfn-by :clock-value >)) + +(def empty-transaction-map + (priority-map-keyfn-by :block #(< (int %1) (int %2)))) diff --git a/src/status_im/wallet/db.cljs b/src/status_im/wallet/db.cljs index 364a421bbb..f692a84af8 100644 --- a/src/status_im/wallet/db.cljs +++ b/src/status_im/wallet/db.cljs @@ -3,7 +3,8 @@ [status-im.i18n :as i18n] status-im.ui.screens.wallet.request.db status-im.ui.screens.wallet.send.db - [status-im.utils.money :as money])) + [status-im.utils.money :as money] + [status-im.utils.priority-map :refer [empty-transaction-map]])) (spec/def :wallet.send/recipient string?) @@ -65,10 +66,18 @@ #{:inbound :outbound :pending :failed}) (def default-wallet - {:filters default-wallet-filters}) + {:filters default-wallet-filters + :transactions empty-transaction-map}) (defn get-confirmations [{:keys [block]} current-block] (if (and current-block block) (inc (- current-block block)) 0)) + +(defn remove-transactions-since-block + [transactions block] + (into empty-transaction-map + (drop-while (fn [[k v]] + (>= (int (:block v)) block)) + transactions))) diff --git a/test/cljs/status_im/test/wallet/transactions.cljs b/test/cljs/status_im/test/wallet/transactions.cljs index 37941cc94b..02559fa1f3 100644 --- a/test/cljs/status_im/test/wallet/transactions.cljs +++ b/test/cljs/status_im/test/wallet/transactions.cljs @@ -1,7 +1,7 @@ (ns status-im.test.wallet.transactions (:require [cljs.test :refer-macros [deftest is]] [goog.Uri :as goog-uri] - [status-im.ethereum.transactions.etherscan :as transactions] + [status-im.ethereum.transactions.core :as transactions] [status-im.utils.http :as http])) (defn- uri-query-data [uri] @@ -24,94 +24,3 @@ (is (nil? (transactions/get-transaction-details-url :not-a-net "asdfasdfg"))) (is (thrown? js/Error (transactions/get-transaction-details-url nil "asdfasdfg"))) (is (thrown? js/Error (transactions/get-transaction-details-url :asdf 1)))) - -(deftest get-transaction-url - (is (= {:scheme "https", - :domain "api.etherscan.io", - :path "/api", - :query - {:module "account", - :action "txlist", - :address "0xasdfasdf", - :startblock "0", - :endblock "99999999", - :sort "desc", - :apikey "DMSI4UAAKUBVGCDMVP3H2STAMSAUV7BYFI", - :q "json"}} - (uri-query-data (transactions/get-transaction-url :mainnet "0xasdfasdf")))) - (is (= {:scheme "https", - :domain "api-rinkeby.etherscan.io", - :path "/api", - :query - {:module "account", - :action "txlist", - :address "0xasdfasdfg", - :startblock "0", - :endblock "99999999", - :sort "desc", - :apikey "DMSI4UAAKUBVGCDMVP3H2STAMSAUV7BYFI", - :q "json"}} - (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 "0xasdfasdfg")))) - -(declare mock-etherscan-success-response - mock-etherscan-error-response - mock-etherscan-empty-response) - -(deftest etherscan-history - (let [ky-set #{:block :hash :symbol :gas-price :value :gas-limit :type - :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-history - :mainnet - "0xasdfasdf" - #(reset! result %) - (fn [er]) - false) - (doseq [[tx-hash tx-map] @result] - (is (string? tx-hash)) - (is (= tx-hash (:hash tx-map))) - (is (keyword (:symbol tx-map))) - (is (= :outbound (:type tx-map))) - (is (every? identity - (map (partial get tx-map) - ky-set))) - (is (= (set (keys tx-map)) - ky-set)))))) - - (with-redefs [http/get (fn [url success-fn error-fn] - (success-fn mock-etherscan-empty-response))] - (let [result (atom nil)] - (transactions/etherscan-history - :mainnet - "0xasdfasdf" - #(reset! result %) - (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-history - :mainnet - "0xasdfasdf" - #(reset! result %) - (fn [er]) - false) - (is (= {} @result))))) - -(def mock-etherscan-error-response - "{\"status\":\"0\",\"message\":\"NOTOK\",\"result\":\"Error!\"}") - -(def mock-etherscan-empty-response - "{\"status\":\"0\",\"message\":\"No transactions found\",\"result\":[]}") - -(def mock-etherscan-success-response - "{\"status\":\"1\",\"message\":\"OK\",\"result\":[{\"blockNumber\":\"6662956\",\"timeStamp\":\"1541632935\",\"hash\":\"0x5899677055f3e4939b3878dc2dbc71b79cd6c2871a3aef61db25bad24c113258\",\"nonce\":\"3\",\"blockHash\":\"0x088411b630edf04bfc57dce6b70faf54dff917e1014aab7d703c3652acf7db1f\",\"transactionIndex\":\"92\",\"from\":\"0xa19536ed80c2a37e6925002e36c28de9c08737d3\",\"to\":\"0x744d70fdbe2ba4cf95131626614a1763df805b9e\",\"value\":\"0\",\"gas\":\"105000\",\"gasPrice\":\"3000000000\",\"isError\":\"0\",\"txreceipt_status\":\"1\",\"input\":\"0xa9059cbb000000000000000000000000e829f7947175fe6a338344e70aa770a8c134372c0000000000000000000000000000000000000000000000000de0b6b3a7640000\",\"contractAddress\":\"\",\"cumulativeGasUsed\":\"7482036\",\"gasUsed\":\"91130\",\"confirmations\":\"5334\"},{\"blockNumber\":\"6662619\",\"timeStamp\":\"1541628439\",\"hash\":\"0x3f2caac716c9bbd65ee76afa1df8e4ba3a1b3a15419316bdb31388bc5ad108b2\",\"nonce\":\"2\",\"blockHash\":\"0xff4c76a65c63ae4cb82e56dc94a4fb5c05aa9879ed1f3abbb0c103931613dbb2\",\"transactionIndex\":\"159\",\"from\":\"0xa19536ed80c2a37e6925002e36c28de9c08737d3\",\"to\":\"0x744d70fdbe2ba4cf95131626614a1763df805b9e\",\"value\":\"0\",\"gas\":\"105000\",\"gasPrice\":\"7260000000\",\"isError\":\"0\",\"txreceipt_status\":\"1\",\"input\":\"0xa9059cbb000000000000000000000000e829f7947175fe6a338344e70aa770a8c134372c0000000000000000000000000000000000000000000000000de0b6b3a7640000\",\"contractAddress\":\"\",\"cumulativeGasUsed\":\"6013584\",\"gasUsed\":\"91130\",\"confirmations\":\"5671\"},{\"blockNumber\":\"6606087\",\"timeStamp\":\"1540827293\",\"hash\":\"0x3ceecf0fc3cf5ef21081da06f4bf2e19dcfdfbb0672f529a1c56d791ad89c3d6\",\"nonce\":\"1\",\"blockHash\":\"0x5a4cd35c3a6cac19addb0e7e407b0fcdf33a16fc93e61442bc8fe494a9f8ccb9\",\"transactionIndex\":\"34\",\"from\":\"0xa19536ed80c2a37e6925002e36c28de9c08737d3\",\"to\":\"0x167c7c3d434315e4415eb802f0beb9ea44cd1546\",\"value\":\"0\",\"gas\":\"1500058\",\"gasPrice\":\"5000000000\",\"isError\":\"0\",\"txreceipt_status\":\"1\",\"input\":\"0x7055d368000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ce66c50e28400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015af1d78b58c4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ce66c50e28400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f399b1438a1000000000000000000000000000000000000000000000000000003782dace9d900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"contractAddress\":\"\",\"cumulativeGasUsed\":\"5272966\",\"gasUsed\":\"1400058\",\"confirmations\":\"62203\"},{\"blockNumber\":\"6600328\",\"timeStamp\":\"1540745424\",\"hash\":\"0x138ca44da0062fb35e391e18f2728f383848c92cf8a4181484006a49729f7047\",\"nonce\":\"115\",\"blockHash\":\"0x3a72a3fa8f19f5d4d1f37531f7d6fb4e2ac8cbac41660dc40221b1625031e7af\",\"transactionIndex\":\"28\",\"from\":\"0xb81791767a4a4ba1b48d57872212132f000f98c9\",\"to\":\"0xa19536ed80c2a37e6925002e36c28de9c08737d3\",\"value\":\"25000000000000000\",\"gas\":\"50000\",\"gasPrice\":\"10000000000\",\"isError\":\"0\",\"txreceipt_status\":\"1\",\"input\":\"0x\",\"contractAddress\":\"\",\"cumulativeGasUsed\":\"1364725\",\"gasUsed\":\"21000\",\"confirmations\":\"67962\"},{\"blockNumber\":\"6582343\",\"timeStamp\":\"1540491006\",\"hash\":\"0x0a18798662544c4988ad914eca56af93326a6ba94be64263d823916f6eb0b032\",\"nonce\":\"0\",\"blockHash\":\"0x54d3c496cdea962adcf9fb924ba77ee116e0d0f3249a7c484090ef937cedcac8\",\"transactionIndex\":\"71\",\"from\":\"0xa19536ed80c2a37e6925002e36c28de9c08737d3\",\"to\":\"0x744d70fdbe2ba4cf95131626614a1763df805b9e\",\"value\":\"0\",\"gas\":\"703286\",\"gasPrice\":\"8000000000\",\"isError\":\"0\",\"txreceipt_status\":\"1\",\"input\":\"0xcae9ca51000000000000000000000000db5ac1a559b02e12f29fc0ec0e37be8e046def490000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084b82fedbb864f2278d86348afb072d601c38419f882977f16ff5c4152359b7b5013bfd533000000000000000000000000a19536ed80c2a37e6925002e36c28de9c08737d3850ed9944dec60f189d04f34a0a2079c1c8c3d6a3ced837df0667f0d2e62a28128635b33d56041ba7f1fae254df20a2378f096a739e2e2871327c9c177fd0a2d00000000000000000000000000000000000000000000000000000000\",\"contractAddress\":\"\",\"cumulativeGasUsed\":\"5085753\",\"gasUsed\":\"332228\",\"confirmations\":\"85947\"},{\"blockNumber\":\"6500995\",\"timeStamp\":\"1539344556\",\"hash\":\"0x2184fa04f3fc32f942b851c7e15d1fb5997e8c6c6b5871e110529cecb75f8972\",\"nonce\":\"16\",\"blockHash\":\"0x2266dc4e76c06fbf16b701bdac9269cec043ad9d1368166b584aaf2ec6fb5f68\",\"transactionIndex\":\"34\",\"from\":\"0x7b80e03797009f4c545cfb85407931272e34962b\",\"to\":\"0xa19536ed80c2a37e6925002e36c28de9c08737d3\",\"value\":\"50000000000000000\",\"gas\":\"21000\",\"gasPrice\":\"3000000000\",\"isError\":\"0\",\"txreceipt_status\":\"1\",\"input\":\"0x\",\"contractAddress\":\"\",\"cumulativeGasUsed\":\"7854654\",\"gasUsed\":\"21000\",\"confirmations\":\"167295\"}]}")