diff --git a/deps.edn b/deps.edn index c27e196821..e314e1fa0e 100644 --- a/deps.edn +++ b/deps.edn @@ -11,7 +11,7 @@ com.taoensso/timbre {:mvn/version "4.10.0"} hickory {:mvn/version "0.7.1"} com.cognitect/transit-cljs {:mvn/version "0.8.248"} - status-im/pluto {:mvn/version "iteration-4-5"} + status-im/pluto {:mvn/version "iteration-4-6"} mvxcvi/alphabase {:mvn/version "1.0.0"} rasom/cljs-react-navigation {:mvn/version "0.1.4"}} diff --git a/project.clj b/project.clj index 1de59c1f50..ac6220ec20 100644 --- a/project.clj +++ b/project.clj @@ -11,7 +11,7 @@ [com.taoensso/timbre "4.10.0"] [hickory "0.7.1"] [com.cognitect/transit-cljs "0.8.248"] - [status-im/pluto "iteration-4-5"] + [status-im/pluto "iteration-4-6"] [mvxcvi/alphabase "1.0.0"] [rasom/cljs-react-navigation "0.1.4"]] :plugins [[lein-cljsbuild "1.1.7"] diff --git a/src/status_im/chat/commands/core.cljs b/src/status_im/chat/commands/core.cljs index 95c82fefdc..d510086493 100644 --- a/src/status_im/chat/commands/core.cljs +++ b/src/status_im/chat/commands/core.cljs @@ -114,17 +114,17 @@ (def command-hook "Hook for extensions" {:properties - {:description? :string - :scope #{:personal-chats :public-chats} - :short-preview :view - :preview :view - :on-send? :event - :on-receive? :event - :on-send-sync? :event - :parameters? [{:id :keyword - :type {:one-of #{:text :phone :password :number}} - :placeholder :string - :suggestions? :view}]} + {:description? :string + :scope #{:personal-chats :public-chats :group-chats} + :short-preview? :view + :preview? :view + :on-send? :event + :on-receive? :event + :on-send-sync? :event + :parameters? [{:id :keyword + :type {:one-of #{:text :phone :password :number}} + :placeholder :string + :suggestions? :view}]} :hook (reify hooks/Hook (hook-in [_ id {extension-id :id} {:keys [description scope parameters preview short-preview @@ -138,8 +138,8 @@ (validate [_ _ _]) (on-send [_ command-message _] (when on-send {:dispatch (on-send command-message)})) (on-receive [_ command-message _] (when on-receive {:dispatch (on-receive command-message)})) - (short-preview [_ props] (short-preview props)) - (preview [_ props] (preview props)) + (short-preview [_ props] (when short-preview (short-preview props))) + (preview [_ props] (when preview (preview props))) protocol/Yielding (yield-control [_ props _] {:dispatch (on-send-sync props)}) protocol/Extension @@ -152,8 +152,8 @@ (validate [_ _ _]) (on-send [_ command-message _] (when on-send {:dispatch (on-send command-message)})) (on-receive [_ command-message _] (when on-receive {:dispatch (on-receive command-message)})) - (short-preview [_ props] (short-preview props)) - (preview [_ props] (preview props)) + (short-preview [_ props] (when short-preview (short-preview props))) + (preview [_ props] (when preview (preview props))) protocol/Extension (extension-id [_] extension-id)))] (load-commands cofx [new-command]))) diff --git a/src/status_im/extensions/core.cljs b/src/status_im/extensions/core.cljs index d5cc22e7bb..55475eebae 100644 --- a/src/status_im/extensions/core.cljs +++ b/src/status_im/extensions/core.cljs @@ -8,6 +8,7 @@ [status-im.chat.commands.impl.transactions :as transactions] [status-im.ui.components.button.view :as button] [status-im.ui.components.checkbox.view :as checkbox] + [status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.list.views :as list] [status-im.ui.components.react :as react] [status-im.ui.screens.wallet.settings.views :as settings] @@ -21,8 +22,7 @@ status-im.extensions.ethereum [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.ethereum.core :as ethereum] - [status-im.chat.commands.sending :as commands-sending] - [status-im.ui.components.icons.vector-icons :as icons])) + [status-im.chat.commands.sending :as commands-sending])) (re-frame/reg-fx ::identity-event @@ -90,7 +90,7 @@ :<- [:balance] (fn [[all-tokens network balance] [_ _ {token :token}]] (let [{:keys [decimals]} (get-token-for network all-tokens token) - value (get balance (keyword token))] + value (or (get balance (keyword token)) (money/bignumber 0))] {:value (money/token->unit value decimals) :value-in-wei value}))) @@ -98,20 +98,26 @@ :extensions.wallet/token :<- [:wallet/all-tokens] :<- [:network] - (fn [[all-tokens network] [_ _ {token :token amount :amount}]] + (fn [[all-tokens network] [_ _ {token :token amount :amount amount-in-wei :amount-in-wei}]] (let [{:keys [decimals] :as m} (get-token-for network all-tokens token)] (merge m - (when amount {:amount (money/unit->token amount decimals)}))))) + (when amount {:amount-in-wei (money/unit->token amount decimals)}) + (when amount-in-wei {:amount (money/token->unit amount-in-wei decimals)}))))) + +(defn normalize-token [m] + (update m :symbol name)) (re-frame/reg-sub :extensions.wallet/tokens :<- [:wallet/all-tokens] + :<- [:wallet/visible-tokens-symbols] :<- [:network] - (fn [[all-tokens network] [_ _ {filter-vector :filter}]] - (let [tokens (tokens/sorted-tokens-for all-tokens (ethereum/network->chain-keyword network))] - (if (= :all (first filter-vector)) - tokens - (filter #((set filter-vector) (:symbol %)) tokens))))) + (fn [[all-tokens visible-tokens-symbols network] [_ _ {filter-vector :filter visible :visible}]] + (let [tokens (map normalize-token (filter #(and (not (:nft? %)) (if visible (contains? visible-tokens-symbols (:symbol %)) true)) + (tokens/sorted-tokens-for all-tokens (ethereum/network->chain-keyword network))))] + (if filter-vector + (filter #((set filter-vector) (:symbol %)) tokens) + tokens)))) (re-frame/reg-sub :store/get @@ -360,43 +366,57 @@ (defn picker [{:keys [style on-change selected enabled data]}] [react/picker {:style style :on-change #(re-frame/dispatch (on-change {:value %})) :selected selected :enabled enabled :data data}]) +(defn- wrap-text-child [o] + (if (ifn? o) o (str o))) + (defn text [o & children] (if (map? o) - [react/text o children] - (into [react/text {} o] children))) + (into [react/text o] (map wrap-text-child children)) + (into [react/text {} o] (map wrap-text-child children)))) (defn- wrap-view-child [child] (if (vector? child) child [text {} child])) -(defn view [o & children] +(defn abstract-view [type o & children] (if (map? o) - (into [react/view o] (map wrap-view-child children)) - (into [react/view {} (wrap-view-child o)] (map wrap-view-child children)))) + (into [type o] (map wrap-view-child children)) + (into [type {} (wrap-view-child o)] (map wrap-view-child children)))) -(defn icon [o] - [icons/icon (:key o) o]) +(defn view [o & children] + (apply abstract-view react/view o children)) + +(defn scroll-view [o & children] + (apply abstract-view react/scroll-view o children)) + +(defn keyboard-avoiding-view [o & children] + (apply abstract-view react/keyboard-avoiding-view o children)) + +(defn icon [{:keys [key] :as o}] + [icons/icon key o]) (def capacities - {:components {'view {:value view} - 'text {:value text} - 'touchable-opacity {:value touchable-opacity :properties {:on-press :event}} - 'icon {:value icon :properties {:key :keyword :color :any}} - 'image {:value image :properties {:uri :string :source :numeric}} - 'input {:value input :properties {:on-change :event :change-delay :number :placeholder :string :keyboard-type :keyword :placeholder-text-color :any}} - 'button {:value button :properties {:enabled :boolean :disabled :boolean :on-click :event}} - 'link {:value link :properties {:uri :string}} - 'list {:value list :properties {:data :vector :item-view :view :key? :keyword}} - 'checkbox {:value checkbox :properties {:on-change :event :checked :boolean}} - 'activity-indicator {:value activity-indicator :properties {:animating :boolean :color :string :size :keyword :hides-when-stopped :boolean}} - 'picker {:value picker :properties {:on-change :event :selected :string :enabled :boolean :data :vector}} - 'nft-token-viewer {:value transactions/nft-token :properties {:token :string}} - 'transaction-status {:value transactions/transaction-status :properties {:outgoing :string :tx-hash :string}}} + {:components {'view {:value view} + 'scroll-view {:value scroll-view} + 'keyboard-avoiding-view {:value react/keyboard-avoiding-view} + 'text {:value text} + 'touchable-opacity {:value touchable-opacity :properties {:on-press :event}} + 'icon {:value icon :properties {:key :keyword :color :any}} + 'image {:value image :properties {:uri :string :source :string}} + 'input {:value input :properties {:on-change :event :placeholder :string :keyboard-type :keyword :change-delay? :number :placeholder-text-color :any}} + 'button {:value button :properties {:enabled :boolean :disabled :boolean :on-click :event}} + 'link {:value link :properties {:uri :string}} + 'list {:value list :properties {:data :vector :item-view :view :key? :keyword}} + 'checkbox {:value checkbox :properties {:on-change :event :checked :boolean}} + 'activity-indicator {:value activity-indicator :properties {:animating :boolean :color :string :size :keyword :hides-when-stopped :boolean}} + 'picker {:value picker :properties {:on-change :event :selected :string :enabled :boolean :data :vector}} + 'nft-token-viewer {:value transactions/nft-token :properties {:token :string}} + 'transaction-status {:value transactions/transaction-status :properties {:outgoing :string :tx-hash :string}}} :queries {'identity {:value :extensions/identity :arguments {:value :map}} 'store/get {:value :store/get :arguments {:key :string}} 'wallet/collectibles {:value :get-collectible-token :arguments {:token :string :symbol :string}} 'wallet/balance {:value :extensions.wallet/balance :arguments {:token :string}} - 'wallet/token {:value :extensions.wallet/token :arguments {:token :string :amount? :numeric}} - 'wallet/tokens {:value :extensions.wallet/tokens :arguments {:filter :vector}}} + 'wallet/token {:value :extensions.wallet/token :arguments {:token :string :amount? :number :amount-in-wei? :number}} + 'wallet/tokens {:value :extensions.wallet/tokens :arguments {:filter? :vector :visible? :boolean}}} :events {'identity {:permissions [:read] :value :extensions/identity-event @@ -437,7 +457,7 @@ {:permissions [:read] :value :extensions/arithmetic :arguments {:values :vector - :operation :keyword + :operation {:one-of #{:plus :minus :times :divide}} :on-result :event}} 'schedule/start {:permissions [:read] @@ -505,140 +525,158 @@ 'ethereum/transaction-receipt {:permissions [:read] :value :extensions/ethereum-transaction-receipt - :arguments {:value :string - :on-result :event}} + :arguments {:value :string + :on-success :event + :on-failure? :event}} 'ethereum/sign {:permissions [:read] :value :extensions/ethereum-sign - :arguments {:message? :string - :data? :string - :on-result :event}} + :arguments {:message? :string + :data? :string + :on-success :event + :on-failure? :event}} 'ethereum/send-transaction {:permissions [:read] :value :extensions/ethereum-send-transaction - :arguments {:to :string - :gas? :string - :gas-price? :string - :value? :string - :method? :string - :params? :vector - :nonce? :string - :on-result :event}} + :arguments {:to :string + :gas? :string + :gas-price? :string + :value? :string + :method? :string + :params? :vector + :nonce? :string + :on-success :event + :on-failure? :event}} 'ethereum/logs {:permissions [:read] :value :extensions/ethereum-logs - :arguments {:fromBlock? :string - :toBlock? :string - :address? :vector - :topics? :vector - :blockhash? :string - :on-result :event}} - 'ethereum/resolve-ens - {:permissions [:read] - :value :extensions/ethereum-resolve-ens - :arguments {:name :string - :on-result :event}} + :arguments {:from? :string + :to? :string + :address? :vector + :topics? :vector + :block-hash? :string + :on-success :event + :on-failure? :event}} 'ethereum/create-filter {:permissions [:read] :value :extensions/ethereum-create-filter - :arguments {:filter-type :string - :from? :string - :to? :string - :address? :vector - :topics? :vector + :arguments {:type {:one-of #{:filter :block :pending-transaction}} + :from? :string + :to? :string + :address? :vector + :topics? :vector :block-hash? :string - :on-result :event}} + :on-success :event + :on-failure? :event}} 'ethereum/logs-changes {:permissions [:read] :value :extensions/ethereum-logs-changes - :arguments {:id :string}} + :arguments {:id :string}} 'ethereum/cancel-filter {:permissions [:read] :value :extensions/ethereum-cancel-filter :arguments {:id :string}} + 'ethereum.ens/resolve + {:permissions [:read] + :value :extensions/ethereum-resolve-ens + :arguments {:name :string + :on-success :event + :on-failure? :event}} 'ethereum.erc20/total-supply {:permissions [:read] :value :extensions/ethereum-erc20-total-supply :arguments {:contract :string - :on-result :event}} + :on-success :event + :on-failure? :event}} 'ethereum.erc20/balance-of {:permissions [:read] :value :extensions/ethereum-erc20-balance-of :arguments {:contract :string :token-owner :string - :on-result :event}} + :on-success :event + :on-failure? :event}} 'ethereum.erc20/transfer {:permissions [:read] :value :extensions/ethereum-erc20-transfer - :arguments {:contract :string - :to :string - :value :number - :on-result :event}} + :arguments {:contract :string + :to :string + :value :number + :on-success :event + :on-failure? :event}} 'ethereum.erc20/transfer-from {:permissions [:read] :value :extensions/ethereum-erc20-transfer-from - :arguments {:contract :string - :from :string - :to :string - :value :number - :on-result :event}} + :arguments {:contract :string + :from :string + :to :string + :value :number + :on-success :event + :on-failure? :event}} 'ethereum.erc20/approve {:permissions [:read] :value :extensions/ethereum-erc20-approve :arguments {:contract :string :spender :string :value :number - :on-result :event}} + :on-success :event + :on-failure? :event}} 'ethereum.erc20/allowance {:permissions [:read] :value :extensions/ethereum-erc20-allowance :arguments {:contract :string :token-owner :string :spender :string - :on-result :event}} + :on-success :event + :on-failure? :event}} 'ethereum.erc721/owner-of {:permissions [:read] :value :extensions/ethereum-erc721-owner-of :arguments {:contract :string :token-id :string - :on-result :event}} + :on-success :event + :on-failure? :event}} 'ethereum.erc721/is-approved-for-all {:permissions [:read] :value :extensions/ethereum-erc721-is-approved-for-all - :arguments {:contract :string - :owner :string - :operator :string - :on-result :event}} + :arguments {:contract :string + :owner :string + :operator :string + :on-success :event + :on-failure? :event}} 'ethereum.erc721/get-approved {:permissions [:read] :value :extensions/ethereum-erc721-get-approved - :arguments {:contract :string - :token-id :string - :on-result :event}} + :arguments {:contract :string + :token-id :string + :on-success :event + :on-failure? :event}} 'ethereum.erc721/set-approval-for-all {:permissions [:read] :value :extensions/ethereum-erc721-set-approval-for-all - :arguments {:contract :string - :operator :string - :approved :boolean - :on-result :event}} + :arguments {:contract :string + :operator :string + :approved :boolean + :on-success :event + :on-failure? :event}} 'ethereum.erc721/safe-transfer-from {:permissions [:read] :value :extensions/ethereum-erc721-safe-transfer-from - :arguments {:contract :string - :from :string - :to :string - :token-id :string - :data? :string - :on-result :event}} + :arguments {:contract :string + :from :string + :to :string + :token-id :string + :data? :string + :on-success :event + :on-failure? :event}} 'ethereum/call {:permissions [:read] :value :extensions/ethereum-call - :arguments {:to :string - :method :string - :params? :vector - :outputs? :vector - :on-result :event}}} + :arguments {:to :string + :method :string + :params? :vector + :outputs? :vector + :on-success :event + :on-failure? :event}}} :hooks {:chat.command commands/command-hook :wallet.settings settings/hook}}) diff --git a/src/status_im/extensions/ethereum.cljs b/src/status_im/extensions/ethereum.cljs index 082e008fa7..aa029a5818 100644 --- a/src/status_im/extensions/ethereum.cljs +++ b/src/status_im/extensions/ethereum.cljs @@ -17,23 +17,20 @@ [status-im.native-module.core :as status])) (handlers/register-handler-fx - :extensions/wallet-ui-on-result - (fn [cofx [_ on-result id result method]] + :extensions/wallet-ui-on-success + (fn [cofx [_ on-success _ result _]] (fx/merge cofx - (when on-result - {:dispatch (on-result {:error nil :result result})}) - (cond - (= method constants/web3-send-transaction) (navigation/navigate-to-clean :wallet-transaction-sent nil) - (= method constants/web3-personal-sign) (navigation/navigate-back))))) + {:dispatch (on-success {:value result})} + (navigation/navigate-back)))) (handlers/register-handler-fx - :extensions/wallet-ui-on-error - (fn [{db :db} [_ on-result message]] - (when on-result {:dispatch (on-result {:error message :result nil})}))) + :extensions/wallet-ui-on-failure + (fn [_ [_ on-failure message]] + (when on-failure {:dispatch (on-failure {:value message})}))) (defn- wrap-with-resolution [db arguments address-keyword f] - "funtction responsible to resolve ens taken from argument - and call the specified function with resolved address" + "function responsible to resolve ens taken from argument + and call the specified function with resolved address" (let [address (get arguments address-keyword) first-address (if (vector? address) ;; currently we only support one ens for address (first address) @@ -46,8 +43,7 @@ (ens/get-addr web3 registry first-address #(f db (assoc arguments address-keyword %)))) (f db arguments)))) -;; EXTENSION TRANSACTION -> SEND TRANSACTION -(defn prepare-extension-transaction [params contacts on-result] +(defn prepare-extension-transaction [params contacts on-success on-failure] (let [{:keys [to value data gas gasPrice nonce]} params contact (get contacts (hex/normalize-hex to))] (cond-> {:id "extension-id" @@ -66,15 +62,15 @@ :gas-price (when gasPrice (money/bignumber gasPrice)) :data data - :on-result [:extensions/wallet-ui-on-result on-result] - :on-error [:extensions/wallet-ui-on-error on-result]} + :on-result [:extensions/wallet-ui-on-success on-success] + :on-error [:extensions/wallet-ui-on-failure on-failure]} nonce (assoc :nonce nonce)))) -(defn- execute-send-transaction [db {:keys [method params on-result] :as arguments}] +(defn- execute-send-transaction [db {:keys [method params on-success on-failure] :as arguments}] (let [tx-object (assoc (select-keys arguments [:to :gas :gas-price :value :nonce]) :data (when (and method params) (abi-spec/encode method params))) - transaction (prepare-extension-transaction tx-object (:contacts/contacts db) on-result)] + transaction (prepare-extension-transaction tx-object (:contacts/contacts db) on-success on-failure)] (models.wallet/open-modal-wallet-for-transaction db transaction tx-object))) (handlers/register-handler-fx @@ -82,143 +78,173 @@ (fn [{db :db} [_ _ arguments]] (wrap-with-resolution db arguments :to execute-send-transaction))) -(defn- execute-ethcall [_ {:keys [to method params outputs on-result]}] +(defn- rpc-args [method params] + {:jsonrpc "2.0" + :method method + :params params}) + +(defn- rpc-dispatch [error result f on-success on-failure] + (when result + (re-frame/dispatch (on-success {:result (f result)}))) + (when (and error on-failure) + (re-frame/dispatch (on-failure {:result error})))) + +(defn- rpc-handler [o f on-success on-failure] + (let [{:keys [error result]} (types/json->clj o)] + (rpc-dispatch error result f on-success on-failure))) + +(defn- rpc-call [method params f {:keys [on-success on-failure]}] + (let [payload (types/clj->json (rpc-args method params))] + (status/call-private-rpc payload #(rpc-handler % f on-success on-failure)))) + +(defn parse-call-result [o outputs] + (let [result (get (js->clj o) "result")] + (cond + (= "0x" result) nil + (and outputs result) + (abi-spec/decode (string/replace result #"^0x" "") outputs) + :else result))) + +(defn- execute-ethcall [_ {:keys [to method params outputs on-success on-failure]}] (let [tx-object {:to to :data (when method (abi-spec/encode method params))}] - {:browser/call-rpc [{"jsonrpc" "2.0" - "method" "eth_call" - "params" [tx-object "latest"]} - #(let [result-str (when %2 - (get (js->clj %2) "result")) - result (cond - (= "0x" result-str) nil - (and outputs result-str) - (abi-spec/decode (string/replace result-str #"0x" "") outputs) - :else result-str)] - (re-frame/dispatch (on-result (merge {:result result} (when %1 {:error %1})))))]})) + {:browser/call-rpc [(rpc-args "eth_call" [tx-object "latest"]) + #(rpc-dispatch %1 %2 (fn [o] (parse-call-result o outputs)) on-success on-failure)]})) (handlers/register-handler-fx :extensions/ethereum-call - (fn [{db :db} [_ _ {:keys [to] :as arguments}]] + (fn [{db :db} [_ _ arguments]] (wrap-with-resolution db arguments :to execute-ethcall))) (handlers/register-handler-fx :extensions/ethereum-erc20-total-supply - (fn [{db :db} [_ _ {:keys [contract on-result]}]] + (fn [{db :db} [_ _ {:keys [contract on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "totalSupply()" - :outputs ["uint256"] - :on-result on-result}] + :method "totalSupply()" + :outputs ["uint256"] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-ethcall)))) (handlers/register-handler-fx :extensions/ethereum-erc20-balance-of - (fn [{db :db} [_ _ {:keys [contract token-owner on-result]}]] + (fn [{db :db} [_ _ {:keys [contract token-owner on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "balanceOf(address)" - :params [token-owner] - :outputs ["uint256"] - :on-result on-result}] + :method "balanceOf(address)" + :params [token-owner] + :outputs ["uint256"] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-ethcall)))) (handlers/register-handler-fx :extensions/ethereum-erc20-allowance - (fn [{db :db} [_ _ {:keys [contract token-owner spender on-result]}]] + (fn [{db :db} [_ _ {:keys [contract token-owner spender on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "allowance(address,address)" - :params [token-owner spender] - :outputs ["uint256"] - :on-result on-result}] + :method "allowance(address,address)" + :params [token-owner spender] + :outputs ["uint256"] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-ethcall)))) (handlers/register-handler-fx :extensions/ethereum-erc20-transfer - (fn [{db :db} [_ _ {:keys [contract to value on-result]}]] + (fn [{db :db} [_ _ {:keys [contract to value on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "transfer(address,uint256)" - :params [to value] - :on-result on-result}] + :method "transfer(address,uint256)" + :params [to value] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-send-transaction)))) (handlers/register-handler-fx :extensions/ethereum-erc20-transfer-from - (fn [{db :db} [_ _ {:keys [contract from to value on-result]}]] + (fn [{db :db} [_ _ {:keys [contract from to value on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "transferFrom(address,address,uint256)" - :params [from to value] - :on-result on-result}] + :method "transferFrom(address,address,uint256)" + :params [from to value] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-send-transaction)))) (handlers/register-handler-fx :extensions/ethereum-erc20-approve - (fn [{db :db} [_ _ {:keys [contract spender value on-result]}]] + (fn [{db :db} [_ _ {:keys [contract spender value on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "approve(address,uint256)" - :params [spender value] - :on-result on-result}] + :method "approve(address,uint256)" + :params [spender value] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-send-transaction)))) (handlers/register-handler-fx :extensions/ethereum-erc721-owner-of - (fn [{db :db} [_ _ {:keys [contract token-id on-result]}]] + (fn [{db :db} [_ _ {:keys [contract token-id on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "ownerOf(uint256)" - :params [token-id] - :outputs ["address"] - :on-result on-result}] + :method "ownerOf(uint256)" + :params [token-id] + :outputs ["address"] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-ethcall)))) (handlers/register-handler-fx :extensions/ethereum-erc721-is-approved-for-all - (fn [{db :db} [_ _ {:keys [contract owner operator on-result]}]] + (fn [{db :db} [_ _ {:keys [contract owner operator on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "isApprovedForAll(address,address)" - :params [owner operator] - :outputs ["bool"] - :on-result on-result}] + :method "isApprovedForAll(address,address)" + :params [owner operator] + :outputs ["bool"] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-ethcall)))) (handlers/register-handler-fx :extensions/ethereum-erc721-get-approved - (fn [{db :db} [_ _ {:keys [contract token-id on-result]}]] + (fn [{db :db} [_ _ {:keys [contract token-id on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "getApproved(uint256)" - :params [token-id] - :outputs ["address"] - :on-result on-result}] + :method "getApproved(uint256)" + :params [token-id] + :outputs ["address"] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-ethcall)))) (handlers/register-handler-fx :extensions/ethereum-erc721-set-approval-for-all - (fn [{db :db} [_ _ {:keys [contract to approved on-result]}]] + (fn [{db :db} [_ _ {:keys [contract to approved on-success on-failure]}]] (let [json-rpc-args {:to contract - :method "setApprovalForAll(address,bool)" - :params [to approved] - :on-result on-result}] + :method "setApprovalForAll(address,bool)" + :params [to approved] + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-send-transaction)))) (handlers/register-handler-fx :extensions/ethereum-erc721-safe-transfer-from - (fn [{db :db} [_ _ {:keys [contract from to token-id data on-result]}]] + (fn [{db :db} [_ _ {:keys [contract from to token-id data on-success on-failure]}]] (let [json-rpc-args {:to contract - :method (if data - "safeTransferFrom(address,address,uint256,bytes)" - "safeTransferFrom(address,address,uint256)") - :params (if data - [from to token-id data] - [from to token-id]) - :on-result on-result}] + :method (if data + "safeTransferFrom(address,address,uint256,bytes)" + "safeTransferFrom(address,address,uint256)") + :params (if data + [from to token-id data] + [from to token-id]) + :on-success on-success + :on-failure on-failure}] (wrap-with-resolution db json-rpc-args :to execute-send-transaction)))) (defn- parse-log [{:keys [address transactionHash blockHash transactionIndex topics blockNumber logIndex removed data]}] - {:address address - :transaction-hash transactionHash - :blockHash blockHash - :transaction-index (abi-spec/hex-to-number transactionIndex) - :topics topics ;; TODO parse topics - :block-number (abi-spec/hex-to-number blockNumber) - :log-index (abi-spec/hex-to-number logIndex) - :removed removed - :data data}) + (merge {:data data + :topics topics + :address address + :removed removed} + ;; TODO parse data and topics, filter useless solidity first topic, aggregate as events ? + (when logIndex {:log-index (abi-spec/hex-to-number logIndex)}) + (when transactionIndex {:transaction-index (abi-spec/hex-to-number transactionIndex)}) + (when transactionHash {:transaction-hash transactionHash}) + (when blockHash {:block-hash blockHash}) + (when blockNumber {:block-number (abi-spec/hex-to-number blockNumber)}))) (defn- parse-receipt [m] (when m @@ -238,16 +264,8 @@ (handlers/register-handler-fx :extensions/ethereum-transaction-receipt - (fn [_ [_ _ {:keys [value on-result]}]] - (let [args {:jsonrpc "2.0" - :method constants/web3-transaction-receipt - :params [value]} - payload (types/clj->json args)] - (status/call-private-rpc payload #(let [{:keys [error result]} (types/json->clj %1) - response (merge {:result (parse-receipt result)} (when error {:error error}))] - (re-frame/dispatch (on-result response))))))) - -;; eth_getLogs implementation + (fn [_ [_ _ {:keys [value] :as m}]] + (rpc-call constants/web3-transaction-receipt [value] parse-receipt m))) (defn- event-topic-enc [event params] (let [eventid (str event "(" (string/join "," params) ")")] @@ -264,17 +282,16 @@ :else :bytes)) (defn- values-topic-enc [type values] - (let [mapped-type (types-mapping type)] - (mapv #(str "0x" (abi-spec/enc {:type mapped-type :value %})) values))) + (mapv #(str "0x" (abi-spec/enc {:type (types-mapping type) :value %})) values)) -(defn- parse-topic [t] +(defn- generate-topic [t] (cond (or (nil? t) (string? t)) t ;; nil topic ;; immediate topic (extension encode topic by its own) ;; vector of immediate topics - (vector? t) (mapv parse-topic t) ;; descend in vector elements + (vector? t) (mapv generate-topic t) ;; descend in vector elements (map? t) ;; simplified topic interface, we need to encode (let [{:keys [event params type values]} t] (cond - (some? event) (event-topic-enc event params);; event params topic + (some? event) (event-topic-enc event params);; event params topic {:event "Transfer" :params ["uint"]} (some? type) (values-topic-enc type values) ;; indexed values topic :else nil)) ;; error :else nil)) @@ -285,19 +302,13 @@ (re-matches #"^[0-9]+$" block) (str "0x" (abi-spec/number-to-hex block)) :else block)) -(defn- execute-get-logs [_ {:keys [fromBlock toBlock address topics blockhash on-result]}] - (let [parsed-topics (mapv parse-topic topics) - args {:jsonrpc "2.0" - :method constants/web3-get-logs - :params [{:fromBlock (ensure-hex-bn fromBlock) - :toBlock (ensure-hex-bn toBlock) - :address address - :topics parsed-topics - :blockhash blockhash}]} - payload (types/clj->json args)] - (status/call-private-rpc payload #(let [{:keys [error result]} (types/json->clj %1) - response (merge {:result result} (when error {:error error}))] - (re-frame/dispatch (on-result response)))))) +(defn- execute-get-logs [_ {:keys [from to address topics block-hash] :as m}] + (let [params [{:from (ensure-hex-bn from) + :to (ensure-hex-bn to) + :address address + :topics (generate-topic topics) + :blockhash block-hash}]] + (rpc-call constants/web3-get-logs params #(map parse-log %) m))) (handlers/register-handler-fx :extensions/ethereum-logs @@ -306,71 +317,59 @@ (handlers/register-handler-fx :extensions/ethereum-resolve-ens - (fn [{db :db} [_ _ {:keys [name on-result]}]] + (fn [{db :db} [_ _ {:keys [name on-success on-failure]}]] (if (ens/is-valid-eth-name? name) (let [{:keys [web3 network]} db network-info (get-in db [:account/account :networks network]) chain (ethereum/network->chain-keyword network-info) registry (get ens/ens-registries chain)] - (ens/get-addr web3 registry name #(re-frame/dispatch (on-result {:result %})))) - (re-frame/dispatch (on-result {:error (str "'" name "' is not a valid name")}))))) + (ens/get-addr web3 registry name #(re-frame/dispatch (on-success {:value %})))) + (when on-failure + (re-frame/dispatch (on-failure {:value (str "'" name "' is not a valid name")})))))) ;; EXTENSION SIGN -> SIGN MESSAGE (handlers/register-handler-fx :extensions/ethereum-sign - (fn [{db :db :as cofx} [_ _ {:keys [message data id on-result]}]] - (if (= (nil? message) (nil? data)) - {:dispatch (on-result {:error "only one of :message and :data can be used"})} + (fn [{db :db :as cofx} [_ _ {:keys [message data id on-success on-failure]}]] + (if (and message data) + (when on-failure + {:dispatch (on-failure {:error "only one of :message and :data can be used"})}) (fx/merge cofx {:db (assoc-in db [:wallet :send-transaction] {:id id - :from (ethereum/normalized-address (get-in db [:account/account :address])) + :from (ethereum/normalized-address (get-in db [:account/account :address])) :data (or data (str "0x" (abi-spec/from-utf8 message))) - :on-result [:extensions/wallet-ui-on-result on-result] - :on-error [:extensions/wallet-ui-on-error on-result] + :on-result [:extensions/wallet-ui-on-success on-success] + :on-error [:extensions/wallet-ui-on-failure on-failure] :method constants/web3-personal-sign})} (navigation/navigate-to-cofx :wallet-sign-message-modal nil))))) -;; poll logs implementation (handlers/register-handler-fx :extensions/ethereum-logs-changes - (fn [_ [_ _ {:keys [filterId on-result]}]] - (let [args {:jsonrpc "2.0" - :method constants/web3-get-filter-changes - :params [filterId]} - payload (types/clj->json args)] - (status/call-private-rpc payload #(let [{:keys [error result]} (types/json->clj %1) - response (merge {:result result} (when error {:error error}))] - (mapv (fn [one-result] - (re-frame/dispatch (on-result one-result))) result)))))) + (fn [_ [_ _ {:keys [id] :as m}]] + (rpc-call constants/web3-get-filter-changes [(abi-spec/number-to-hex id)] #(map parse-log %) m))) + (handlers/register-handler-fx :extensions/ethereum-cancel-filter - (fn [_ [_ _ {:keys [filterId on-result]}]] - (let [args {:jsonrpc "2.0" - :method constants/web3-uninstall-filter - :params [filterId]} - payload (types/clj->json args)] - (status/call-private-rpc payload #(let [{:keys [error result]} (types/json->clj %1) - response (merge {:result result} (when error {:error error}))] - (re-frame/dispatch (on-result response))))))) + (fn [_ [_ _ {:keys [id] :as m}]] + (rpc-call constants/web3-uninstall-filter [(abi-spec/number-to-hex id)] #(abi-spec/hex-to-value % "bool") m))) + +(defn create-filter-method [type] + (case type + :filter constants/web3-new-filter + :block constants/web3-new-block-filter + :pending-transaction constants/web3-new-pending-transaction-filter)) + +(defn create-filter-arguments [type {:keys [from to address block-hash topics]}] + (case type + :filter + [{:fromBlock (ensure-hex-bn from) + :toBlock (ensure-hex-bn to) + :address address + :topics (mapv generate-topic topics) + :blockhash block-hash}])) (handlers/register-handler-fx :extensions/ethereum-create-filter - (fn [_ [_ _ {:keys [filter-type from to address topics block-hash on-result]}]] - (let [parsed-topics (mapv parse-topic topics) - args (case filter-type - "filter" {:jsonrpc "2.0" - :method constants/web3-new-filter - :params [{:fromBlock (ensure-hex-bn from) - :toBlock (ensure-hex-bn to) - :address address - :topics parsed-topics - :blockhash block-hash}]} - "block" {:jsonrpc "2.0" - :method constants/web3-new-block-filter} - "pendingTransaction" {:jsonrpc "2.0" - :method constants/web3-new-pending-transaction-filter}) - payload (types/clj->json args)] - (status/call-private-rpc payload #(let [{:keys [error result]} (types/json->clj %1) - response (merge {:result result} (when error {:error error}))] - (re-frame/dispatch (on-result response))))))) + (fn [_ [_ _ {:keys [type] :as m}]] + (rpc-call (create-filter-method type) (create-filter-arguments type m) abi-spec/hex-to-number m))) diff --git a/src/status_im/extensions/registry.cljs b/src/status_im/extensions/registry.cljs index 95ec0733d5..46678071b6 100644 --- a/src/status_im/extensions/registry.cljs +++ b/src/status_im/extensions/registry.cljs @@ -15,8 +15,9 @@ hooks (get-in account [:extensions extension-id :hooks])] (apply fx/merge cofx (mapcat (fn [[_ extension-hooks]] - (map (fn [[hook-id {:keys [hook-ref parsed]}]] - (partial hook-fn (:hook hook-ref) hook-id {:id extension-id} parsed)) + (map (fn [[hook-id {parsed :parsed {hook :hook} :hook-ref}]] + (when hook + (partial hook-fn hook hook-id {:id extension-id} parsed))) extension-hooks)) hooks)))) diff --git a/src/status_im/ui/screens/wallet/settings/views.cljs b/src/status_im/ui/screens/wallet/settings/views.cljs index 76ef4d2688..99d1ec802b 100644 --- a/src/status_im/ui/screens/wallet/settings/views.cljs +++ b/src/status_im/ui/screens/wallet/settings/views.cljs @@ -18,13 +18,12 @@ "Hook for extensions" {:properties {:label :string - :view :view - :on-click? :event} + :view :view} :hook (reify hooks/Hook - (hook-in [_ id env {:keys [label view on-click]} {:keys [db]}] - {:db (assoc-in db [:wallet :settings id] {:label label :view view :on-click on-click})}) - (unhook [_ id env _ {:keys [db]}] + (hook-in [_ id _ {:keys [label view _]} {:keys [db]}] + {:db (assoc-in db [:wallet :settings id] {:label label :view view})}) + (unhook [_ id _ _ {:keys [db]}] {:db (update-in db [:wallet :settings] dissoc id)}))}) (defn- render-token [{:keys [symbol name icon]} visible-tokens] @@ -57,7 +56,8 @@ :render-fn #(render-token % visible-tokens)}]]])) (defview settings-hook [] - (letsubs [{:keys [label view]} [:get-screen-params :wallet-settings-hook]] + (letsubs [{:keys [label view]} [:get-screen-params :wallet-settings-hook] + {address :address} [:account/account]] [react/view {:style {:flex 1 :background-color colors/white}} [status-bar/status-bar {:type :modal-wallet}] [toolbar/toolbar {:style wallet.styles/toolbar} @@ -65,14 +65,11 @@ (re-frame/dispatch [:navigate-back])))] [toolbar/content-title {:color colors/white} label]] - [view]])) + [view {:address (ethereum/normalized-address address)}]])) -(defn- setting->action [{:keys [label on-click] :as m}] +(defn- setting->action [{:keys [label] :as m}] {:label label - :action #(do - (when on-click - (re-frame/dispatch (on-click {}))) - (re-frame/dispatch [:navigate-to :wallet-settings-hook m]))}) + :action #(re-frame/dispatch [:navigate-to :wallet-settings-hook m])}) (defview toolbar-view [] (letsubs [settings [:wallet/settings]] diff --git a/src/status_im/utils/ethereum/abi_spec.cljs b/src/status_im/utils/ethereum/abi_spec.cljs index ad5669572b..a19ca3bb6e 100644 --- a/src/status_im/utils/ethereum/abi_spec.cljs +++ b/src/status_im/utils/ethereum/abi_spec.cljs @@ -22,16 +22,20 @@ (.leftPad utils x (+ len to-pad))))) (defn to-two-complement [x] - (subs (.toTwosComplement utils x) 2)) + (when x + (subs (.toTwosComplement utils x) 2))) (defn from-utf8 [x] - (subs (.fromUtf8 utils x) 2)) + (when x + (subs (.fromUtf8 utils x) 2))) (defn bytes-to-hex [x] - (subs (.bytesToHex utils x) 2)) + (when x + (subs (.bytesToHex utils x) 2))) (defn number-to-hex [x] - (subs (.numberToHex utils x) 2)) + (when x + (subs (.numberToHex utils x) 2))) (defn hex-to-utf8 [x] (.hexToUtf8 utils (str "0x" x))) diff --git a/test/cljs/status_im/test/extensions/ethereum.cljs b/test/cljs/status_im/test/extensions/ethereum.cljs index 13a4808312..f373983f35 100644 --- a/test/cljs/status_im/test/extensions/ethereum.cljs +++ b/test/cljs/status_im/test/extensions/ethereum.cljs @@ -8,34 +8,34 @@ (testing "topic parsing check - events" (is (= ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" "0x29f31b934498b0deabbe211b01cc30eee6475abf0f0d553e7eb8ca71deeb24b3"] - (ethereum/parse-topic [{:event "Transfer" :params ["address" "address" "uint256"]} - {:event "drawBet" :params ["uint256" "uint8" "int8" "address" "uint256"]}]))))) + (ethereum/generate-topic [{:event "Transfer" :params ["address" "address" "uint256"]} + {:event "drawBet" :params ["uint256" "uint8" "int8" "address" "uint256"]}]))))) (deftest test-parse-topic-indexed-values-address (testing "topic parsing check - indexed values address" (is (= ["0x000000000000000000000000299b18709d4aacbda99048721448f65893a0c73a" "0x00000000000000000000000094eaa5fa6b313968b2abd6da375ef28077d95d53"] - (ethereum/parse-topic {:type "address" :values ["0x299b18709d4aacbda99048721448f65893a0c73a" "0x94eaa5fa6b313968b2abd6da375ef28077d95d53"]}))))) + (ethereum/generate-topic {:type "address" :values ["0x299b18709d4aacbda99048721448f65893a0c73a" "0x94eaa5fa6b313968b2abd6da375ef28077d95d53"]}))))) (deftest test-parse-topic-indexed-values-uint256 (testing "topic parsing check - indexed values uint256" (is (= ["0x00000000000000000000000000000000000000000000000000000000000003fd" "0x0000000000000000000000000000000000000000000000000000000000000078"] - (ethereum/parse-topic {:type "uint256" :values ["0x3fd" "120"]}))))) + (ethereum/generate-topic {:type "uint256" :values ["0x3fd" "120"]}))))) (deftest test-parse-topic-indexed-values-direct (testing "topic parsing check - indexed values direct" (is (= ["0x00000000000000000000000000000000000000000000000000000000000003fd" "0x0000000000000000000000000000000000000000000000000000000000000078"] - (ethereum/parse-topic ["0x00000000000000000000000000000000000000000000000000000000000003fd" - "0x0000000000000000000000000000000000000000000000000000000000000078"]))))) + (ethereum/generate-topic ["0x00000000000000000000000000000000000000000000000000000000000003fd" + "0x0000000000000000000000000000000000000000000000000000000000000078"]))))) (deftest test-parse-topic-indexed-nil (testing "topic parsing check - nil" (is (= ["0x00000000000000000000000000000000000000000000000000000000000003fd" nil] - (ethereum/parse-topic ["0x00000000000000000000000000000000000000000000000000000000000003fd" - nil]))))) + (ethereum/generate-topic ["0x00000000000000000000000000000000000000000000000000000000000003fd" + nil]))))) (deftest test-ensure-hex-bn-nonnumber (is (= "latest" (ethereum/ensure-hex-bn "latest")))) @@ -47,4 +47,4 @@ (is (= "0xf" (ethereum/ensure-hex-bn "0xf")))) (deftest test-ensure-hex-bn-nil - (is (= nil (ethereum/ensure-hex-bn nil)))) \ No newline at end of file + (is (= nil (ethereum/ensure-hex-bn nil))))