Update sending transaction end point (#21480) (#21541)

* Update sending transaction end point

* Transaction approval

* Fixes

* Fixes

* Fixes

* Fix for issue 3

* Fixes for 2, 4, 7

* Update

* Fixes

* Fixes
This commit is contained in:
Alexander 2024-11-28 14:08:35 +01:00 committed by GitHub
parent 5f8d5fd57e
commit de2b5fb935
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 488 additions and 443 deletions

View File

@ -45,6 +45,12 @@
"wallet.suggested.routes"
{:fx [[:dispatch [:wallet/handle-suggested-routes (transforms/js->clj event-js)]]]}
"wallet.router.sign-transactions"
{:fx [[:dispatch [:wallet/sign-transactions-signal-received (transforms/js->clj event-js)]]]}
"wallet.router.transactions-sent"
{:fx [[:dispatch [:wallet/transactions-sent-signal-received (transforms/js->clj event-js)]]]}
"envelope.sent"
(messages.transport/update-envelopes-status
cofx

View File

@ -116,6 +116,7 @@
:on-close #(rf/dispatch [:standard-auth/reset-login-password])
:content (fn []
[keycard.pin/auth {:on-complete on-complete}])}]]]})
(rf/reg-event-fx :standard-auth/authorize-with-keycard authorize-with-keycard)
(defn authorize-with-password

View File

@ -565,6 +565,18 @@
(:less-than-five-minutes constants/wallet-transaction-estimation) "3-5"
">5"))
(defn transactions->hash-to-transaction-map
[transactions]
(reduce
(fn [acc {to-chain :toChain tx-hash :hash}]
(assoc acc
tx-hash
{:status :pending
:id tx-hash
:chain-id to-chain}))
{}
transactions))
(defn get-accounts-with-token-balance
[accounts token]
(let [operable-account (filter :operable? (vals accounts))

View File

@ -5,6 +5,7 @@
[promesa.core :as promesa]
[status-im.common.json-rpc.events :as json-rpc]
[status-im.contexts.profile.recover.effects :as profile.recover.effects]
[status-im.contexts.wallet.rpc :as wallet-rpc]
[taoensso.timbre :as log]
[utils.re-frame :as rf]
[utils.security.core :as security]
@ -103,3 +104,22 @@
(-> (verify-private-key-for-keypair keypair-key-uid private-key)
(promesa/then (partial rf/call-continuation on-success))
(promesa/catch (partial rf/call-continuation on-error)))))
(defn sign-transaction-hashes
[hashes address password]
(-> (promesa/all
(for [h hashes]
(promesa/let [signature (wallet-rpc/sign-message h address password)]
{:message h
:signature signature})))
(promesa/catch (fn [err]
(throw (ex-info "Failed to sign transaction hashes"
{:error err
:code :error/sign-transaction-hashes}))))))
(rf/reg-fx
:effects.wallet/sign-transaction-hashes
(fn [{:keys [hashes address password on-success on-error]}]
(-> (sign-transaction-hashes hashes address password)
(promesa/then on-success)
(promesa/catch on-error))))

View File

@ -701,3 +701,36 @@
(let [full-status (cske/transform-keys message transforms/->kebab-case-keyword)]
{:db (assoc-in db [:wallet :blockchain] full-status)})))
(rf/reg-event-fx
:wallet/sign-transactions-signal-received
(fn [{:keys [db]} [{send-details :sendDetails :as data}]]
(let [type (if (or (= (:fromToken send-details)
(:toToken send-details))
(string/blank? (:toToken send-details)))
:send
:swap)
callback-fx (get-in db [:wallet :ui type :sign-transactions-callback-fx])
error-response (:errorResponse send-details)]
{:fx [(when (and callback-fx (not error-response))
callback-fx)]
:db (-> db
(assoc-in [:wallet :ui type :sign-transactions-callback-fx] nil)
(assoc-in [:wallet :ui type :error-response] error-response)
(assoc-in [:wallet :ui type :transaction-for-signing] data))})))
(rf/reg-event-fx
:wallet/transactions-sent-signal-received
(fn [{:keys [db]}
[{sent-transactions :sentTransactions
send-details :sendDetails}]]
(let [swap? (get-in db [:wallet :ui :swap])]
{:fx [[:dispatch
(if-let [error-response (:errorResponse send-details)]
[(if swap?
:wallet.swap/transaction-failure
:wallet/transaction-failure)
error-response]
[(if swap?
:wallet.swap/transaction-success
:wallet/transaction-success)
sent-transactions])]]})))

View File

@ -0,0 +1,59 @@
(ns status-im.contexts.wallet.rpc
(:require [oops.core :as oops]
[promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events]
[status-im.constants :as constants]
[utils.hex]
[utils.transforms :as transforms]))
(defn build-transaction
[chain-id tx]
(promesa/let [res (rpc-events/call-async "wallet_buildTransaction" true chain-id tx)]
{:message-to-sign (oops/oget res :messageToSign)
:tx-args (oops/oget res :txArgs)}))
(defn 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 hash-message-eip-191
[message]
(rpc-events/call-async "wallet_hashMessageEIP191" true message))
(defn safe-sign-typed-data
[data address password chain-id legacy?]
(rpc-events/call-async "wallet_safeSignTypedDataForDApps"
true
data
address
password
chain-id
legacy?))
(defn get-suggested-fees
[chain-id]
(-> (rpc-events/call-async "wallet_getSuggestedFees" true chain-id)
(promesa/then transforms/js->clj)))
(defn send-transaction-with-signature
[chain-id tx-args signature]
(rpc-events/call-async "wallet_sendTransactionWithSignature"
true
chain-id
constants/transaction-pending-type-wallet-connect-transfer
(transforms/js-stringify tx-args 0)
signature))
(defn sign-message
[message address password]
(-> (rpc-events/call-async "wallet_signMessage"
true
message
address
password)
(promesa/then utils.hex/normalize-hex)))

View File

@ -1,7 +1,6 @@
(ns status-im.contexts.wallet.send.events
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[status-im.constants :as constants]
[status-im.contexts.wallet.collectible.utils :as collectible.utils]
[status-im.contexts.wallet.common.utils :as utils]
@ -9,11 +8,10 @@
[status-im.contexts.wallet.data-store :as data-store]
[status-im.contexts.wallet.send.utils :as send-utils]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.hex :as utils.hex]
[utils.money :as utils.money]
[utils.number]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-event-fx :wallet/clean-send-data
(fn [{:keys [db]}]
@ -327,22 +325,30 @@
(rf/reg-event-fx
:wallet/set-token-amount-to-send
(fn [{:keys [db]} [{:keys [amount stack-id start-flow?]}]]
{:db (assoc-in db [:wallet :ui :send :amount] amount)
:fx [[:dispatch
[:wallet/wizard-navigate-forward
{:current-screen stack-id
:start-flow? start-flow?
:flow-id :wallet-send-flow}]]]}))
(let [last-request-uuid (get-in db [:wallet :ui :send :last-request-uuid])]
{:db (-> db
(assoc-in [:wallet :ui :send :amount] amount)
(update-in [:wallet :ui :send] dissoc :transaction-for-signing))
:fx [[:dispatch [:wallet/build-transactions-from-route {:request-uuid last-request-uuid}]]
[:dispatch
[:wallet/wizard-navigate-forward
{:current-screen stack-id
:start-flow? start-flow?
:flow-id :wallet-send-flow}]]]})))
(rf/reg-event-fx
:wallet/set-token-amount-to-bridge
(fn [{:keys [db]} [{:keys [amount stack-id start-flow?]}]]
{:db (assoc-in db [:wallet :ui :send :amount] amount)
:fx [[:dispatch
[:wallet/wizard-navigate-forward
{:current-screen stack-id
:start-flow? start-flow?
:flow-id :wallet-bridge-flow}]]]}))
(let [last-request-uuid (get-in db [:wallet :ui :send :last-request-uuid])]
{:db (-> db
(assoc-in [:wallet :ui :send :amount] amount)
(update-in [:wallet :ui :send] dissoc :transaction-for-signing))
:fx [[:dispatch [:wallet/build-transactions-from-route {:request-uuid last-request-uuid}]]
[:dispatch
[:wallet/wizard-navigate-forward
{:current-screen stack-id
:start-flow? start-flow?
:flow-id :wallet-bridge-flow}]]]})))
(rf/reg-event-fx
:wallet/clean-bridge-to-selection
@ -458,7 +464,8 @@
:token-networks-ids token-networks-ids
:tx-type tx-type
:receiver? true}))
params [{:uuid (str (random-uuid))
request-uuid (str (random-uuid))
params [{:uuid request-uuid
:sendType send-type
:addrFrom from-address
:addrTo to-address
@ -477,7 +484,8 @@
{:db (update-in db
[:wallet :ui :send]
#(-> %
(assoc :amount amount
(assoc :last-request-uuid request-uuid
:amount amount
:loading-suggested-routes? true
:sender-network-values sender-network-values
:receiver-network-values receiver-network-values)
@ -565,27 +573,39 @@
:else [:wallet/suggested-routes-success (fix-routes data)
enough-assets?])]]})))))
(rf/reg-event-fx :wallet/add-authorized-transaction
(fn [{:keys [db]} [transaction]]
(let [transaction-batch-id (:id transaction)
transaction-hashes (:hashes transaction)
transaction-ids (flatten (vals transaction-hashes))
transaction-details (send-utils/map-multitransaction-by-ids transaction-batch-id
transaction-hashes)]
(rf/reg-event-fx
:wallet/transaction-success
(fn [{:keys [db]} [sent-transactions]]
(let [wallet-transactions (get-in db [:wallet :transactions] {})
transactions (utils/transactions->hash-to-transaction-map sent-transactions)
transaction-ids (->> transactions
vals
(map :hash))]
{:db (-> db
(assoc-in [:wallet :ui :send :just-completed-transaction?] true)
(assoc-in [:wallet :transactions] transaction-details)
(assoc-in [:wallet :ui :send :transaction-ids] transaction-ids))
:fx [;; Remove wallet/stop-and-clean-suggested-routes event when we move to new transaction
;; submission process as the routes as stopped by status-go
[:dispatch
[:wallet/stop-and-clean-suggested-routes]]
[:dispatch
[:wallet/end-transaction-flow]]
(assoc-in [:wallet :ui :send :transaction-ids] transaction-ids)
(assoc-in [:wallet :transactions] (merge wallet-transactions transactions)))
:fx [[:dispatch [:wallet/end-transaction-flow]]
[:dispatch-later
[{:ms 2000
:dispatch [:wallet/stop-and-clean-suggested-routes]}]]
[:dispatch-later
[{:ms 2000
:dispatch [:wallet/clean-just-completed-transaction]}]]]})))
(rf/reg-event-fx
:wallet/transaction-failure
(fn [_ [{:keys [details]}]]
{:fx [[:dispatch [:wallet/end-transaction-flow]]
[:dispatch-later
[{:ms 2000
:dispatch [:wallet/stop-and-clean-suggested-routes]}]]
[:dispatch
[:toasts/upsert
{:id :send-transaction-failure
:type :negative
:text (or details "An error occured")}]]]}))
(rf/reg-event-fx :wallet/clean-just-completed-transaction
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :just-completed-transaction?)}))
@ -609,100 +629,65 @@
[{:ms 20
:dispatch [:wallet/clean-up-transaction-flow]}]]]})))
(rf/reg-event-fx :wallet/send-transaction
(fn [{:keys [db]} [sha3-pwd]]
(let [routes (get-in db [:wallet :ui :send :route])
first-route (first routes)
from-address (get-in db [:wallet :current-viewing-account-address])
transaction-type (get-in db [:wallet :ui :send :tx-type])
multi-transaction-type (if (= :tx/bridge transaction-type)
constants/multi-transaction-type-bridge
constants/multi-transaction-type-send)
token (get-in db [:wallet :ui :send :token])
collectible (get-in db [:wallet :ui :send :collectible])
first-route-from-chain-id (get-in first-route [:from :chain-id])
token-id (if token
(:symbol token)
(get-in collectible [:id :token-id]))
erc20-transfer? (and token (not= token-id "ETH"))
eth-transfer? (and token (not erc20-transfer?))
token-address (cond collectible
(get-in collectible
[:id :contract-id :address])
erc20-transfer?
(get-in token [:balances-per-chain first-route-from-chain-id :address]))
to-address (get-in db [:wallet :ui :send :to-address])
transaction-paths (into []
(mapcat
(fn [route]
(let [approval-required? (:approval-required route)
data (when erc20-transfer?
(native-module/encode-transfer
(address/normalized-hex to-address)
(:amount-in route)))
base-path (utils/transaction-path
{:to-address to-address
:from-address from-address
:route route
:token-address token-address
:token-id-from (if collectible
(utils.hex/number-to-hex
token-id)
token-id)
:data data
:eth-transfer? eth-transfer?})]
(if approval-required?
[(utils/approval-path {:route route
:token-address token-address
:from-address from-address
:to-address to-address})
base-path]
[base-path]))))
routes)
request-params
[(utils/multi-transaction-command
{:from-address from-address
:to-address to-address
:from-asset token-id
:to-asset token-id
:amount-out (if eth-transfer? (:amount-out first-route) "0x0")
:multi-transaction-type multi-transaction-type})
transaction-paths
sha3-pwd]]
(log/info "multi transaction called")
{:json-rpc/call [{:method "wallet_createMultiTransaction"
:params request-params
:on-success (fn [result]
(when result
(rf/dispatch [:wallet/add-authorized-transaction result])
(rf/dispatch [:hide-bottom-sheet])))
(rf/reg-event-fx
:wallet/build-transactions-from-route
(fn [_ [{:keys [request-uuid slippage] :or {slippage constants/default-slippage}}]]
{:json-rpc/call [{:method "wallet_buildTransactionsFromRoute"
:params [{:uuid request-uuid
:slippagePercentage slippage}]
:on-error (fn [error]
(log/error "failed to build transactions from route"
{:event :wallet/build-transactions-from-route
:error error})
(rf/dispatch [:toasts/upsert
{:id :build-transactions-from-route-error
:type :negative
:text (:message error)}]))}]}))
(rf/reg-event-fx
:wallet/prepare-signatures-for-transactions
(fn [{:keys [db]} [type sha3-pwd]]
(let [transaction-for-signing (get-in db [:wallet :ui type :transaction-for-signing])]
{:fx [[:effects.wallet/sign-transaction-hashes
{:hashes (get-in transaction-for-signing [:signingDetails :hashes])
:address (get-in transaction-for-signing [:signingDetails :address])
:password (security/safe-unmask-data sha3-pwd)
:on-success (fn [signatures]
(rf/dispatch [:wallet/send-router-transactions-with-signatures type
signatures]))
:on-error (fn [error]
(log/error "failed to prepare signatures for transactions"
{:event :wallet/prepare-signatures-for-transactions
:error error})
(rf/dispatch [:toasts/upsert
{:id :prepare-signatures-for-transactions-error
:type :negative
:text (:message error)}]))}]]})))
(rf/reg-event-fx
:wallet/send-router-transactions-with-signatures
(fn [{:keys [db]} [type signatures]]
(let [transaction-for-signing (get-in db [:wallet :ui type :transaction-for-signing])
signatures-map (reduce (fn [acc {:keys [message signature]}]
(assoc acc
message
(send-utils/signature-rsv signature)))
{}
signatures)]
{:json-rpc/call [{:method "wallet_sendRouterTransactionsWithSignatures"
:params [{:uuid (get-in transaction-for-signing [:sendDetails :uuid])
:signatures signatures-map}]
:on-success (fn []
(rf/dispatch [:hide-bottom-sheet]))
:on-error (fn [error]
(log/error "failed to send transaction"
{:event :wallet/send-transaction
:error error
:params request-params})
(log/error "failed to send router transactions with signatures"
{:event :wallet/send-router-transactions-with-signatures
:error error})
(rf/dispatch [:toasts/upsert
{:id :send-transaction-error
{:id :send-router-transactions-with-signatures-error
:type :negative
:text (:message error)}]))}]})))
(rf/reg-event-fx :wallet/proceed-with-transactions-signatures
(fn [_ [signatures]]
{:json-rpc/call [{:method "wallet_proceedWithTransactionsSignatures"
:params [signatures]
:on-success (fn [result]
(when result
(rf/dispatch [:wallet/add-authorized-transaction result])
(rf/dispatch [:hide-bottom-sheet])))
:on-error (fn [error]
(log/error "failed to proceed-with-transactions-signatures"
{:event :wallet/proceed-with-transactions-signatures
:error error})
(rf/dispatch [:toasts/upsert
{:id :send-transaction-error
:type :negative
:text (:message error)}]))}]}))
(rf/reg-event-fx
:wallet/select-from-account
(fn [{db :db} [{:keys [address stack-id network-details start-flow?]}]]

View File

@ -575,29 +575,6 @@
:from-locked-amounts {}}}}})
(is (match? expected-db (:db (dispatch [event-id suggested-routes timestamp]))))))
(h/deftest-event :wallet/add-authorized-transaction
[event-id dispatch]
(let [hashes {:chain-1 ["tx-1" "tx-2" "tx-3"]
:chain-2 ["tx-4" "tx-5"]
:chain-3 ["tx-6" "tx-7" "tx-8" "tx-9"]}
transaction-id "txid-1"
expected-result {:db {:wallet {:ui {:send {:transaction-ids ["tx-1" "tx-2" "tx-3"
"tx-4" "tx-5" "tx-6"
"tx-7" "tx-8" "tx-9"]}}
:transactions (send-utils/map-multitransaction-by-ids
transaction-id
hashes)}}
:fx [[:dispatch
[:wallet/stop-and-clean-suggested-routes]]
[:dispatch [:wallet/end-transaction-flow]]
[:dispatch-later
[{:ms 2000
:dispatch [:wallet/clean-just-completed-transaction]}]]]}]
(is (match? expected-result
(dispatch [event-id
{:id transaction-id
:hashes hashes}])))))
(h/deftest-event :wallet/select-from-account
[event-id dispatch]
(let [stack-id :screen/stack

View File

@ -13,8 +13,7 @@
[status-im.contexts.wallet.send.utils :as send-utils]
[status-im.feature-flags :as ff]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
[utils.re-frame :as rf]))
(defn- transaction-title
[{:keys [token-display-name amount account route to-network image-url transaction-type
@ -238,6 +237,7 @@
bridge-to-chain-id]))
loading-suggested-routes? (rf/sub
[:wallet/wallet-send-loading-suggested-routes?])
transaction-for-signing (rf/sub [:wallet/wallet-send-transaction-for-signing])
from-account-props {:customization-color account-color
:size 32
:emoji (:emoji account)
@ -268,17 +268,21 @@
:transaction-type transaction-type}]
(when (and (not loading-suggested-routes?) route (seq route))
[standard-auth/slide-button
{:keycard-supported? true
:size :size-48
:track-text (if (= transaction-type :tx/bridge)
(i18n/label :t/slide-to-bridge)
(i18n/label :t/slide-to-send))
:container-style {:z-index 2}
{:keycard-supported? (get-in transaction-for-signing
[:signingDetails :signOnKeycard])
:size :size-48
:track-text (if (= transaction-type :tx/bridge)
(i18n/label :t/slide-to-bridge)
(i18n/label :t/slide-to-send))
:container-style {:z-index 2}
:disabled? (not transaction-for-signing)
:customization-color account-color
:on-auth-success #(rf/dispatch
[:wallet/send-transaction
(security/safe-unmask-data %)])
:auth-button-label (i18n/label :t/confirm)}])]
:on-auth-success
(fn [data]
(rf/dispatch
[:wallet/prepare-signatures-for-transactions
:send data]))
:auth-button-label (i18n/label :t/confirm)}])]
:gradient-cover? true
:customization-color (:color account)}
[rn/view

View File

@ -303,3 +303,9 @@
(defn bridge-disabled?
[token-symbol]
(not (constants/bridge-assets token-symbol)))
(defn signature-rsv
[signature]
{:r (subs signature 0 64)
:s (subs signature 64 128)
:v (subs signature 128 130)})

View File

@ -1,14 +1,11 @@
(ns status-im.contexts.wallet.swap.events
(:require [native-module.core :as native-module]
[re-frame.core :as rf]
(:require [re-frame.core :as rf]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.send.utils :as send-utils]
[status-im.contexts.wallet.sheets.network-selection.view :as network-selection]
[status-im.contexts.wallet.swap.utils :as swap-utils]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.number :as number]))
@ -151,10 +148,9 @@
:disabledFromChainIDs disabled-from-chain-ids
:disabledToChainIDs disabled-to-chain-ids
:gasFeeMode gas-rates
:fromLockedAmount from-locked-amount}
amount-in (assoc :amountIn amount-in-hex)
amount-out (assoc :amountOut amount-out-hex))]]
:fromLockedAmount from-locked-amount
:amountOut (or amount-out-hex "0x0")}
amount-in (assoc :amountIn amount-in-hex))]]
(when-let [amount (or amount-in amount-out)]
{:db (update-in db
[:wallet :ui :swap]
@ -270,150 +266,21 @@
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui] dissoc :swap)}))
(rf/reg-event-fx :wallet/swap-transaction
(fn [{:keys [db]} [sha3-pwd]]
(let [wallet-address (get-in db
[:wallet
:current-viewing-account-address])
{:keys [asset-to-pay asset-to-receive
swap-proposal network amount
approval-transaction-id
max-slippage]} (get-in db [:wallet :ui :swap])
transactions (get-in db [:wallet :transactions])
approval-transaction (when approval-transaction-id
(get transactions approval-transaction-id))
already-approved? (and approval-transaction
(= (:status approval-transaction)
:confirmed))
approval-required? (and (:approval-required swap-proposal)
(not already-approved?))
multi-transaction-type constants/multi-transaction-type-swap
swap-chain-id (:chain-id network)
token-id-from (:symbol asset-to-pay)
token-id-to (:symbol asset-to-receive)
erc20-transfer? (and asset-to-pay (not= token-id-from "ETH"))
eth-transfer? (and asset-to-pay (not erc20-transfer?))
token-address (when erc20-transfer?
(get-in asset-to-pay
[:balances-per-chain swap-chain-id
:address]))
data (when erc20-transfer?
(native-module/encode-transfer
(address/normalized-hex wallet-address)
(:amount-in swap-proposal)))
transaction-paths (if approval-required?
[(utils/approval-path
{:route swap-proposal
:token-address token-address
:from-address wallet-address
:to-address wallet-address})]
[(utils/transaction-path
{:to-address wallet-address
:from-address wallet-address
:route swap-proposal
:token-address token-address
:token-id-from token-id-from
:token-id-to token-id-to
:data data
:slippage-percentage max-slippage
:eth-transfer? eth-transfer?})])
request-params [(utils/multi-transaction-command
{:from-address wallet-address
:to-address wallet-address
:from-asset token-id-from
:to-asset (if approval-required?
token-id-from
token-id-to)
:amount-out (if eth-transfer?
(:amount-out swap-proposal)
"0x0")
:multi-transaction-type
multi-transaction-type})
transaction-paths
sha3-pwd]]
(log/info "multi transaction called")
{:json-rpc/call [{:method "wallet_createMultiTransaction"
:params request-params
:on-success (fn [result]
(when result
(let [receive-token-decimals (:decimals asset-to-receive)
amount-out (:amount-out swap-proposal)
receive-amount
(when amount-out
(-> amount-out
(number/hex->whole receive-token-decimals)
(money/to-fixed receive-token-decimals)))]
(rf/dispatch [:centralized-metrics/track
(if approval-required?
:metric/swap-approval-execution-start
:metric/swap-transaction-execution-start)
(cond-> {:network swap-chain-id
:pay_token token-id-from}
(not approval-required?)
(assoc :receive_token token-id-to))])
(rf/dispatch [:wallet.swap/add-authorized-transaction
(cond-> {:transaction result
:approval-transaction?
approval-required?}
(not approval-required?)
(assoc :swap-data
{:pay-token-symbol token-id-from
:pay-amount amount
:receive-token-symbol token-id-to
:receive-amount receive-amount
:swap-chain-id swap-chain-id}))])
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:dismiss-modal
(if approval-required?
:screen/wallet.swap-set-spending-cap
:screen/wallet.swap-confirmation)])
(when-not approval-required?
(rf/dispatch [:wallet/end-swap-flow])
(debounce/debounce-and-dispatch
[:toasts/upsert
{:id :swap-transaction-pending
:icon :i/info
:type :neutral
:text (i18n/label :t/swapping-to
{:pay-amount amount
:pay-token-symbol token-id-from
:receive-token-symbol token-id-to
:receive-amount receive-amount})}]
500)))))
:on-error (fn [error]
(rf/dispatch [:centralized-metrics/track
(if approval-required?
:metric/swap-approval-execution-failed
:metric/swap-transaction-execution-failed)
(cond-> {:network swap-chain-id
:error error
:pay_token token-id-from}
(not approval-required?)
(assoc :receive_token token-id-to))])
(log/error "failed swap transaction"
{:event :wallet/swap-transaction
:error error
:params request-params})
(rf/dispatch [:toasts/upsert
{:id :swap-transaction-error
:type :negative
:text (:message error)}]))}]})))
(rf/reg-event-fx :wallet.swap/add-authorized-transaction
(fn [{:keys [db]} [{:keys [transaction swap-data approval-transaction?]}]]
(let [transactions (get-in db [:wallet :transactions] {})
transaction-batch-id (:id transaction)
transaction-hashes (:hashes transaction)
transaction-ids (flatten (vals transaction-hashes))
(fn [{:keys [db]} [{:keys [sent-transactions swap-data approval-transaction?]}]]
(let [wallet-transactions (get-in db [:wallet :transactions] {})
transactions (utils/transactions->hash-to-transaction-map sent-transactions)
transaction-ids (->> transactions
vals
(map :hash))
transaction-id (first transaction-ids)
transaction-details (cond-> (send-utils/map-multitransaction-by-ids transaction-batch-id
transaction-hashes)
transaction-details (cond-> transactions
:always (assoc-in [transaction-id :tx-type] :swap)
swap-data (assoc-in [transaction-id :swap-data] swap-data))
swap-transaction-ids (get-in db [:wallet :swap-transaction-ids])]
{:db (cond-> db
:always (assoc-in [:wallet :transactions]
(merge transactions transaction-details))
(merge wallet-transactions transaction-details))
:always (assoc-in [:wallet :ui :swap :transaction-ids] transaction-ids)
approval-transaction? (assoc-in [:wallet :ui :swap :approval-transaction-id]
transaction-id)
@ -463,7 +330,8 @@
(not transaction-confirmed?)
(assoc :db (update-in db [:wallet :ui :swap] dissoc :approval-transaction-id)))))))
(rf/reg-event-fx :wallet.swap/swap-transaction-update
(rf/reg-event-fx
:wallet.swap/swap-transaction-update
(fn [{:keys [db]} [{:keys [tx-hash status]}]]
(let [{:keys [pay-amount pay-token-symbol
receive-amount receive-token-symbol
@ -494,7 +362,8 @@
:receive-amount receive-amount})
(i18n/label :t/swap-failed))}]]]}))))
(rf/reg-event-fx :wallet.swap/flip-assets
(rf/reg-event-fx
:wallet.swap/flip-assets
(fn [{:keys [db]}]
(let [{:keys [asset-to-pay asset-to-receive
swap-proposal amount network]} (get-in db [:wallet :ui :swap])
@ -528,14 +397,169 @@
:previous_token (:symbol asset-to-receive)
:new_token (:symbol asset-to-pay)}]]]})))
(rf/reg-event-fx :wallet/end-swap-flow
(rf/reg-event-fx
:wallet.swap/set-sign-transactions-callback-fx
(fn [{:keys [db]} [callback-fx]]
{:db (assoc-in db [:wallet :ui :swap :sign-transactions-callback-fx] callback-fx)}))
(rf/reg-event-fx
:wallet.swap/approve
(fn [{:keys [db]}]
(let [launch-screen (get-in db [:wallet :ui :swap :launch-screen])
address (get-in db [:wallet :current-viewing-account-address])]
{:fx [(when (= launch-screen :wallet-stack)
[:dispatch [:wallet/navigate-to-account-within-stack address]])
(let [last-request-uuid (get-in db [:wallet :ui :swap :last-request-uuid])
max-slippage (get-in db [:wallet :ui :swap :max-slippage])]
{:fx [[:dispatch
[:wallet/build-transactions-from-route
{:request-uuid last-request-uuid
:slippage max-slippage}]]
[:dispatch
[:wallet.swap/set-sign-transactions-callback-fx
[:dispatch [:open-modal :screen/wallet.swap-set-spending-cap]]]]]})))
(rf/reg-event-fx
:wallet.swap/review-swap
(fn [{:keys [db]}]
(let [last-request-uuid (get-in db [:wallet :ui :swap :last-request-uuid])
max-slippage (get-in db [:wallet :ui :swap :max-slippage])]
{:db (-> db
(update-in [:wallet :ui :swap] dissoc :transaction-for-signing))
:fx [[:dispatch
[:wallet/build-transactions-from-route
{:request-uuid last-request-uuid
:slippage max-slippage}]]
[:dispatch
[:navigate-to-within-stack
[:screen/wallet.swap-confirmation
:screen/wallet.setup-swap]]]]})))
(defn transaction-approval-required?
[transactions {:keys [swap-proposal approval-transaction-id]}]
(let [approval-transaction (when approval-transaction-id
(get transactions approval-transaction-id))
already-approved? (and approval-transaction
(= (:status approval-transaction)
:confirmed))]
(and (:approval-required swap-proposal)
(not already-approved?))))
(rf/reg-event-fx
:wallet.swap/mark-as-pending
(fn [{:keys [db]} [transaction-id]]
{:db (-> db
(assoc-in [:wallet :transactions transaction-id :status] :pending)
(assoc-in [:wallet :ui :swap :approval-transaction-id] transaction-id))}))
(rf/reg-event-fx
:wallet.swap/transaction-success
(fn [{:keys [db]} [sent-transactions]]
(let [transactions (get-in db [:wallet :transactions])
{:keys [swap-proposal
asset-to-pay
asset-to-receive
network
amount]
:as swap} (get-in db [:wallet :ui :swap])
swap-chain-id (:chain-id network)
token-id-from (:symbol asset-to-pay)
token-id-to (:symbol asset-to-receive)
receive-token-decimals (:decimals asset-to-receive)
amount-out (:amount-out swap-proposal)
receive-amount (when amount-out
(-> amount-out
(number/hex->whole receive-token-decimals)
(money/to-fixed receive-token-decimals)))
approval-required? (transaction-approval-required? transactions swap)]
{:fx [[:dispatch
[:centralized-metrics/track
(if approval-required?
:metric/swap-approval-execution-start
:metric/swap-transaction-execution-start)
(cond-> {:network swap-chain-id
:pay_token token-id-from}
(not approval-required?)
(assoc :receive_token token-id-to))]]
[:dispatch
[:wallet.swap/add-authorized-transaction
(cond-> {:sent-transactions sent-transactions
:approval-transaction? approval-required?}
(not approval-required?)
(assoc :swap-data
{:pay-token-symbol token-id-from
:pay-amount amount
:receive-token-symbol token-id-to
:receive-amount receive-amount
:swap-chain-id swap-chain-id}))]]
(when approval-required?
;; dismiss the spending cap dialog if the transaction needs to be approved
[:dispatch [:dismiss-modal :screen/wallet.swap-set-spending-cap]])
(when approval-required?
[:dispatch [:wallet.swap/mark-as-pending (-> sent-transactions first :hash)]])
(when-not approval-required?
;; just end the whole transaction flow if no approval needed
[:dispatch [:wallet.swap/end-transaction-flow]])
(when-not approval-required?
[:dispatch-later
{:ms 500
:dispatch [:toasts/upsert
{:id :swap-transaction-pending
:icon :i/info
:type :neutral
:text (i18n/label :t/swapping-to
{:pay-amount amount
:pay-token-symbol token-id-from
:receive-token-symbol token-id-to
:receive-amount receive-amount})}]}])]})))
(rf/reg-event-fx
:wallet.swap/transaction-failure
(fn [{:keys [db]} [{:keys [details] :as error}]]
(let [transactions (get-in db [:wallet :transactions])
{:keys [asset-to-pay
asset-to-receive
network]
:as swap} (get-in db [:wallet :ui :swap])
swap-chain-id (:chain-id network)
token-id-from (:symbol asset-to-pay)
token-id-to (:symbol asset-to-receive)
approval-required? (transaction-approval-required? transactions swap)]
{:fx [[:centralized-metrics/track
(if approval-required?
:metric/swap-approval-execution-failed
:metric/swap-transaction-execution-failed)
(cond-> {:network swap-chain-id
:error error
:pay_token token-id-from}
(not approval-required?)
(assoc :receive_token token-id-to))]
[:dispatch [:wallet.swap/end-transaction-flow]]
[:dispatch
[:toasts/upsert
{:id :send-transaction-error
:type :negative
:text (or details "An error occured")}]]]})))
(rf/reg-event-fx
:wallet.swap/clean-up-transaction-flow
(fn [{:keys [db]}]
(let [transactions (get-in db [:wallet :transactions])
swap (get-in db [:wallet :ui :swap])
approval-required? (transaction-approval-required? transactions swap)]
{:db (update-in db [:wallet :ui] dissoc :swap)
:fx [[:dispatch
[:dismiss-modal
(if approval-required?
:screen/wallet.swap-set-spending-cap
:screen/wallet.swap-confirmation)]]]})))
(rf/reg-event-fx
:wallet.swap/end-transaction-flow
(fn [{:keys [db]}]
(let [address (get-in db [:wallet :current-viewing-account-address])]
{:fx [[:dispatch [:wallet/navigate-to-account-within-stack address]]
[:dispatch [:wallet/fetch-activities-for-current-account]]
[:dispatch [:wallet/select-account-tab :activity]]]})))
[:dispatch [:wallet/select-account-tab :activity]]
[:dispatch-later
[{:ms 20
:dispatch [:wallet.swap/clean-up-transaction-flow]}]]]})))
(rf/reg-event-fx :wallet.swap/start-from-account
(fn [{:keys [db]} [account]]

View File

@ -12,8 +12,7 @@
[status-im.contexts.wallet.swap.set-spending-cap.style :as style]
[utils.address :as address-utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
[utils.re-frame :as rf]))
(defn- swap-title
[]
@ -221,9 +220,8 @@
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
on-auth-success (rn/use-callback #(rf/dispatch
[:wallet/swap-transaction
(security/safe-unmask-data %)]))]
on-auth-success (rn/use-callback
#(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap %]))]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-sign)

View File

@ -95,8 +95,10 @@
:subtitle (str max-slippage "%")
:subtitle-icon :i/edit
:size :small
:on-press #(rf/dispatch [:show-bottom-sheet
{:content slippage-settings/view}])}]]))
:on-press (fn []
(when-not loading-swap-proposal?
(rf/dispatch [:show-bottom-sheet
{:content slippage-settings/view}])))}]]))
(defn- pay-token-input
[{:keys [input-state on-max-press on-input-focus on-token-press on-approve-press input-focused?]}]
@ -195,7 +197,8 @@
:approve)
:token-value approval-label-token-value
:button-props (merge {:on-press on-approve-press}
(when error-response
(when (or loading-swap-proposal?
error-response)
{:disabled? true}))
:customization-color account-color
:token-symbol pay-token-symbol}}]))
@ -361,9 +364,7 @@
(not pay-input-error?))
on-review-swap-press (rn/use-callback
(fn []
(rf/dispatch [:navigate-to-within-stack
[:screen/wallet.swap-confirmation
:screen/wallet.setup-swap]])))
(rf/dispatch [:wallet.swap/review-swap])))
on-press (rn/use-callback
(fn [c]
(let
@ -492,7 +493,7 @@
:on-max-press on-max-press
:input-focused? pay-input-focused?
:on-token-press #(rf/dispatch [:show-bottom-sheet {:content pay-token-bottom-sheet}])
:on-approve-press #(rf/dispatch [:open-modal :screen/wallet.swap-set-spending-cap])
:on-approve-press #(rf/dispatch [:wallet.swap/approve])
:on-input-focus (fn []
(when platform/android? (rf/dispatch [:dismiss-keyboard]))
(set-pay-input-focused? true))}]

View File

@ -12,8 +12,7 @@
[status-im.contexts.wallet.swap.swap-confirmation.style :as style]
[utils.address :as address-utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
[utils.re-frame :as rf]))
(defn- on-close-action
[]
@ -165,22 +164,23 @@
(defn- slide-button
[]
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
on-auth-success (rn/use-callback (fn [data]
(rf/dispatch [:wallet/stop-get-swap-proposal])
(rf/dispatch [:wallet/swap-transaction
(security/safe-unmask-data data)])))]
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
transaction-for-signing (rf/sub [:wallet/swap-transaction-for-signing])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-swap)
:container-style {:z-index 2}
:customization-color account-color
:disabled? (or loading-swap-proposal? (not swap-proposal))
:disabled? (or loading-swap-proposal?
(not swap-proposal)
(not transaction-for-signing))
:auth-button-label (i18n/label :t/confirm)
:on-auth-success on-auth-success}]))
:on-auth-success (fn [data]
(rf/dispatch [:wallet/stop-get-swap-proposal])
(rf/dispatch [:wallet/prepare-signatures-for-transactions :swap data]))}]))
(defn footer
[]

View File

@ -1,4 +0,0 @@
(ns status-im.contexts.wallet.swap.swap-proposal.style)
(def container
{:flex 1})

View File

@ -1,27 +0,0 @@
(ns status-im.contexts.wallet.swap.swap-proposal.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings]
[status-im.contexts.wallet.swap.swap-proposal.style :as style]
[utils.re-frame :as rf]))
(defn view
[]
(let [max-slippage (rf/sub [:wallet/swap-max-slippage])]
[rn/view {:style style/container}
[quo/button
{:on-press #(rf/dispatch [:show-bottom-sheet
{:content slippage-settings/view}])}
(str "Edit Slippage: " max-slippage "%")]
[quo/button
{:on-press #(rf/dispatch [:navigate-to-within-stack
[:screen/wallet.swap-confirmation :screen/wallet.swap-propasal]])}
"Swap confirmation"]
[quo/button
{:on-press #(rf/dispatch [:open-modal :screen/wallet.swap-set-spending-cap])}
"Set spending cap"]
[quo/button
{:on-press #(rf/dispatch [:navigate-to-within-stack
[:screen/wallet.setup-swap :screen/wallet.swap-propasal]])}
"Setup swap"]]))

View File

@ -1,63 +1,8 @@
(ns status-im.contexts.wallet.wallet-connect.utils.rpc
(:require [oops.core :as oops]
[promesa.core :as promesa]
(:require [promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events]
[status-im.constants :as constants]
[utils.hex :as hex]
[utils.transforms :as transforms]))
(defn wallet-build-transaction
[chain-id tx]
(promesa/let [res (rpc-events/call-async :wallet_buildTransaction true chain-id tx)]
{: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"
true
chain-id
constants/transaction-pending-type-wallet-connect-transfer
(transforms/js-stringify tx-args 0)
signature))
(defn wallet-sign-message
[message address password]
(-> (rpc-events/call-async "wallet_signMessage"
true
message
address
password)
(promesa/then hex/normalize-hex)))
(defn wallet-hash-message-eip-191
[message]
(rpc-events/call-async "wallet_hashMessageEIP191" true message))
(defn wallet-safe-sign-typed-data
[data address password chain-id legacy?]
(rpc-events/call-async "wallet_safeSignTypedDataForDApps"
true
data
address
password
chain-id
legacy?))
(defn wallet-get-suggested-fees
[chain-id]
(-> (rpc-events/call-async "wallet_getSuggestedFees" true chain-id)
(promesa/then transforms/js->clj)))
(defn wallet-disconnect-persisted-session
[topic]
(rpc-events/call-async "wallet_disconnectWalletConnectSession" true topic))

View File

@ -2,9 +2,9 @@
(:require
[native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.contexts.wallet.rpc :as wallet-rpc]
[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.transforms :as transforms]))
@ -19,6 +19,6 @@
(defn personal-sign
[password address data]
(-> (rpc/wallet-hash-message-eip-191 data)
(promesa/then #(rpc/wallet-sign-message % address password))
(-> (wallet-rpc/hash-message-eip-191 data)
(promesa/then #(wallet-rpc/sign-message % address password))
(promesa/then hex/prefix-hex)))

View File

@ -4,9 +4,10 @@
[native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.constants :as constants]
[status-im.contexts.wallet.rpc :as wallet-rpc]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as wallet-connect-rpc]
[utils.money :as money]
[utils.transforms :as transforms]))
@ -119,16 +120,17 @@
"Formats and builds the incoming transaction, adding the missing properties and returning the final
transaction, along with the transaction hash and the suggested fees"
[tx chain-id tx-priority]
(promesa/let [suggested-fees (rpc/wallet-get-suggested-fees chain-id)
(promesa/let [suggested-fees (wallet-rpc/get-suggested-fees chain-id)
{:keys [tx-args message-to-sign]} (->>
(prepare-transaction-fees tx
tx-priority
suggested-fees)
prepare-transaction-for-rpc
(rpc/wallet-build-transaction chain-id))
estimated-time (rpc/wallet-get-transaction-estimated-time
chain-id
(:maxPriorityFeePerGas suggested-fees))]
(wallet-rpc/build-transaction
chain-id))
estimated-time (wallet-connect-rpc/wallet-get-transaction-estimated-time
chain-id
(:maxPriorityFeePerGas suggested-fees))]
{:tx-args tx-args
:tx-hash message-to-sign
:suggested-fees suggested-fees
@ -137,15 +139,15 @@
(defn sign-transaction
[password address tx-hash tx-args chain-id]
(promesa/let
[signature (rpc/wallet-sign-message tx-hash address password)
raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)]
[signature (wallet-rpc/sign-message tx-hash address password)
raw-tx (wallet-rpc/build-raw-transaction chain-id tx-args signature)]
raw-tx))
(defn send-transaction
[password address tx-hash tx-args chain-id]
(promesa/let
[signature (rpc/wallet-sign-message tx-hash address password)
tx (rpc/wallet-send-transaction-with-signature chain-id
[signature (wallet-rpc/sign-message tx-hash address password)
tx (wallet-rpc/send-transaction-with-signature chain-id
tx-args
signature)]
tx))

View File

@ -1,8 +1,8 @@
(ns status-im.contexts.wallet.wallet-connect.utils.typed-data
(:require [clojure.string :as string]
[status-im.constants :as constants]
[status-im.contexts.wallet.rpc :as wallet-rpc]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.number :as number]))
(declare flatten-data)
@ -88,7 +88,7 @@
[password address data chain-id-eip155 version]
(let [legacy? (= version :v1)
chain-id (networks/eip155->chain-id chain-id-eip155)]
(rpc/wallet-safe-sign-typed-data data
(wallet-rpc/safe-sign-typed-data data
address
password
chain-id

View File

@ -141,7 +141,6 @@
[status-im.contexts.wallet.swap.set-spending-cap.view :as wallet-swap-set-spending-cap]
[status-im.contexts.wallet.swap.setup-swap.view :as wallet-swap-setup-swap]
[status-im.contexts.wallet.swap.swap-confirmation.view :as wallet-swap-confirmation]
[status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal]
[status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as
wallet-connect-send-transaction]
[status-im.contexts.wallet.wallet-connect.modals.session-proposal.view :as
@ -651,12 +650,6 @@
:insets {:bottom? true}}
:component wallet-swap-setup-swap/view}
{:name :screen/wallet.swap-propasal
:metrics {:track? true
:alias-id :wallet-swap.swap-proposal}
:options {:insets {:top? true}}
:component wallet-swap-propasal/view}
{:name :screen/wallet.swap-set-spending-cap
:metrics {:track? true
:alias-id :wallet-swap.set-spending-cap}

View File

@ -143,6 +143,11 @@
:<- [:wallet/swap]
:-> :loading-swap-proposal?)
(rf/reg-sub
:wallet/swap-transaction-for-signing
:<- [:wallet/swap]
:-> :transaction-for-signing)
(rf/reg-sub
:wallet/swap-proposal-amount-out
:<- [:wallet/swap-proposal]

View File

@ -220,6 +220,11 @@
:<- [:wallet/wallet-send]
:-> :loading-suggested-routes?)
(rf/reg-sub
:wallet/wallet-send-transaction-for-signing
:<- [:wallet/wallet-send]
:-> :transaction-for-signing)
(rf/reg-sub
:wallet/wallet-send-suggested-routes
:<- [:wallet/wallet-send]