From f9f8d4c524fc065d882794c4a47baa980200a5fe Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Mon, 21 Oct 2024 12:14:00 +0300 Subject: [PATCH] feat: added transaction record --- .../events/session_requests.cljs | 15 +- .../events/session_responses.cljs | 7 +- .../modals/send_transaction/view.cljs | 7 +- .../wallet/wallet_connect/utils/rpc.cljs | 18 +- .../wallet_connect/utils/transactions.cljs | 192 +++++++++++------- .../wallet_connect/utils/typed_data.cljs | 6 +- .../subs/wallet/dapps/transactions.cljs | 53 ++++- 7 files changed, 189 insertions(+), 109 deletions(-) diff --git a/src/status_im/contexts/wallet/wallet_connect/events/session_requests.cljs b/src/status_im/contexts/wallet/wallet_connect/events/session_requests.cljs index 6faef94ffd..5417eed1c9 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events/session_requests.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_requests.cljs @@ -82,17 +82,16 @@ (rf/reg-event-fx :wallet-connect/prepare-transaction-success (fn [{:keys [db]} [prepared-tx]] - (let [{:keys [tx-args tx-details]} prepared-tx - tx (-> tx-args - bean/->clj - (transactions/Transaction. tx-details))] + (let [{:keys [tx-args]} prepared-tx + tx (-> tx-args + bean/->clj)] {:db (update-in db [:wallet-connect/current-request] assoc - :raw-data prepared-tx - :address (.sender tx) - :transaction-summary (.summary tx) - :display-data (.beautify-params tx))}))) + :raw-data prepared-tx + :transaction tx + :address (transactions/get-sender tx) + :display-data (transactions/beautify-transaction tx))}))) (rf/reg-event-fx :wallet-connect/process-eth-send-transaction diff --git a/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs b/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs index 02f262b549..615290dc76 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events/session_responses.cljs @@ -55,7 +55,7 @@ :wallet-connect/respond-sign-typed-data (fn [{:keys [db]} [password typed-data-version]] (let [{:keys [address raw-data event]} (get db :wallet-connect/current-request) - chain-id (get-in event [:params :chainId])] + chain-id (data-store/get-chain-id event)] {:fx [[:effects.wallet-connect/sign-typed-data {:password password :address address @@ -68,8 +68,9 @@ (rf/reg-event-fx :wallet-connect/respond-send-transaction-data (fn [{:keys [db]} [password]] - (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request) - {:keys [tx-hash tx-args]} raw-data] + (let [{:keys [raw-data address event]} (get db :wallet-connect/current-request) + {:keys [tx-hash tx-args]} raw-data + chain-id (data-store/get-chain-id event)] {:fx [[:effects.wallet-connect/send-transaction {:password password :address address diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs index c8224135cd..d2d0d2a68b 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs @@ -13,6 +13,7 @@ [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] [status-im.contexts.wallet.wallet-connect.modals.common.style :as style] [status-im.contexts.wallet.wallet-connect.utils.transactions :as transaction-utils] + [status-im.setup.hot-reload :as hot-reload] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -73,9 +74,9 @@ (set! (.-current refetch-interval-ref) (js/setInterval refetch-transaction constants/wallet-connect-transaction-refresh-interval-ms)))) - (rn/use-unmount (fn [] - (clear-interval) - (rf/dispatch [:wallet-connect/on-request-modal-dismissed]))) + (hot-reload/use-safe-unmount (fn [] + (clear-interval) + (rf/dispatch [:wallet-connect/on-request-modal-dismissed]))) [rn/view {:style (style/container bottom)} [quo/gradient-cover {:customization-color customization-color}] diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs index 12de1d398f..90c0817819 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/rpc.cljs @@ -1,6 +1,5 @@ (ns status-im.contexts.wallet.wallet-connect.utils.rpc - (:require [cljs-bean.core :as bean] - [oops.core :as oops] + (:require [oops.core :as oops] [promesa.core :as promesa] [status-im.common.json-rpc.events :as rpc-events] [status-im.constants :as constants] @@ -13,15 +12,6 @@ {:message-to-sign (oops/oget res :messageToSign) :tx-args (oops/oget res :txArgs)})) -(defn wallet-build-raw-transaction - [chain-id tx-args signature] - (-> (rpc-events/call-async "wallet_buildRawTransaction" - true - chain-id - (transforms/js-stringify tx-args 0) - signature) - (promesa/then #(oops/oget % "rawTx")))) - (defn wallet-send-transaction-with-signature [chain-id tx-args signature] (rpc-events/call-async "wallet_sendTransactionWithSignature" @@ -80,9 +70,3 @@ [chain-id max-fee-per-gas] (-> (rpc-events/call-async "wallet_getTransactionEstimatedTime" true chain-id max-fee-per-gas) (promesa/then transforms/js->clj))) - -(defn wallet-get-transaction-details - [transaction] - (->> (transforms/js-stringify transaction 0) - (rpc-events/call-async "wallet_getTransactionMetadata" true) - (transforms/js->clj))) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/transactions.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/transactions.cljs index 765e35a5a4..1d4ffa09c4 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils/transactions.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/transactions.cljs @@ -7,6 +7,8 @@ [status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store] [status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc] + [utils.address :as address] + [utils.hex :as hex] [utils.money :as money] [utils.transforms :as transforms])) @@ -132,11 +134,9 @@ (prepare-transaction-fees tx tx-priority) prepare-transaction-for-rpc - (rpc/wallet-build-transaction chain-id)) - details (rpc/wallet-get-transaction-details tx-args)] + (rpc/wallet-build-transaction chain-id))] {:tx-args tx-args :tx-hash message-to-sign - :tx-details details :tx-estimations estimations})) (defn send-transaction @@ -153,82 +153,134 @@ (= k :Symbol))) data)) +(defrecord Transaction + [version from to gas gasPrice value nonce maxFeePerGas + maxPriorityFeePerGas input data multiTransactionID]) -(def tx-args +(defn create-transaction + [tx] + ;; TODO add malli check schema + (map->Transaction tx)) + +(defn- non-empty-hex + [hex-string] + (let [normalized (hex/normalize-hex hex-string)] + (when-not (string/blank? normalized) + hex-string))) + +(defn get-data + [^Transaction tx] + (let [{:keys [input data]} tx] + (or (non-empty-hex input) + (non-empty-hex data)))) + +(def function-selector-to-type + {constants/method-id-transfer :transaction/erc-20-transfer + constants/method-id-approve :transaction/erc-20-approve}) + +(defn get-type + [^Transaction tx] + (let [tx-data (get-data tx) + eth-transfer? (nil? tx-data) + contract-type (when-not eth-transfer? + (-> tx-data (subs 0 10) function-selector-to-type))] + (cond + eth-transfer? :transaction/eth-transfer + (keyword? contract-type) contract-type + :else :transaction/unknown))) + +(defn get-sender + [^Transaction tx] + (-> tx :from string/lower-case)) + +(defn- subs-bytes + [hex-data start-bytes end-bytes] + (when (-> hex-data count (/ 2) (>= end-bytes)) + (-> hex-data + hex/normalize-hex + (subs (* start-bytes 2) + (* end-bytes 2))))) + +(defn- decode-erc-20-transfer + "Decode ERC20 transfer data, which has the following function signature: + `approve(spender, amount)`" + [tx-data] + (when (>= (count tx-data) 138) + (let [selector (subs-bytes tx-data 0 4) ;; Function selector (first 4 bytes) + recipient-hex (subs-bytes tx-data 16 36) ;; Recipient address (next 32 bytes) + amount-hex (subs-bytes tx-data 36 68)] ;; Amount (next 32 bytes) + {:selector selector + :recipient (address/normalized-hex recipient-hex) + :amount (money/from-hex amount-hex)}))) + +(defn- decode-erc-20-approve + "Decode ERC20 transfer data, which has the following function signature: + `approve(spender, amount)`" + [tx-data] + (when (>= (count tx-data) 138) + (let [selector (subs-bytes tx-data 0 4) ;; Function selector (first 4 bytes) + spender-hex (subs-bytes tx-data 16 36) ;; Spender address (next 32 bytes) + amount-hex (subs-bytes tx-data 36 68)] ;; Amount (next 32 bytes) + {:selector selector + :spender (address/normalized-hex spender-hex) + :amount (money/from-hex amount-hex)}))) + +(defn get-recipient + "Get the transaction recipient, depending on the transaction type" + [^Transaction tx] + (let [default (:to tx)] + (condp = (get-type tx) + :transaction/eth-transfer default + :transaction/erc-20-transfer (-> tx + get-data + decode-erc-20-transfer + :recipient + (or default)) + :transaction/erc-20-approve (-> tx + get-data + decode-erc-20-approve + :spender + (or default)) + default))) + +(defn get-amount + [^Transaction tx] + (let [default (-> tx :value money/from-hex)] + (condp = (get-type tx) + :transaction/eth-transfer default + :transaction/erc-20-transfer (-> tx get-data decode-erc-20-transfer :amount) + :transaction/erc-20-approve (-> tx get-data decode-erc-20-approve :amount) + default))) + +(-> + (decode-erc-20-transfer + "0xa9059cbb00000000000000000000000097654628dd47c2d88fc9b3d0cc38a92e46a32cd400000000000000000000000000000000000000000000000a0af513f7628a8000")) + +(def tt {:version 0 :from "0xb18ec1808bd8b84f244c6e34cbedee9b0cd7e1fb" :to "0x744d70fdbe2ba4cf95131626614a1763df805b9e" - :gas "0x21563" - :gasPrice "0xda9a06de5" + :gas "0x21e76" + :gasPrice "0x8b491f4cf" :value "0x0" - :nonce "0x19" - :maxFeePerGas "0xda9a06de5" - :maxPriorityFeePerGas "0x5d56b25c" + :nonce "0x1b" + :maxFeePerGas "0x8b491f4cf" + :maxPriorityFeePerGas "0x3b9aca00" :input "0x" :data - "0xa9059cbb00000000000000000000000097654628dd47c2d88fc9b3d0cc38a92e46a32cd4000000000000000000000000000000000000000000000016ca63768fcf860000" + "0xa9059cbb00000000000000000000000097654628dd47c2d88fc9b3d0cc38a92e46a32cd400000000000000000000000000000000000000000000000a0af513f7628a8000" :multiTransactionID 0}) -(def tx-met - {:FunctionSelector "0xa9059cbb" - :FunctionName "transfer" - :Recipient "0x97654628dd47c2d88fc9b3d0cc38a92e46a32cd4" - :Amount "420412000000000000000" - :TokenID ""}) +(get-amount tt) +(-> tt + decode-erc-20-transfer) -(defrecord Transaction [params metadata] - Object - (beautify-params [this] - (-> this :params beautify-transaction)) +(get-type tt) - (type [this] - (condp = (get-in this [:metadata :FunctionName]) - "" :transaction/eth-transfer - "transfer" :transaction/erc-20-transfer - "approve" :transaction/approve - :else :transaction/unknown)) - (amount [this] - (let [metadata-amount (get-in this [:metadata :Amount]) - params-amount (get-in this [:params :value])] - (-> (condp = - (.type this) - :transaction/erc-20-transfer - metadata-amount - :transaction/approve - metadata-amount - :else - params-amount) - money/bignumber))) - - (sender [this] - (-> this :params :from string/lower-case)) - - (recipient [this] - (let [metadata-recipient (get-in this [:metadata :Recipient]) - params-recipient (get-in this [:params :to])] - (-> (condp = - (.type this) - :transaction/erc-20-transfer - metadata-recipient - :transaction/approve - metadata-recipient - :else - params-recipient) - string/lower-case))) - - (token-address [this] - (when (->> this - .type - (contains? #{:transaction/erc-20-transfer - :transaction/approve})) - (-> this :params :to string/lower-case))) - - (summary [this] - {:type (.type this) - :amount (.amount this) - :recipient (.recipient this) - :sender (.sender this) - :token-address (.token-address this)})) - -(def tx (->Transaction tx-args tx-met)) +(defn get-contract-address + [^Transaction tx] + (condp = (get-type tx) + :transaction/erc-20-transfer (:to tx) + :transaction/erc-20-approve (:to tx) + nil)) diff --git a/src/status_im/contexts/wallet/wallet_connect/utils/typed_data.cljs b/src/status_im/contexts/wallet/wallet_connect/utils/typed_data.cljs index 4d499032dd..238828d308 100644 --- a/src/status_im/contexts/wallet/wallet_connect/utils/typed_data.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/utils/typed_data.cljs @@ -1,7 +1,6 @@ (ns status-im.contexts.wallet.wallet-connect.utils.typed-data (:require [clojure.string :as string] [status-im.constants :as constants] - [status-im.contexts.wallet.wallet-connect.utils.networks :as networks] [status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc] [utils.number :as number])) @@ -85,9 +84,8 @@ data-chain-id))) (defn sign - [password address data chain-id-eip155 version] - (let [legacy? (= version :v1) - chain-id (networks/eip155->chain-id chain-id-eip155)] + [password address data chain-id version] + (let [legacy? (= version :v1)] (rpc/wallet-safe-sign-typed-data data address password diff --git a/src/status_im/subs/wallet/dapps/transactions.cljs b/src/status_im/subs/wallet/dapps/transactions.cljs index da8fc7b0c6..53c59d2aed 100644 --- a/src/status_im/subs/wallet/dapps/transactions.cljs +++ b/src/status_im/subs/wallet/dapps/transactions.cljs @@ -6,11 +6,11 @@ [utils.string])) (rf/reg-sub - :wallet-connect/transaction-args + :wallet-connect/transaction :<- [:wallet-connect/current-request] (fn [{:keys [event transaction]}] (when (transactions/transaction-request? event) - transaction))) + (transactions/create-transaction transaction)))) (rf/reg-sub :wallet-connect/transaction-suggested-fees @@ -22,7 +22,7 @@ (rf/reg-sub :wallet-connect/transaction-max-fees-wei - :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/transaction] :<- [:wallet-connect/transaction-suggested-fees] (fn [[transaction suggested-fees]] (when transaction @@ -49,7 +49,7 @@ :wallet-connect/current-request-transaction-information :<- [:wallet-connect/chain-id] :<- [:wallet-connect/transaction-max-fees-wei] - :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/transaction] :<- [:wallet-connect/account-eth-token] :<- [:profile/currency] :<- [:profile/currency-symbol] @@ -78,3 +78,48 @@ (not (money/sufficient-funds? total-transaction-value balance)) :not-enough-assets-to-pay-gas-fees)})))) + +(rf/reg-sub + :wallet-connect/transaction-type + :<- [:wallet-connect/transaction] + transactions/get-type) + +(rf/reg-sub + :wallet-connect/transaction-recipient + :<- [:wallet-connect/transaction] + transactions/get-recipient) + +(rf/reg-sub + :wallet-connect/transaction-amount + :<- [:wallet-connect/transaction] + transactions/get-amount) + +(rf/reg-sub + :wallet-connect/transaction-contract + :<- [:wallet-connect/transaction] + :<- [:wallet-connect/transaction-type] + :<- [:wallet/tokens] + (fn [[transaction type tokens]] + (->> tokens + :by-address + (some #(when (= (:address %) (transactions/get-contract-address transaction)) %))) + + )) + +(comment + (->> [#_[:wallet-connect/transaction] + #_[:wallet-connect/transaction-type] + #_[:wallet-connect/transaction-recipient] + #_[:wallet-connect/transaction-amount] + [:wallet-connect/transaction-contract] + #_[:wallet/tokens] + ] + (map (comp deref rf/subscribe))) + + ;; => ({:address "0x744d70fdbe2ba4cf95131626614a1763df805b9e", + ;; :decimals 18, :key "1-0x744d70fdbe2ba4cf95131626614a1763df805b9e", :community-id nil, + ;; :symbol "SNT", :sources ["Uniswap Labs Default Token List" "Status Token List"], :name + ;; "Status Network Token", :type :erc20, :verified? true, :chain-id 1, + ;; :image nil}) + +)