From a1ecfa70baa4b8e41af596e2fb90e051ebae3cd9 Mon Sep 17 00:00:00 2001 From: yenda Date: Mon, 20 May 2019 02:21:38 +0200 Subject: [PATCH] [refactor] remove ethereum `call` and `call-params` - use `json-rpc/eth-call` and `json-rpc/eth-transaction-call` everywhere - move all conversions to abi-spec --- src/status_im/ethereum/contracts.cljs | 64 +---- src/status_im/stickers/core.cljs | 258 ++++++++++-------- src/status_im/subs.cljs | 4 +- src/status_im/tribute_to_talk/core.cljs | 66 +++-- .../ui/screens/add_new/new_chat/events.cljs | 5 +- .../ui/screens/chat/stickers/views.cljs | 6 +- src/status_im/ui/screens/db.cljs | 6 +- .../ui/screens/wallet/send/events.cljs | 22 +- src/status_im/utils/ethereum/abi_spec.cljs | 3 + src/status_im/utils/ethereum/core.cljs | 70 +---- src/status_im/utils/ethereum/eip165.cljs | 17 +- src/status_im/utils/ethereum/ens.cljs | 122 +++++---- src/status_im/utils/ethereum/erc721.cljs | 31 ++- src/status_im/utils/ethereum/resolver.cljs | 1 - src/status_im/utils/ethereum/stickers.cljs | 34 --- src/status_im/wallet/core.cljs | 70 ++--- src/status_im/wallet/custom_tokens/core.cljs | 199 +++++++++----- .../status_im/test/utils/ethereum/core.cljs | 12 +- 18 files changed, 509 insertions(+), 481 deletions(-) delete mode 100644 src/status_im/utils/ethereum/stickers.cljs diff --git a/src/status_im/ethereum/contracts.cljs b/src/status_im/ethereum/contracts.cljs index 517ca76fbbf..47fa87a71c4 100644 --- a/src/status_im/ethereum/contracts.cljs +++ b/src/status_im/ethereum/contracts.cljs @@ -1,61 +1,17 @@ (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])) + (:require [status-im.utils.ethereum.core :as ethereum])) (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}}}}) + {:status/snt + {:mainnet "0x744d70fdbe2ba4cf95131626614a1763df805b9e" + :testnet "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"} + :status/tribute-to-talk + {:testnet "0x3da3fc53e24707f36c5b4433b442e896c4955f0e"} + :status/stickers + {:testnet "0x39d16CdB56b5a6a89e1A397A13Fe48034694316E"}}) -(re-frame/reg-fx - ::call - (fn [{:keys [address data callback]}] - (ethereum/call {:to address - :data data} - callback))) - -(defn get-contract-address +(defn get-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)))}})))) + (get-in contracts [contract chain-keyword]))) diff --git a/src/status_im/stickers/core.cljs b/src/status_im/stickers/core.cljs index 8d83f36890e..8a7ec3ee010 100644 --- a/src/status_im/stickers/core.cljs +++ b/src/status_im/stickers/core.cljs @@ -3,22 +3,94 @@ [re-frame.core :as re-frame] [status-im.accounts.core :as accounts] [status-im.constants :as constants] + [status-im.ethereum.contracts :as contracts] + [status-im.ethereum.json-rpc :as json-rpc] [status-im.ui.screens.navigation :as navigation] [status-im.utils.ethereum.abi-spec :as abi-spec] [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.stickers :as ethereum.stickers] [status-im.utils.fx :as fx] - [status-im.utils.money :as money] [status-im.utils.multihash :as multihash] + [status-im.utils.utils :as utils] [status-im.wallet.core :as wallet])) -(fx/defn init-stickers-packs [{:keys [db]}] +(defn pack-data-callback + [id open?] + (fn [[category owner mintable timestamp price contenthash]] + (let [proto-code (subs contenthash 2 4) + hash (when contenthash + (multihash/base58 (multihash/create :sha2-256 (subs contenthash 12))))] + (when (and (#{constants/swarm-proto-code constants/ipfs-proto-code} + proto-code) hash) + (re-frame/dispatch [:stickers/load-pack proto-code hash id price open?]))))) + +(re-frame/reg-fx + :stickers/set-pending-timout-fx + (fn [] + (utils/set-timeout #(re-frame/dispatch [:stickers/pending-timout]) + 10000))) + +(defn eth-call-pack-data + [contract id open?] + (json-rpc/eth-call + {:contract contract + ;; Returns vector of pack data parameters by pack id: + ;; [category owner mintable timestamp price contenthash] + :method "getPackData(uint256)" + :params [id] + :outputs ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"] + :on-success (pack-data-callback id open?)})) + +(re-frame/reg-fx + :stickers/pack-data-fx + (fn [[contract id]] + (eth-call-pack-data contract id true))) + +(re-frame/reg-fx + :stickers/load-packs-fx + (fn [[contract]] + (json-rpc/eth-call + {:contract contract + ;; Returns number of packs registered in the contract + :method "packCount()" + :outputs ["uint256"] + :on-success + (fn [[count]] + (dotimes [id count] + (eth-call-pack-data contract id false)))}))) + +(re-frame/reg-fx + :stickers/owned-packs-fx + (fn [[contract address]] + (json-rpc/eth-call + {:contract contract + ;; Returns vector of owned tokens ids in the contract by address + :method "tokensOwnedBy(address)" + :params [address] + :outputs ["uint256[]"] + :on-success + (fn [[tokens]] + (doseq [id tokens] + (json-rpc/eth-call + {:contract contract + ;; Returns pack id in the contract by token id + :method "tokenPackId(uint256)" + :params [id] + :outputs ["uint256"] + :on-success + (fn [[pack-id]] + (re-frame/dispatch [:stickers/pack-owned pack-id]))})))}))) + +(fx/defn init-stickers-packs + [{:keys [db]}] (let [sticker-packs (into {} (map #(let [pack (edn/read-string %)] (vector (:id pack) pack)) (get-in db [:account/account :stickers])))] - {:db (assoc db :stickers/packs-installed sticker-packs :stickers/packs sticker-packs)})) + {:db (assoc db + :stickers/packs-installed sticker-packs + :stickers/packs sticker-packs)})) -(fx/defn install-stickers-pack [{{:account/keys [account] :as db} :db :as cofx} id] +(fx/defn install-stickers-pack + [{{:account/keys [account] :as db} :db :as cofx} id] (let [pack (get-in db [:stickers/packs id])] (fx/merge cofx @@ -27,125 +99,91 @@ (assoc :stickers/selected-pack id))} (accounts/update-stickers (conj (:stickers account) (pr-str pack)))))) -(fx/defn load-sticker-pack-success [{:keys [db] :as cofx} edn-string id price open?] +(fx/defn load-sticker-pack-success + [{:keys [db] :as cofx} edn-string id price open?] (let [pack (assoc (get (edn/read-string edn-string) 'meta) :id id :price price)] (fx/merge cofx - {:db (-> db (assoc-in [:stickers/packs id] pack))} - #(when open? (navigation/navigate-to-cofx % :stickers-pack-modal pack))))) - -(defn pack-data-callback [id open?] - (fn [[category owner mintable timestamp price contenthash]] - (let [proto-code (subs contenthash 2 4) - hash (when contenthash (multihash/base58 (multihash/create :sha2-256 (subs contenthash 12))))] - (when (and (#{constants/swarm-proto-code constants/ipfs-proto-code} proto-code) hash) - (re-frame/dispatch [:stickers/load-pack proto-code hash id price open?]))))) + {:db (assoc-in db [:stickers/packs id] pack)} + #(when open? + (navigation/navigate-to-cofx % :stickers-pack-modal pack))))) (fx/defn open-sticker-pack - [{{:keys [network] :stickers/keys [packs packs-installed] :as db} :db :as cofx} id] + [{{:stickers/keys [packs packs-installed] :as db} :db :as cofx} id] (when id - (let [pack (or (get packs-installed id) (get packs id)) - network (get-in db [:account/account :networks network])] + (let [pack (or (get packs-installed id) + (get packs id)) + contract-address (contracts/get-address db :status/stickers)] (if pack (navigation/navigate-to-cofx cofx :stickers-pack-modal pack) - {:stickers/pack-data-fx [network id true]})))) - -(fx/defn load-pack [cofx proto-code hash id price open?] - {:http-get {:url (str (if (= constants/swarm-proto-code proto-code) - "https://swarm-gateways.net/bzz:/" - "https://ipfs.infura.io/ipfs/") - hash) - :success-event-creator (fn [o] - [:stickers/load-sticker-pack-success o id price open?]) - :failure-event-creator (constantly nil)}}) - -(fx/defn load-packs [{{:keys [network] :as db} :db}] - (let [network (get-in db [:account/account :networks network]) - address (ethereum/normalized-address (get-in db [:account/account :address]))] - {:stickers/owned-packs-fx [network address] - :stickers/load-packs-fx [network]})) - -(defn prepare-transaction [id tx on-result] - (merge {:id id - :symbol :ETH - :method constants/web3-send-transaction - :amount (money/bignumber 0)} - (when on-result {:on-result on-result}) - tx)) - -(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 snt-contracts chain) - :data (abi-spec/encode "approveAndCall(address,uint256,bytes)" [stickers-contract price data])}] - (wallet/open-modal-wallet-for-transaction - db - (prepare-transaction "approve" tx-object [:stickers/pending-pack pack-id]) - tx-object))) + (when contract-address + {:stickers/pack-data-fx [contract-address id]}))))) + +(fx/defn load-pack + [cofx proto-code hash id price open?] + {:http-get {:url (str (if (= constants/swarm-proto-code proto-code) + "https://swarm-gateways.net/bzz:/" + "https://ipfs.infura.io/ipfs/") + hash) + :success-event-creator + (fn [o] + [:stickers/load-sticker-pack-success o id price open?]) + :failure-event-creator + (constantly nil)}}) + +(fx/defn load-packs + [{:keys [db]}] + (let [contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + {:stickers/owned-packs-fx [contract address] + :stickers/load-packs-fx [contract]}))) + +(fx/defn approve-pack + [{db :db :as cofx} pack-id price] + (let [address (ethereum/current-address db) + chain (ethereum/chain-keyword db) + stickers-contract (contracts/get-address db :status/stickers) + snt-contract (contracts/get-address db :status/snt)] + (wallet/eth-transaction-call + cofx + {:contract snt-contract + :method "approveAndCall(address,uint256,bytes)" + :params [stickers-contract + price + (abi-spec/encode "buyToken(uint256,address)" + [pack-id address])] + :on-result [:stickers/pending-pack pack-id]}))) (fx/defn pending-pack - [{{:keys [network] :as db} :db :as cofx} id] - (let [network (get-in db [:account/account :networks network]) - address (ethereum/normalized-address (get-in db [:account/account :address]))] - (fx/merge cofx - {:db (update db :stickers/packs-pendning conj id) - :stickers/owned-packs-fx [network address]} - (navigation/navigate-to-clean :wallet-transaction-sent-modal {}) - #(when (zero? (count (:stickers/packs-pendning db))) - {:stickers/set-pending-timout-fx nil})))) + [{:keys [db] :as cofx} id] + (let [contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + (fx/merge cofx + {:db (update db :stickers/packs-pending conj id) + :stickers/owned-packs-fx [contract address]} + (navigation/navigate-to-clean :wallet-transaction-sent-modal {}) + #(when (zero? (count (:stickers/packs-pending db))) + {:stickers/set-pending-timout-fx nil}))))) (fx/defn pending-timeout - [{{:keys [network] :stickers/keys [packs-pendning packs-owned] :as db} :db}] - (let [packs-diff (clojure.set/difference packs-pendning packs-owned) - network (get-in db [:account/account :networks network]) - address (ethereum/normalized-address (get-in db [:account/account :address]))] - (merge {:db (assoc db :stickers/packs-pendning packs-diff)} - (when-not (zero? (count packs-diff)) - {:stickers/owned-packs-fx [network address] - :stickers/set-pending-timout-fx nil})))) + [{{:stickers/keys [packs-pending packs-owned] :as db} :db}] + (let [packs-diff (clojure.set/difference packs-pending packs-owned) + contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + (merge {:db (assoc db :stickers/packs-pending packs-diff)} + (when-not (zero? (count packs-diff)) + {:stickers/owned-packs-fx [contract address] + :stickers/set-pending-timout-fx nil}))))) (fx/defn pack-owned [{db :db} id] {:db (update db :stickers/packs-owned conj id)}) (fx/defn get-owned-pack - [{{:keys [network] :as db} :db}] - (let [address (ethereum/normalized-address (get-in db [:account/account :address]))] - {:stickers/owned-packs-fx [network address]})) - -(re-frame/reg-fx - :stickers/pack-data-fx - (fn [[network id open?]] - (when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))] - (ethereum.stickers/pack-data contract id (pack-data-callback id open?))))) - -(re-frame/reg-fx - :stickers/set-pending-timout-fx - (fn [] - (js/setTimeout #(re-frame/dispatch [:stickers/pending-timout]) 10000))) - -(re-frame/reg-fx - :stickers/load-packs-fx - (fn [[network]] - (when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))] - (ethereum.stickers/pack-count contract - (fn [count] - (dotimes [n count] - (ethereum.stickers/pack-data contract n (pack-data-callback n false)))))))) - -(re-frame/reg-fx - :stickers/owned-packs-fx - (fn [[network address]] - (when-let [contract (get ethereum.stickers/contracts (ethereum/network->chain-keyword network))] - (ethereum.stickers/owned-tokens contract address - (fn [tokens] - (doseq [n tokens] - (ethereum.stickers/token-pack-id contract n - #(re-frame/dispatch [:stickers/pack-owned %])))))))) + [{:keys [db]}] + (let [contract (contracts/get-address db :status/stickers) + address (ethereum/current-address db)] + (when contract + {:stickers/owned-packs-fx [contract address]}))) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 626741af783..40eeba2b896 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -125,7 +125,7 @@ (reg-root-key-sub :stickers/packs :stickers/packs) (reg-root-key-sub :stickers/installed-packs :stickers/packs-installed) (reg-root-key-sub :stickers/packs-owned :stickers/packs-owned) -(reg-root-key-sub :stickers/packs-pendning :stickers/packs-pendning) +(reg-root-key-sub :stickers/packs-pending :stickers/packs-pending) ;;mailserver (reg-root-key-sub :mailserver/current-id :mailserver/current-id) @@ -767,7 +767,7 @@ :<- [:stickers/packs] :<- [:stickers/installed-packs] :<- [:stickers/packs-owned] - :<- [:stickers/packs-pendning] + :<- [:stickers/packs-pending] (fn [[packs installed owned pending]] (map (fn [{:keys [id] :as pack}] (cond-> pack diff --git a/src/status_im/tribute_to_talk/core.cljs b/src/status_im/tribute_to_talk/core.cljs index 50afcd941ae..cf8bd80cfc7 100644 --- a/src/status_im/tribute_to_talk/core.cljs +++ b/src/status_im/tribute_to_talk/core.cljs @@ -5,12 +5,14 @@ [status-im.accounts.update.core :as accounts.update] [status-im.contact.db :as contact.db] [status-im.ethereum.contracts :as contracts] + [status-im.ethereum.json-rpc :as json-rpc] [status-im.ipfs.core :as ipfs] [status-im.tribute-to-talk.db :as tribute-to-talk.db] [status-im.ui.screens.navigation :as navigation] [status-im.utils.contenthash :as contenthash] [status-im.utils.ethereum.core :as ethereum] [status-im.utils.fx :as fx] + [status-im.wallet.core :as wallet] [taoensso.timbre :as log])) (fx/defn update-settings @@ -195,25 +197,35 @@ [:tribute-to-talk.callback/fetch-manifest-success identity manifest])))})) +(re-frame/reg-fx + :tribute-to-talk/get-manifest + (fn [{:keys [contract address on-success]}] + (json-rpc/eth-call + {:contract contract + :method "getManifest(address)" + :params [address] + :outputs ["bytes"] + :on-success on-success}))) + (fx/defn check-manifest - [{:keys [db] :as cofx} identity] - (or (contracts/call cofx - {:contract :status/tribute-to-talk - :method :get-manifest - :params [(contact.db/public-key->address identity)] - :return-params ["bytes"] - :callback - #(re-frame/dispatch - (if-let [contenthash (first %)] - [:tribute-to-talk.callback/check-manifest-success - identity - contenthash] - [:tribute-to-talk.callback/no-manifest-found identity]))}) - ;; `contracts/call` returns nil if there is no contract for the current network - ;; update settings if checking own manifest or do nothing otherwise - (when-let [me? (= identity - (get-in cofx [:db :account/account :public-key]))] - (update-settings cofx nil)))) + [{:keys [db] :as cofx} public-key] + (if-let [contract (contracts/get-address db :status/tribute-to-talk)] + (let [address (contact.db/public-key->address public-key)] + {:tribute-to-talk/get-manifest + {:contract contract + :address address + :on-success + (fn [[contenthash]] + (re-frame/dispatch + (if contenthash + [:tribute-to-talk.callback/check-manifest-success + public-key + contenthash] + [:tribute-to-talk.callback/no-manifest-found public-key])))}}) + ;; update settings if checking own manifest or do nothing otherwise + (when-let [me? (= identity + (get-in cofx [:db :account/account :public-key]))] + (update-settings cofx nil)))) (fx/defn check-own-manifest [cofx] @@ -224,13 +236,17 @@ (let [contenthash (when hash (contenthash/encode {:hash hash :namespace :ipfs}))] - (or (contracts/call cofx - {:contract :status/tribute-to-talk - :method :set-manifest - :params [contenthash] - :on-result [:tribute-to-talk.callback/set-manifest-transaction-completed] - :on-error [:tribute-to-talk.callback/set-manifest-transaction-failed]}) - {:db (assoc-in db [:navigation/screen-params :tribute-to-talk :state] :transaction-failed)}))) + (if-let [contract (contracts/get-address db :status/tribute-to-talk)] + (wallet/eth-transaction-call + cofx + {:contract contract + :method "setManifest(bytes)" + :params [contenthash] + :on-result [:tribute-to-talk.callback/set-manifest-transaction-completed] + :on-error [:tribute-to-talk.callback/set-manifest-transaction-failed]}) + {:db (assoc-in db + [:navigation/screen-params :tribute-to-talk :state] + :transaction-failed)}))) (defn remove [{:keys [db] :as cofx}] diff --git a/src/status_im/ui/screens/add_new/new_chat/events.cljs b/src/status_im/ui/screens/add_new/new_chat/events.cljs index 2c0c869b5d2..c2c4edc6c4f 100644 --- a/src/status_im/ui/screens/add_new/new_chat/events.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/events.cljs @@ -14,15 +14,14 @@ (handlers/register-handler-fx :new-chat/set-new-identity - (fn [{{:keys [network network-status] :as db} :db} [_ new-identity]] + (fn [{{:keys [network-status] :as db} :db} [_ new-identity]] (let [is-public-key? (and (string? new-identity) (string/starts-with? new-identity "0x"))] (merge {:db (assoc db :contacts/new-identity new-identity :contacts/new-identity-error (db/validate-pub-key db new-identity))} (when-not is-public-key? - (let [network (get-in db [:account/account :networks network]) - chain (ethereum/network->chain-keyword network)] + (let [chain (ethereum/chain-keyword db)] {:resolve-public-key {:registry (get ens/ens-registries chain) :ens-name (if (ens/is-valid-eth-name? new-identity) new-identity diff --git a/src/status_im/ui/screens/chat/stickers/views.cljs b/src/status_im/ui/screens/chat/stickers/views.cljs index 80221720086..b261ab8cfed 100644 --- a/src/status_im/ui/screens/chat/stickers/views.cljs +++ b/src/status_im/ui/screens/chat/stickers/views.cljs @@ -93,7 +93,8 @@ [stickers-panel (map #(assoc % :pack id) stickers) window-width])])) (defn pack-icon [{:keys [id on-press background-color] - :or {on-press #(re-frame/dispatch [:stickers/select-pack id])}} icon] + :or {on-press #(re-frame/dispatch [:stickers/select-pack id])}} + icon] [react/touchable-highlight {:on-press on-press} [react/view {:style {:align-items :center}} [react/view {:style (styles/pack-icon background-color icon-size icon-horizontal-margin)} @@ -148,7 +149,8 @@ [vector-icons/icon :stickers-icons/recent {:color colors/gray}]] (for [{:keys [id thumbnail]} installed-packs] ^{:key id} - [pack-icon {:id id} + [pack-icon {:id id + :background-color colors/white} [react/image {:style {:width icon-size :height icon-size :border-radius (/ icon-size 2)} :source {:uri thumbnail}}]])] [scroll-indicator]]]]])) diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index bd666de8cfc..f65765c9db3 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -61,7 +61,7 @@ :push-notifications/stored {} :registry {} :stickers/packs-owned #{} - :stickers/packs-pendning #{} + :stickers/packs-pending #{} :hardwallet {:nfc-supported? false :nfc-enabled? false :pin {:original [] @@ -192,7 +192,7 @@ (spec/def :stickers/packs (spec/nilable map?)) (spec/def :stickers/packs-owned (spec/nilable set?)) -(spec/def :stickers/packs-pendning (spec/nilable set?)) +(spec/def :stickers/packs-pending (spec/nilable set?)) (spec/def :stickers/packs-installed (spec/nilable map?)) (spec/def :stickers/selected-pack (spec/nilable any?)) (spec/def :stickers/recent (spec/nilable vector?)) @@ -269,7 +269,7 @@ :stickers/selected-pack :stickers/recent :stickers/packs-owned - :stickers/packs-pendning + :stickers/packs-pending :bottom-sheet/show? :bottom-sheet/view :extensions/profile diff --git a/src/status_im/ui/screens/wallet/send/events.cljs b/src/status_im/ui/screens/wallet/send/events.cljs index 51ac18a9245..cec87137a7c 100644 --- a/src/status_im/ui/screens/wallet/send/events.cljs +++ b/src/status_im/ui/screens/wallet/send/events.cljs @@ -6,6 +6,7 @@ [status-im.native-module.core :as status] [status-im.transport.utils :as transport.utils] [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.tokens :as tokens] [status-im.utils.fx :as fx] @@ -29,10 +30,13 @@ {:keys [from to value gas gasPrice]} on-completed masked-password] (let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))] (status/send-transaction (types/clj->json - (merge (ethereum/call-params contract "transfer(address,uint256)" to value) - {:from from - :gas gas - :gasPrice gasPrice})) + {:to contract + :from from + :data (abi-spec/encode + "transfer(address,uint256)" + [to value]) + :gas gas + :gasPrice gasPrice}) (security/safe-unmask-data masked-password) on-completed))) @@ -66,9 +70,10 @@ (handlers/register-handler-fx :wallet/send-transaction (fn [{{:keys [chain] :as db} :db} _] - (let [{:keys [password symbol in-progress?] :as transaction} (get-in db [:wallet :send-transaction]) + (let [{:keys [password symbol in-progress?] :as transaction} + (get-in db [:wallet :send-transaction]) all-tokens (:wallet/all-tokens db) - from (get-in db [:account/account :address])] + from (ethereum/current-address db)] (when-not in-progress? {:db (-> db (assoc-in [:wallet :send-transaction :wrong-password?] false) @@ -374,8 +379,9 @@ (wallet/prepare-send-transaction from transaction) (let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol)) {:keys [gas gasPrice to from value]} (wallet/prepare-send-transaction from transaction)] - (merge (ethereum/call-params contract "transfer(address,uint256)" to value) - {:from from + (merge (abi-spec/encode "transfer(address,uint256)" [to value]) + {:to contract + :from from :gas gas :gasPrice gasPrice})))) diff --git a/src/status_im/utils/ethereum/abi_spec.cljs b/src/status_im/utils/ethereum/abi_spec.cljs index fd671a1015d..2fe587909ea 100644 --- a/src/status_im/utils/ethereum/abi_spec.cljs +++ b/src/status_im/utils/ethereum/abi_spec.cljs @@ -28,6 +28,9 @@ (when x (subs (.fromUtf8 utils x) 2))) +(defn hex-to-boolean [x] + (= x "0x0")) + (defn bytes-to-hex [x] (when x (subs (.bytesToHex utils x) 2))) diff --git a/src/status_im/utils/ethereum/core.cljs b/src/status_im/utils/ethereum/core.cljs index 6d719a78c20..55cb8125413 100644 --- a/src/status_im/utils/ethereum/core.cljs +++ b/src/status_im/utils/ethereum/core.cljs @@ -1,6 +1,5 @@ (ns status-im.utils.ethereum.core (:require [clojure.string :as string] - [status-im.ethereum.json-rpc :as json-rpc] [status-im.js-dependencies :as dependencies] [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.money :as money])) @@ -40,6 +39,10 @@ address (str hex-prefix address)))) +(defn current-address [db] + (-> (get-in db [:account/account :address]) + normalized-address)) + (defn naked-address [s] (when s (string/replace s hex-prefix ""))) @@ -74,71 +77,6 @@ ([s opts] (.sha3 dependencies/Web3.prototype (str s) (clj->js opts)))) -(defn hex->string [s] - (when s - (let [hex (.toString s)] - (loop [res "" i (if (string/starts-with? hex hex-prefix) 2 0)] - (if (and (< i (.-length hex))) - (recur - (if (= (.substr hex i 2) "00") - res - (str res (.fromCharCode js/String (js/parseInt (.substr hex i 2) 16)))) - (+ i 2)) - res))))) - -(defn hex->boolean [s] - (= s "0x0")) - -(defn boolean->hex [b] - (if b "0x0" "0x1")) - -(defn hex->int [s] - (if (= s hex-prefix) - 0 - (js/parseInt s 16))) - -(defn int->hex [i] - (.toHex dependencies/Web3.prototype i)) - -(defn hex->bignumber [s] - (money/bignumber (if (= s hex-prefix) 0 s))) - -(defn hex->address - "When hex value is 66 char in length (2 for 0x, 64 for - the 32 bytes used by abi-spec for an address), only keep - the part that constitute the address and normalize it," - [s] - (when (= 66 (count s)) - (normalized-address (subs s 26)))) - -(defn zero-pad-64 [s] - (str (apply str (drop (count s) (repeat 64 "0"))) s)) - -(defn string->hex [i] - (.fromAscii dependencies/Web3.prototype i)) - -(defn format-param [param] - (if (number? param) - (zero-pad-64 (str (hex->int param))) - (zero-pad-64 (subs param 2)))) - -(defn format-call-params [method-id & params] - (let [params (string/join (map format-param params))] - (str method-id params))) - -(defn- sig->method-id [signature] - (apply str (take 10 (sha3 signature)))) - -(defn call [params callback] - (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})) - (def default-transaction-gas (money/bignumber 21000)) (defn estimate-gas [symbol] diff --git a/src/status_im/utils/ethereum/eip165.cljs b/src/status_im/utils/ethereum/eip165.cljs index 6785cb6da2b..4286ffdddef 100644 --- a/src/status_im/utils/ethereum/eip165.cljs +++ b/src/status_im/utils/ethereum/eip165.cljs @@ -1,13 +1,18 @@ (ns status-im.utils.ethereum.eip165 "Utility function related to [EIP165](https://eips.ethereum.org/EIPS/eip-165)" - (:require [status-im.utils.ethereum.core :as ethereum])) + (:require [status-im.ethereum.json-rpc :as json-rpc] + [status-im.utils.ethereum.abi-spec :as abi-spec])) (def supports-interface-hash "0x01ffc9a7") (def marker-hash "0xffffffff") -(defn supports-interface? [contract hash cb] - (ethereum/call (ethereum/call-params contract "supportsInterface(bytes4)" hash) - #(cb %))) +(defn supports-interface? + [contract hash cb] + (json-rpc/eth-call + {:contract contract + :method "supportsInterface(bytes4)" + :params [hash] + :on-success cb})) (defn supports? "Calls cb with true if `supportsInterface` is supported by this contract. @@ -16,9 +21,9 @@ (supports-interface? contract supports-interface-hash - #(if (true? (ethereum/hex->boolean %)) + #(if (true? (abi-spec/hex-to-boolean %)) (supports-interface? contract marker-hash (fn [response] - (cb (false? (ethereum/hex->boolean response))))) + (cb (false? (abi-spec/hex-to-boolean response))))) (cb false)))) diff --git a/src/status_im/utils/ethereum/ens.cljs b/src/status_im/utils/ethereum/ens.cljs index 3edc6c1db75..f54e0aa2993 100644 --- a/src/status_im/utils/ethereum/ens.cljs +++ b/src/status_im/utils/ethereum/ens.cljs @@ -6,8 +6,8 @@ " (:refer-clojure :exclude [name]) (:require [clojure.string :as string] - [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.abi-spec :as abi-spec])) + [status-im.ethereum.json-rpc :as json-rpc] + [status-im.utils.ethereum.core :as ethereum])) ;; this is the addresses of ens registries for the different networks (def ens-registries @@ -17,7 +17,7 @@ (def default-namehash "0000000000000000000000000000000000000000000000000000000000000000") (def default-address "0x0000000000000000000000000000000000000000") -(def default-key "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") +(def default-key "0x0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") (defn namehash [s] @@ -35,28 +35,37 @@ (defn resolver [registry ens-name cb] - (ethereum/call (ethereum/call-params registry - "resolver(bytes32)" - (namehash ens-name)) - (fn [address] - (let [address (ethereum/hex->address address)] - (cb (if (and address (not= address default-address)) address "")))))) + (json-rpc/eth-call + {:contract registry + :method "resolver(bytes32)" + :params [(namehash ens-name)] + :outputs ["address"] + :on-success + (fn [[address]] + (when-not (= address default-address) + (cb address)))})) (defn owner [registry ens-name cb] - (ethereum/call (ethereum/call-params registry - "owner(bytes32)" - (namehash ens-name)) - (fn [address] - (cb address)))) + (json-rpc/eth-call + {:contract registry + :method "owner(bytes32)" + :params [(namehash ens-name)] + :outputs ["address"] + :on-success + (fn [[address]] + (cb address))})) (defn ttl [registry ens-name cb] - (ethereum/call (ethereum/call-params registry - "ttl(bytes32)" - (namehash ens-name)) - (fn [ttl] - (cb (ethereum/hex->int ttl))))) + (json-rpc/eth-call + {:contract registry + :method "ttl(bytes32)" + :params [(namehash ens-name)] + :outputs ["uint256"] + :on-success + (fn [[ttl]] + (cb ttl))})) ;; Resolver contract ;; Resolver must implement EIP65 (supportsInterface). When interacting with an unknown resolver it's safer to rely on it. @@ -65,9 +74,14 @@ (defn addr [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver "addr(bytes32)" (namehash ens-name)) - (fn [address] - (cb (ethereum/hex->address address))))) + (json-rpc/eth-call + {:contract resolver + :method "addr(bytes32)" + :params [(namehash ens-name)] + :outputs ["address"] + :on-success + (fn [[address]] + (cb address))})) (def name-hash "0x691f3431") @@ -75,37 +89,43 @@ (defn name [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "name(bytes32)" - (namehash ens-name)) - (fn [address] - (cb (ethereum/hex->address address))))) + (json-rpc/eth-call + {:contract resolver + :method "name(bytes32)" + :params [(namehash ens-name)] + :outputs ["string"] + :on-success + (fn [[name]] + (cb name))})) (defn contenthash [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "contenthash(bytes32)" - (namehash ens-name)) - (fn [hash] - (cb (first (abi-spec/decode hash ["bytes"])))))) + (json-rpc/eth-call + {:contract resolver + :method "contenthash(bytes32)" + :params [(namehash ens-name)] + :outputs ["bytes"] + :on-success + (fn [[hash]] + (cb hash))})) (defn content [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "content(bytes32)" - (namehash ens-name)) - (fn [hash] - (cb hash)))) + (json-rpc/eth-call + {:contract resolver + :method "content(bytes32)" + :params [(namehash ens-name)] + :outputs ["bytes"] + :on-success + (fn [[hash]] + (cb hash))})) (def ABI-hash "0x2203ab56") (def pubkey-hash "0xc8690233") -(defn add-uncompressed-public-key-prefix - [key] - (when (and key - (not= "0x" key) - (not= default-key key)) - (str "0x04" (subs key 2)))) +(defn uncompressed-public-key + [x y] + (str "0x04" x y)) (defn is-valid-eth-name? [ens-name] @@ -115,12 +135,16 @@ (defn pubkey [resolver ens-name cb] - (ethereum/call (ethereum/call-params resolver - "pubkey(bytes32)" - (namehash ens-name)) - (fn [key] - (when-let [public-key (add-uncompressed-public-key-prefix key)] - (cb public-key))))) + (json-rpc/eth-call + {:contract resolver + :method "pubkey(bytes32)" + :params [(namehash ens-name)] + :outputs ["bytes32" "bytes32"] + :on-success + (fn [[x y]] + (when-let [public-key (uncompressed-public-key x y)] + (when-not (= public-key default-key) + (cb public-key))))})) (defn get-addr [registry ens-name cb] diff --git a/src/status_im/utils/ethereum/erc721.cljs b/src/status_im/utils/ethereum/erc721.cljs index 3dc5d402992..3e05685c3f8 100644 --- a/src/status_im/utils/ethereum/erc721.cljs +++ b/src/status_im/utils/ethereum/erc721.cljs @@ -2,19 +2,22 @@ " Helper functions to interact with [ERC721](https://eips.ethereum.org/EIPS/eip-721) smart contract " - (:require [status-im.utils.ethereum.core :as ethereum])) + (:require [status-im.ethereum.json-rpc :as json-rpc])) -(defn token-of-owner-by-index [contract address index cb] - (ethereum/call (ethereum/call-params - contract - "tokenOfOwnerByIndex(address,uint256)" - (ethereum/normalized-address address) - (ethereum/int->hex index)) - #(cb (ethereum/hex->bignumber %)))) +(defn token-of-owner-by-index + [contract address index cb] + (json-rpc/eth-call + {:contract contract + :method "tokenOfOwnerByIndex(address,uint256)" + :params [address index] + :outputs ["uint256"] + :on-success (fn [[token]] (cb token))})) -(defn token-uri [contract tokenId cb] - (ethereum/call (ethereum/call-params - contract - "tokenURI(uint256)" - (ethereum/int->hex tokenId)) - #(cb (ethereum/hex->string %)))) +(defn token-uri + [contract tokenId cb] + (json-rpc/eth-call + {:contract contract + :method "tokenURI(uint256)" + :params [tokenId] + :outputs ["string"] + :on-success (fn [[uri]] (cb uri))})) diff --git a/src/status_im/utils/ethereum/resolver.cljs b/src/status_im/utils/ethereum/resolver.cljs index 291409f6acc..18357ed8453 100644 --- a/src/status_im/utils/ethereum/resolver.cljs +++ b/src/status_im/utils/ethereum/resolver.cljs @@ -3,7 +3,6 @@ (:refer-clojure :exclude [name])) (def default-hash "0x0000000000000000000000000000000000000000000000000000000000000000") - (defn contenthash [registry ens-name cb] (ens/resolver registry ens-name diff --git a/src/status_im/utils/ethereum/stickers.cljs b/src/status_im/utils/ethereum/stickers.cljs deleted file mode 100644 index fbd39a49b27..00000000000 --- a/src/status_im/utils/ethereum/stickers.cljs +++ /dev/null @@ -1,34 +0,0 @@ -(ns status-im.utils.ethereum.stickers - (:require [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.abi-spec :as abi-spec])) - -(def contracts - {:mainnet nil - :testnet "0x39d16CdB56b5a6a89e1A397A13Fe48034694316E" - :rinkeby nil}) - -(defn pack-count - "Returns number of packs rigestered in the contract" - [contract cb] - (ethereum/call (ethereum/call-params contract "packCount()") - (fn [count] (cb (ethereum/hex->int count))))) - -(defn pack-data - "Returns vector of pack data parameters by pack id: [category owner mintable timestamp price contenthash]" - [contract pack-id cb] - (ethereum/call (ethereum/call-params contract "getPackData(uint256)" (ethereum/int->hex pack-id)) - (fn [data] - (cb (abi-spec/decode data ["bytes4[]" "address" "bool" "uint256" "uint256" "bytes"]))))) - -(defn owned-tokens - "Returns vector of owned tokens ids in the contract by address" - [contract address cb] - (ethereum/call (ethereum/call-params contract "tokensOwnedBy(address)" (ethereum/normalized-address address)) - (fn [data] - (cb (first (abi-spec/decode data ["uint256[]"])))))) - -(defn token-pack-id - "Returns pack id in the contract by token id" - [contract token cb] - (ethereum/call (ethereum/call-params contract "tokenPackId(uint256)" (ethereum/int->hex token)) - (fn [data] (cb (ethereum/hex->int data))))) diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs index 62db8de2145..fb6bdee75e0 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -9,6 +9,7 @@ [status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.utils.config :as config] [status-im.utils.core :as utils.core] + [status-im.utils.ethereum.abi-spec :as abi-spec] [status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.fx :as fx] @@ -256,9 +257,9 @@ [from {:keys [amount to gas gas-price data nonce]}] (cond-> {:from (ethereum/normalized-address from) :to (ethereum/normalized-address to) - :value (ethereum/int->hex amount) - :gas (ethereum/int->hex gas) - :gasPrice (ethereum/int->hex gas-price)} + :value (str "0x" (abi-spec/number-to-hex amount)) + :gas (str "0x" (abi-spec/number-to-hex gas)) + :gasPrice (str "0x" (abi-spec/number-to-hex gas-price))} data (assoc :data data) nonce @@ -314,15 +315,15 @@ (when on-error {:dispatch (conj on-error "transaction was cancelled by user")})))) -(defn prepare-unconfirmed-transaction [db now hash] +(defn prepare-unconfirmed-transaction + [db now hash] (let [transaction (get-in db [:wallet :send-transaction]) all-tokens (:wallet/all-tokens db)] (let [chain (:chain db) token (tokens/symbol->token all-tokens (keyword chain) (:symbol transaction))] (-> transaction - (assoc :confirmations "0" - :timestamp (str now) - :type :outbound + (assoc :timestamp (str now) + :type :pending :hash hash :value (:amount transaction) :token token @@ -467,30 +468,6 @@ :wallet-send-modal-stack :wallet-send-modal-stack-with-onboarding)]]})) -(fx/defn open-sign-transaction-flow - [{:keys [db] :as cofx} - {:keys [gas gas-price] :as transaction}] - (let [go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?]) - :wallet-send-modal-stack - :wallet-send-modal-stack-with-onboarding)] - (fx/merge cofx - (cond-> {:db (-> db - (assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true) - (assoc-in [:wallet :send-transaction] - transaction) - (assoc-in [:wallet :send-transaction :original-gas] - gas))} - (not gas) - (assoc :wallet/update-estimated-gas - {:obj (select-keys transaction [:to :data]) - :success-event :wallet/update-estimated-gas-success}) - - (not gas-price) - (assoc :wallet/update-gas-price - {:success-event :wallet/update-gas-price-success - :edit? false})) - (navigation/navigate-to-cofx go-to-view-id {})))) - (defn send-transaction-screen-did-load [{:keys [db]}] {:db (assoc-in db @@ -569,3 +546,34 @@ (toggle-visible-token symbol true) ;;TODO(goranjovic): move `update-token-balance-success` function to wallet models (update-token-balance symbol balance))) + +(fx/defn eth-transaction-call + [{:keys [db] :as cofx} + {:keys [contract method params on-success on-error details] :as transaction}] + (let [current-address (ethereum/current-address db) + transaction (merge {:to contract + :from current-address + :data (abi-spec/encode method params) + :id "approve" + :symbol :ETH + :method "eth_sendTransaction" + :amount (money/bignumber 0) + :on-success on-success + :on-error on-error} + details) + go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?]) + :wallet-send-modal-stack + :wallet-send-modal-stack-with-onboarding)] + (fx/merge cofx + {:db (-> db + (assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true) + (assoc-in [:wallet :send-transaction] + transaction)) + :wallet/update-estimated-gas + {:obj (select-keys transaction [:to :from :data]) + :success-event :wallet/update-estimated-gas-success} + + :wallet/update-gas-price + {:success-event :wallet/update-gas-price-success + :edit? false}} + (navigation/navigate-to-cofx go-to-view-id {})))) diff --git a/src/status_im/wallet/custom_tokens/core.cljs b/src/status_im/wallet/custom_tokens/core.cljs index 795b111f83f..4dd2e36bf1a 100644 --- a/src/status_im/wallet/custom_tokens/core.cljs +++ b/src/status_im/wallet/custom_tokens/core.cljs @@ -1,131 +1,204 @@ (ns status-im.wallet.custom-tokens.core - (:require-macros [status-im.utils.views :refer [defview letsubs]]) - (:require [re-frame.core :as re-frame] + (:require [clojure.string :as string] + [re-frame.core :as re-frame] + [status-im.ethereum.decode :as decode] + [status-im.ethereum.json-rpc :as json-rpc] [status-im.i18n :as i18n] [status-im.ui.components.colors :as colors] [status-im.ui.components.react :as react] [status-im.utils.ethereum.core :as ethereum] - [clojure.string :as string] - [status-im.ethereum.decode :as decode] + [status-im.utils.money :as money] [status-im.utils.fx :as fx] [status-im.wallet.core :as wallet])) (re-frame/reg-fx :wallet.custom-token/get-decimals (fn [contract] - (ethereum/call - (ethereum/call-params contract "decimals()") - #(re-frame/dispatch [:wallet.custom-token/decimals-result %])))) + (json-rpc/eth-call + {:contract contract + :method "decimals()" + :outputs ["uint256"] + :on-success + (fn [[contract-decimals]] + (re-frame/dispatch [:wallet.custom-token/decimals-result + contract-decimals]))}))) (re-frame/reg-fx :wallet.custom-token/get-symbol (fn [contract] - (ethereum/call - (ethereum/call-params contract "symbol()") - #(re-frame/dispatch [:wallet.custom-token/symbol-result contract %])))) + (json-rpc/eth-call + {:contract contract + :method "symbol()" + :outputs ["string"] + :on-success + (fn [[contract-symbol]] + (re-frame/dispatch [:wallet.custom-token/symbol-result + contract + contract-symbol]))}))) (re-frame/reg-fx :wallet.custom-token/get-balance - (fn [[contract address]] - (ethereum/call - (ethereum/call-params contract "balanceOf(address)" (ethereum/normalized-address address)) - #(re-frame/dispatch [:wallet.custom-token/balance-result contract %])))) + (fn [[contract wallet-address]] + (json-rpc/eth-call + {:contract contract + :method "balanceOf(address)" + :params [wallet-address] + :outputs ["uint256"] + :on-success + (fn [[balance]] + (re-frame/dispatch [:wallet.custom-token/balance-result + contract + (money/bignumber balance)]))}))) (re-frame/reg-fx :wallet.custom-token/get-name (fn [contract] - (ethereum/call - (ethereum/call-params contract "name()") - #(re-frame/dispatch [:wallet.custom-token/name-result contract %])))) + (json-rpc/eth-call + {:contract contract + :method "name()" + :outputs ["string"] + :on-success + (fn [[contract-name]] + (re-frame/dispatch [:wallet.custom-token/name-result + contract + contract-name]))}))) (re-frame/reg-fx :wallet.custom-token/get-total-supply (fn [contract] - (ethereum/call - (ethereum/call-params contract "totalSupply()") - #(re-frame/dispatch [:wallet.custom-token/total-supply-result contract %])))) + (json-rpc/eth-call + {:contract contract + :method "totalSupply()" + :outputs ["uint256"] + :on-success + (fn [[contract-total-supply]] + (re-frame/dispatch [:wallet.custom-token/total-supply-result + contract + (money/bignumber contract-total-supply)]))}))) (re-frame/reg-fx :wallet.custom-token/contract-address-paste (fn [] - (react/get-from-clipboard #(re-frame/dispatch [:wallet.custom-token/contract-address-is-pasted (string/trim %)])))) + (react/get-from-clipboard + #(re-frame/dispatch [:wallet.custom-token/contract-address-is-pasted + (string/trim %)])))) -(defn field-exists? [{:wallet/keys [all-tokens] :as db} field-key field-value] +(defn field-exists? + [{:wallet/keys [all-tokens] :as db} field-key field-value] (let [chain-key (ethereum/get-chain-keyword db)] - (some #(= field-value (get % field-key)) (vals (get all-tokens chain-key))))) - -(fx/defn total-supply-result [{:keys [db]} contract result] - (if (and (string? result) (string/starts-with? result "0x") (> (count result) 2)) - {:wallet.custom-token/get-balance [contract (get-in db [:account/account :address])]} - {:db (update db :wallet/custom-token-screen merge {:in-progress? nil :error (i18n/label :t/wrong-contract)})})) - -(defn token-in-list? [{:wallet/keys [all-tokens] :as db} contract] + (some #(= field-value (get % field-key)) + (vals (get all-tokens chain-key))))) + +(fx/defn total-supply-result + [{:keys [db]} contract total-supply] + (if (money/valid? total-supply) + {:wallet.custom-token/get-balance + [contract (ethereum/current-address db)]} + {:db (update db + :wallet/custom-token-screen + merge {:in-progress? nil + :error (i18n/label :t/wrong-contract)})})) + +(defn token-in-list? + [{:wallet/keys [all-tokens] :as db} contract] (let [chain-key (ethereum/get-chain-keyword db) addresses (set (map string/lower-case (keys (get all-tokens chain-key))))] (not (nil? (get addresses (string/lower-case contract)))))) -(fx/defn contract-address-is-changed [{:keys [db]} contract] +(fx/defn contract-address-is-changed + [{:keys [db]} contract] (if (ethereum/address? contract) (if (token-in-list? db contract) - {:db (assoc db :wallet/custom-token-screen {:contract contract :error (i18n/label :t/already-have-asset)})} - {:db (assoc db :wallet/custom-token-screen {:contract contract :in-progress? true}) + {:db (assoc db + :wallet/custom-token-screen + {:contract contract :error (i18n/label :t/already-have-asset)})} + {:db (assoc db + :wallet/custom-token-screen + {:contract contract :in-progress? true}) :wallet.custom-token/get-total-supply contract}) - {:db (assoc db :wallet/custom-token-screen {:contract contract :error (i18n/label :t/wrong-address)})})) - -(fx/defn decimals-result [{:keys [db]} result] - {:db (update db :wallet/custom-token-screen merge {:decimals (str (decode/uint result)) - :in-progress? nil})}) - -(fx/defn symbol-result [{:keys [db]} contract result] - (let [token-symbol (decode/string result) - symbol-exists? (field-exists? db :symbol (keyword token-symbol))] + {:db (assoc db + :wallet/custom-token-screen + {:contract contract + :error (i18n/label :t/wrong-address)})})) + +(fx/defn decimals-result + [{:keys [db]} result] + {:db (update db + :wallet/custom-token-screen + merge + {:decimals (str (decode/uint result)) + :in-progress? nil})}) + +(fx/defn symbol-result + [{:keys [db]} contract token-symbol] + (let [symbol-exists? (field-exists? db :symbol (keyword token-symbol))] {:db - (update db :wallet/custom-token-screen merge + (update db + :wallet/custom-token-screen merge {:symbol token-symbol :error-symbol (when symbol-exists? (i18n/label :t/you-already-have-an-asset {:value token-symbol}))}) :wallet.custom-token/get-decimals contract})) -(fx/defn name-result [{:keys [db]} contract result] - (let [token-name (decode/string result) - name-exists? (field-exists? db :name token-name)] +(fx/defn name-result + [{:keys [db]} contract token-name] + (let [name-exists? (field-exists? db :name token-name)] {:db (update db :wallet/custom-token-screen merge {:name token-name :error-name (when name-exists? - (i18n/label :t/you-already-have-an-asset {:value token-name}))}) - :wallet.custom-token/get-symbol - contract})) - -(fx/defn balance-result [{:keys [db]} contract result] - (if (and (string? result) (string/starts-with? result "0x") (> (count result) 2)) - {:db (assoc-in db [:wallet/custom-token-screen :balance] (str (decode/uint result))) + (i18n/label :t/you-already-have-an-asset + {:value token-name}))}) + :wallet.custom-token/get-symbol contract})) + +(fx/defn balance-result + [{:keys [db]} contract balance] + (if (money/valid? balance) + {:db (assoc-in db + [:wallet/custom-token-screen :balance] + (str balance)) :wallet.custom-token/get-name contract} - {:db (update db :wallet/custom-token-screen merge {:in-progress? nil :error (i18n/label :t/wrong-contract)})})) - -(fx/defn add-pressed [{:keys [db] :as cofx}] + {:db (update db + :wallet/custom-token-screen + merge + {:in-progress? nil + :error (i18n/label :t/wrong-contract)})})) + +(fx/defn add-pressed + [{:keys [db] :as cofx}] (let [{:keys [contract name symbol decimals]} (get db :wallet/custom-token-screen) chain-key (ethereum/get-chain-keyword db) symbol (keyword symbol) - new-token {:address contract :name name :symbol symbol :custom? true - :decimals (int decimals) :color (rand-nth colors/chat-colors)}] - (fx/merge (assoc-in cofx [:db :wallet/all-tokens chain-key contract] new-token) + new-token {:address contract + :name name + :symbol symbol + :custom? true + :decimals (int decimals) + :color (rand-nth colors/chat-colors)}] + (fx/merge (assoc-in cofx + [:db :wallet/all-tokens chain-key contract] + new-token) (wallet/add-custom-token new-token)))) -(fx/defn field-is-edited [{:keys [db] :as cofx} field-key value] +(fx/defn field-is-edited + [{:keys [db] :as cofx} field-key value] (case field-key :contract (contract-address-is-changed cofx value) - :name {:db (update db :wallet/custom-token-screen merge + :name {:db (update db + :wallet/custom-token-screen merge {field-key value :error-name (when (field-exists? db field-key value) - (i18n/label :t/you-already-have-an-asset {:value value}))})} + (i18n/label :t/you-already-have-an-asset + {:value value}))})} :symbol {:db (update db :wallet/custom-token-screen merge {field-key value :error-symbol (when (field-exists? db field-key (keyword value)) (i18n/label :t/you-already-have-an-asset {:value value}))})} - :decimals {:db (assoc-in db [:wallet/custom-token-screen :decimals] value)})) + :decimals {:db (assoc-in db + [:wallet/custom-token-screen :decimals] + value)})) diff --git a/test/cljs/status_im/test/utils/ethereum/core.cljs b/test/cljs/status_im/test/utils/ethereum/core.cljs index 733ed8baeb3..629ceb6070e 100644 --- a/test/cljs/status_im/test/utils/ethereum/core.cljs +++ b/test/cljs/status_im/test/utils/ethereum/core.cljs @@ -1,17 +1,9 @@ (ns status-im.test.utils.ethereum.core - (:require [cljs.test :refer-macros [deftest is testing]] + (:require [cljs.test :refer-macros [deftest is]] [status-im.utils.ethereum.core :as ethereum])) -(deftest call-params - (testing "ERC20 balance-of params" - (let [contract "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5" - address "0xa7cfd581060ec66414790691681732db249502bd"] - (is (= (ethereum/call-params contract "balanceOf(address)" address) - {:to "0x29b5f6efad2ad701952dfde9f29c960b5d6199c5" - :data "0x70a08231000000000000000000000000a7cfd581060ec66414790691681732db249502bd"}))))) - (deftest chain-id->chain-keyword (is (= (ethereum/chain-id->chain-keyword 1) :mainnet)) (is (= (ethereum/chain-id->chain-keyword 3) :testnet)) (is (= (ethereum/chain-id->chain-keyword 4) :rinkeby)) - (is (= (ethereum/chain-id->chain-keyword 5777) :custom))) \ No newline at end of file + (is (= (ethereum/chain-id->chain-keyword 5777) :custom)))