test: wallet-connect transaction utils
This commit is contained in:
parent
699986c0b2
commit
3c00b7fdae
|
@ -3,13 +3,32 @@
|
|||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[promesa.core :as promesa]
|
||||
[schema.core :as schema]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
|
||||
[utils.hex :as hex]
|
||||
[utils.money :as money]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(def ^:private ?string-or-number
|
||||
[:or number? string?])
|
||||
|
||||
(def ?transaction
|
||||
[:map
|
||||
[:to :string]
|
||||
[:from :string]
|
||||
[:value ?string-or-number]
|
||||
[:gas {:optional true} ?string-or-number]
|
||||
[:gasPrice {:optional true} ?string-or-number]
|
||||
[:gasLimit {:optional true} ?string-or-number]
|
||||
[:nonce {:optional true} ?string-or-number]
|
||||
[:maxFeePerGas {:optional true} ?string-or-number]
|
||||
[:maxPriorityFeePerGas {:optional true} ?string-or-number]
|
||||
[:input {:optional true} [:maybe :string]]
|
||||
[:data {:optional true} [:maybe :string]]])
|
||||
|
||||
(defn transaction-request?
|
||||
[event]
|
||||
(->> (data-store/get-request-method event)
|
||||
|
@ -20,7 +39,7 @@
|
|||
;; show the estimated time, but when we implement it, we should allow to change it
|
||||
(def ^:constant default-tx-priority :medium)
|
||||
|
||||
(defn- strip-hex-prefix
|
||||
(defn strip-hex-prefix
|
||||
"Strips the extra 0 in hex value if present"
|
||||
[hex-value]
|
||||
(let [formatted-hex (string/replace hex-value #"^0x0*" "0x")]
|
||||
|
@ -28,8 +47,8 @@
|
|||
"0x0"
|
||||
formatted-hex)))
|
||||
|
||||
(defn- format-tx-hex-values
|
||||
"Due to how status-go expects hex values, we should remove the extra 0s in transaction hex values e.g. 0x0f -> 0xf"
|
||||
(defn format-tx-hex-values
|
||||
"Apply f on transaction keys that are hex numbers"
|
||||
[tx f]
|
||||
(let [tx-keys [:gasLimit :gas :gasPrice :nonce :value :maxFeePerGas :maxPriorityFeePerGas]]
|
||||
(reduce (fn [acc tx-key]
|
||||
|
@ -40,7 +59,14 @@
|
|||
tx
|
||||
tx-keys)))
|
||||
|
||||
(defn- prepare-transaction-for-rpc
|
||||
(schema/=> format-tx-hex-values
|
||||
[:=>
|
||||
[:catn
|
||||
[:tx ?transaction]
|
||||
[:f fn?]]
|
||||
?transaction])
|
||||
|
||||
(defn prepare-transaction-for-rpc
|
||||
"Formats the transaction and transforms it into a stringified JS object, ready to be passed to an RPC call."
|
||||
[tx]
|
||||
(-> tx
|
||||
|
@ -50,42 +76,67 @@
|
|||
bean/->js
|
||||
(transforms/js-stringify 0)))
|
||||
|
||||
(schema/=> prepare-transaction-for-rpc
|
||||
[:=>
|
||||
[:cat ?transaction]
|
||||
:string])
|
||||
|
||||
(defn beautify-transaction
|
||||
[tx]
|
||||
(let [hex->number #(-> % (subs 2) native-module/hex-to-number)]
|
||||
(-> tx
|
||||
(format-tx-hex-values hex->number)
|
||||
clj->js
|
||||
(js/JSON.stringify nil 2))))
|
||||
(-> tx
|
||||
(format-tx-hex-values hex/hex-to-number)
|
||||
clj->js
|
||||
(js/JSON.stringify nil 2)))
|
||||
|
||||
(defn- gwei->hex
|
||||
(schema/=> beautify-transaction
|
||||
[:=>
|
||||
[:cat ?transaction]
|
||||
:string])
|
||||
|
||||
(defn gwei->hex
|
||||
[gwei]
|
||||
(->> gwei
|
||||
money/gwei->wei
|
||||
native-module/number-to-hex
|
||||
(str "0x")))
|
||||
|
||||
(schema/=> gwei->hex
|
||||
[:=>
|
||||
[:cat ?string-or-number]
|
||||
:string])
|
||||
|
||||
(defn- get-max-fee-per-gas-key
|
||||
"Mapping transaction priority (which determines how quickly a tx is processed)
|
||||
to the `suggested-routes` key that should be used for `:maxPriorityFeePerGas`.
|
||||
|
||||
Returns `:high` | `:medium` | `:low`"
|
||||
to the `suggested-routes` key that should be used for `:maxPriorityFeePerGas` "
|
||||
[tx-priority]
|
||||
(get {:high :maxFeePerGasHigh
|
||||
:medium :maxFeePerGasMedium
|
||||
:low :maxFeePerGasLow}
|
||||
tx-priority))
|
||||
|
||||
(defn- dynamic-fee-tx?
|
||||
(def ?tx-priority [:enum :high :medium :low])
|
||||
(def ?max-fee-priority [:enum :maxFeePerGasHigh :maxFeePerGasMedium :maxFeePerGasLow])
|
||||
|
||||
(schema/=> get-max-fee-per-gas-key
|
||||
[:=>
|
||||
[:cat ?tx-priority]
|
||||
?max-fee-priority])
|
||||
|
||||
(defn dynamic-fee-tx?
|
||||
"Checks if a transaction has dynamic fees (EIP1559)"
|
||||
[tx]
|
||||
(every? tx [:maxFeePerGas :maxPriorityFeePerGas]))
|
||||
|
||||
(schema/=> dynamic-fee-tx?
|
||||
[:=>
|
||||
[:cat ?transaction]
|
||||
:boolean])
|
||||
|
||||
(defn- tx->eip1559-tx
|
||||
"Adds `:maxFeePerGas` and `:maxPriorityFeePerGas` for dynamic fee support (EIP1559) and
|
||||
removes `:gasPrice`, if the chain supports EIP1559 and the transaction doesn't already
|
||||
have dynamic fees."
|
||||
[tx suggested-fees tx-priority]
|
||||
[tx tx-priority suggested-fees]
|
||||
(if (and (:eip1559Enabled suggested-fees)
|
||||
(not (dynamic-fee-tx? tx)))
|
||||
(let [max-fee-per-gas-key (get-max-fee-per-gas-key tx-priority)
|
||||
|
@ -99,17 +150,49 @@
|
|||
(dissoc :gasPrice)))
|
||||
tx))
|
||||
|
||||
(defn- prepare-transaction-fees
|
||||
(def ?suggested-fees
|
||||
[:map
|
||||
[:eip1559Enabled boolean?]
|
||||
[:maxFeePerGasLow number?]
|
||||
[:maxFeePerGasMedium number?]
|
||||
[:maxFeePerGasHigh number?]
|
||||
[:maxPriorityFeePerGas number?]
|
||||
[:gasPrice {:optional true} number?]
|
||||
[:baseFee {:optional true} number?]
|
||||
[:l1GasFee {:optional true} number?]])
|
||||
|
||||
(schema/=> tx->eip1559-tx
|
||||
[:=>
|
||||
[:catn
|
||||
[:tx ?transaction]
|
||||
[:tx-priority ?tx-priority]
|
||||
[:suggested-fees ?suggested-fees]]
|
||||
?transaction])
|
||||
|
||||
(defn rename-gas-limit
|
||||
[tx]
|
||||
(if (:gasLimit tx)
|
||||
(-> tx
|
||||
;; NOTE: `gasLimit` is ignored on status-go when building a transaction
|
||||
;; (`wallet_buildTransaction`), so we're setting it as the `gas` property
|
||||
(assoc :gas (:gasLimit tx))
|
||||
(dissoc :gasLimit))
|
||||
tx))
|
||||
|
||||
(defn prepare-transaction-fees
|
||||
"Makes sure the transaction has the correct gas and fees properties"
|
||||
[tx tx-priority suggested-fees]
|
||||
(-> (assoc tx
|
||||
;; NOTE: `gasLimit` is ignored on status-go when building a transaction
|
||||
;; (`wallet_buildTransaction`), so we're setting it as the `gas` property
|
||||
:gas
|
||||
(or (:gasLimit tx)
|
||||
(:gas tx)))
|
||||
(dissoc :gasLimit)
|
||||
(tx->eip1559-tx suggested-fees tx-priority)))
|
||||
(-> tx
|
||||
rename-gas-limit
|
||||
(tx->eip1559-tx tx-priority suggested-fees)))
|
||||
|
||||
(schema/=> prepare-transaction-fees
|
||||
[:=>
|
||||
[:catn
|
||||
[:tx ?transaction]
|
||||
[:tx-priority ?tx-priority]
|
||||
[:suggested-fees ?suggested-fees]]
|
||||
?transaction])
|
||||
|
||||
(defn prepare-transaction
|
||||
"Formats and builds the incoming transaction, adding the missing properties and returning the final
|
||||
|
@ -126,6 +209,17 @@
|
|||
:tx-hash message-to-sign
|
||||
:suggested-fees suggested-fees}))
|
||||
|
||||
(schema/=> prepare-transaction
|
||||
[:=>
|
||||
[:catn
|
||||
[:tx ?transaction
|
||||
:chain-id :int
|
||||
:tx-priority ?tx-priority]]
|
||||
[:map {:closed true}
|
||||
[:tx-args :string]
|
||||
[:tx-hash :string]
|
||||
[:suggested-fees ?suggested-fees]]])
|
||||
|
||||
(defn sign-transaction
|
||||
[password address tx-hash tx-args chain-id]
|
||||
(promesa/let
|
||||
|
@ -133,6 +227,16 @@
|
|||
raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)]
|
||||
raw-tx))
|
||||
|
||||
(schema/=> sign-transaction
|
||||
[:=>
|
||||
[:catn
|
||||
[:password :string]
|
||||
[:address :string]
|
||||
[:tx-hash :string]
|
||||
[:tx-args ?transaction]
|
||||
[:chain-id :int]]
|
||||
:string])
|
||||
|
||||
(defn send-transaction
|
||||
[password address tx-hash tx-args chain-id]
|
||||
(promesa/let
|
||||
|
@ -141,3 +245,13 @@
|
|||
tx-args
|
||||
signature)]
|
||||
tx))
|
||||
|
||||
(schema/=> sign-transaction
|
||||
[:=>
|
||||
[:catn
|
||||
[:password :string]
|
||||
[:address :string]
|
||||
[:tx-hash :string]
|
||||
[:tx-args ?transaction]
|
||||
[:chain-id :int]]
|
||||
:string])
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
(ns status-im.contexts.wallet.wallet-connect.utils.transactions-test
|
||||
(:require
|
||||
[cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.transactions :as sut]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(deftest strip-hex-prefix-test
|
||||
(testing "passed value with extra 0 hex prefix"
|
||||
(is (= (sut/strip-hex-prefix "0x0123")
|
||||
"0x123")))
|
||||
|
||||
(testing "passed empty 0x value"
|
||||
(is (= (sut/strip-hex-prefix "0x")
|
||||
"0x0")))
|
||||
|
||||
(testing "passed value without 0 in prefix"
|
||||
(is (= (sut/strip-hex-prefix "0x123")
|
||||
"0x123"))))
|
||||
|
||||
(deftest format-tx-hex-values-test
|
||||
(testing "applies f only on the tx keys that are hex numbers"
|
||||
(let [tx {:to "0x0123"
|
||||
:from "0x0456"
|
||||
:value "0x0fff"
|
||||
:gas "0x01"}
|
||||
expected-tx {:to "0x0123"
|
||||
:from "0x0456"
|
||||
:value "0xfff"
|
||||
:gas "0x1"}]
|
||||
(is (= (sut/format-tx-hex-values tx sut/strip-hex-prefix)
|
||||
expected-tx)))))
|
||||
|
||||
(cljs.test/test-var #'format-tx-hex-values-test)
|
||||
|
||||
(deftest prepare-transaction-for-rpc-test
|
||||
(testing "original transaction nonce is removed"
|
||||
(let [tx {:to "0x123"
|
||||
:from "0x456"
|
||||
:value "0xfff"
|
||||
:nonce "0x1"}]
|
||||
(is (-> (sut/prepare-transaction-for-rpc tx)
|
||||
(transforms/json->clj)
|
||||
:nonce
|
||||
nil?))))
|
||||
(testing "transaction hex numbers are formatted"
|
||||
(let [tx {:to "0x123"
|
||||
:from "0x456"
|
||||
:value "0x0fff"
|
||||
:nonce "0x1"}]
|
||||
(is (-> (sut/prepare-transaction-for-rpc tx)
|
||||
(transforms/json->clj)
|
||||
:value
|
||||
(= "0xfff"))))))
|
||||
|
||||
(deftest beautify-transaction-test
|
||||
(testing "hex number values are converted to utf-8"
|
||||
(let [tx {:to "0x123"
|
||||
:from "0x456"
|
||||
:value "0xfff"}]
|
||||
(is (-> (sut/beautify-transaction tx)
|
||||
(transforms/json->clj)
|
||||
:value
|
||||
(= 4095))))))
|
||||
|
||||
(cljs.test/test-var #'beautify-transaction-test)
|
||||
|
||||
(deftest gwei->hex-test
|
||||
(testing "gwei amount is converted to wei as hex"
|
||||
(is (-> (sut/gwei->hex "1000000000")
|
||||
(= "0xde0b6b3a7640000")))))
|
||||
|
||||
(deftest dynamic-fee-tx?-test
|
||||
(testing "correctly asserts tx as dynamic"
|
||||
(is (-> {:to "0x123"
|
||||
:from "0x123"
|
||||
:value "0x123"
|
||||
:maxFeePerGas "0x123"
|
||||
:maxPriorityFeePerGas "0x1"}
|
||||
sut/dynamic-fee-tx?)))
|
||||
|
||||
(testing "correnctly asserts tx as not dynamic (legacy)"
|
||||
(is (-> {:to "0x123"
|
||||
:from "0x123"
|
||||
:value "0x123"}
|
||||
sut/dynamic-fee-tx?
|
||||
not)))
|
||||
|
||||
(testing "asserts tx as not dynamic (legacy) when required keys are only partially present"
|
||||
(is (-> {:to "0x123"
|
||||
:from "0x123"
|
||||
:value "0x123"
|
||||
:maxFeePerGas "0x123"}
|
||||
sut/dynamic-fee-tx?
|
||||
not))))
|
||||
|
||||
(deftest prepare-transaction-fees-test
|
||||
(let [suggested-fees {:eip1559Enabled true
|
||||
:maxFeePerGasLow 1
|
||||
:maxFeePerGasMedium 2
|
||||
:maxFeePerGasHigh 3
|
||||
:maxPriorityFeePerGas 100}]
|
||||
|
||||
(testing "correctly prepares eip1559 gas values"
|
||||
(let [tx {:to "0x123"
|
||||
:from "0x123"
|
||||
:value "0x123"}
|
||||
tx-priority :high]
|
||||
(is (= (sut/prepare-transaction-fees tx
|
||||
tx-priority
|
||||
suggested-fees)
|
||||
{:to (:to tx)
|
||||
:from (:from tx)
|
||||
:value (:value tx)
|
||||
:maxFeePerGas "0xb2d05e00"
|
||||
:maxPriorityFeePerGas "0x174876e800"}))))
|
||||
|
||||
(testing "renames gasLimit key to gas and removed gasPrice"
|
||||
(let [gasLimit "0x1234"
|
||||
prepared-tx (sut/prepare-transaction-fees {:to "0x123"
|
||||
:from "0x123"
|
||||
:value "0x123"
|
||||
:gasPrice "0x123"
|
||||
:gasLimit gasLimit}
|
||||
:high
|
||||
suggested-fees)]
|
||||
(is (and (= (:gas prepared-tx) gasLimit)
|
||||
(nil? (:gasLimit prepared-tx))
|
||||
(nil? (:gasPrice prepared-tx))))))
|
||||
|
||||
(testing "returns original tx for non-dynamic transactions"
|
||||
(let [tx {:to "0x123"
|
||||
:from "0x123"
|
||||
:value "0x123"
|
||||
:gasPrice "0x123"}
|
||||
suggested-fees (assoc suggested-fees :eip1559Enabled false)]
|
||||
(is (= (sut/prepare-transaction-fees tx :high suggested-fees)
|
||||
tx))))
|
||||
|
||||
(testing "throws a schema exception due wrong arguments"
|
||||
(let [tx {:to "0x123"
|
||||
:from "0x123"
|
||||
:value "0x123"}
|
||||
wrong-tx-priority :wrong
|
||||
wrong-suggested-fees {}]
|
||||
(is (thrown? js/Error
|
||||
(sut/prepare-transaction-fees tx
|
||||
wrong-tx-priority
|
||||
wrong-suggested-fees)))))))
|
|
@ -37,3 +37,12 @@
|
|||
[:=>
|
||||
[:cat [:or :string :int]]
|
||||
:string])
|
||||
|
||||
(defn hex-to-number
|
||||
[hex]
|
||||
(-> hex normalize-hex native-module/hex-to-number))
|
||||
|
||||
(schema/=> hex-to-number
|
||||
[:=>
|
||||
[:cat :string]
|
||||
:int])
|
||||
|
|
Loading…
Reference in New Issue