feat: approve token transactions on swap (#21076)

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2024-09-04 13:53:45 -03:00 committed by GitHub
parent c861d95d69
commit d5238ac82f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 519 additions and 319 deletions

View File

@ -591,11 +591,17 @@
(def ^:const default-slippage 0.5) (def ^:const default-slippage 0.5)
(def ^:const max-recommended-slippage 5) (def ^:const max-recommended-slippage 5)
(def ^:const max-slippage-decimal-places 2) (def ^:const max-slippage-decimal-places 2)
(def ^:const swap-default-provider (def ^:const swap-provider-paraswap
{:name :paraswap {:name :paraswap
:full-name "Paraswap" :full-name "Paraswap"
:color :blue :color :blue
:contract-address "0xdef171fe48cf0115b1d80b88dc8eab59176fee57" :contract-address "0xdef171fe48cf0115b1d80b88dc8eab59176fee57"
:terms-and-conditions-url "https://files.paraswap.io/tos_v4.pdf"}) :terms-and-conditions-url "https://files.paraswap.io/tos_v4.pdf"})
(def ^:const swap-providers
{:paraswap swap-provider-paraswap})
(def ^:const token-for-fees-symbol "ETH") (def ^:const token-for-fees-symbol "ETH")
(def ^:const transaction-status-success "Success")
(def ^:const transaction-status-pending "Pending")
(def ^:const transaction-status-failed "Failed")

View File

@ -1,8 +1,10 @@
(ns status-im.contexts.wallet.common.utils (ns status-im.contexts.wallet.common.utils
(:require [clojure.string :as string] (:require [clojure.string :as string]
[native-module.core :as native-module]
[quo.foundations.resources :as resources] [quo.foundations.resources :as resources]
[status-im.common.qr-codes.view :as qr-codes] [status-im.common.qr-codes.view :as qr-codes]
[status-im.constants :as constants] [status-im.constants :as constants]
[utils.hex :as utils.hex]
[utils.money :as money] [utils.money :as money]
[utils.number :as number] [utils.number :as number]
[utils.string])) [utils.string]))
@ -348,3 +350,121 @@
[tokens] [tokens]
(let [priority #(get constants/token-sort-priority (:symbol %) ##Inf)] (let [priority #(get constants/token-sort-priority (:symbol %) ##Inf)]
(sort-by (juxt (comp - :balance) priority) tokens))) (sort-by (juxt (comp - :balance) priority) tokens)))
(defn- transaction-data
[{:keys [from-address to-address token-address route data eth-transfer?]}]
(let [{:keys [amount-in gas-amount gas-fees]} route
eip-1559-enabled? (:eip-1559-enabled gas-fees)
{:keys [gas-price max-fee-per-gas-medium
max-priority-fee-per-gas]} gas-fees]
(cond-> {:From from-address
:To (or token-address to-address)
:Gas (money/to-hex gas-amount)
:Value (when eth-transfer? amount-in)
:Nonce nil
:Input ""
:Data (or data "0x")}
eip-1559-enabled? (assoc
:TxType "0x02"
:MaxFeePerGas
(money/to-hex
(money/->wei
:gwei
max-fee-per-gas-medium))
:MaxPriorityFeePerGas
(money/to-hex
(money/->wei
:gwei
max-priority-fee-per-gas)))
(not eip-1559-enabled?) (assoc :TxType "0x00"
:GasPrice
(money/to-hex
(money/->wei
:gwei
gas-price))))))
(defn approval-path
[{:keys [route from-address to-address token-address]}]
(let [{:keys [from]} route
from-chain-id (:chain-id from)
approval-amount-required (:approval-amount-required route)
approval-amount-required-sanitized (-> approval-amount-required
(utils.hex/normalize-hex)
(native-module/hex-to-number))
approval-contract-address (:approval-contract-address route)
data (native-module/encode-function-call
constants/contract-function-signature-erc20-approve
[approval-contract-address
approval-amount-required-sanitized])
tx-data (transaction-data {:from-address from-address
:to-address to-address
:token-address token-address
:route route
:data data
:eth-transfer? false})]
{:BridgeName constants/bridge-name-transfer
:ChainID from-chain-id
:TransferTx tx-data}))
(defn transaction-path
[{:keys [from-address to-address token-id token-address route data eth-transfer?]}]
(let [{:keys [bridge-name amount-in bonder-fees from
to]} route
tx-data (transaction-data {:from-address from-address
:to-address to-address
:token-address token-address
:route route
:data data
:eth-transfer? eth-transfer?})
to-chain-id (:chain-id to)
from-chain-id (:chain-id from)]
(cond-> {:BridgeName bridge-name
:ChainID from-chain-id}
(= bridge-name constants/bridge-name-erc-721-transfer)
(assoc :ERC721TransferTx
(assoc tx-data
:Recipient to-address
:TokenID token-id
:ChainID to-chain-id))
(= bridge-name constants/bridge-name-erc-1155-transfer)
(assoc :ERC1155TransferTx
(assoc tx-data
:Recipient to-address
:TokenID token-id
:ChainID to-chain-id
:Amount amount-in))
(= bridge-name constants/bridge-name-transfer)
(assoc :TransferTx tx-data)
(= bridge-name constants/bridge-name-hop)
(assoc :HopTx
(assoc tx-data
:ChainID from-chain-id
:ChainIDTo to-chain-id
:Symbol token-id
:Recipient to-address
:Amount amount-in
:BonderFee bonder-fees))
(not (or (= bridge-name constants/bridge-name-erc-721-transfer)
(= bridge-name constants/bridge-name-transfer)
(= bridge-name constants/bridge-name-hop)))
(assoc :CbridgeTx
(assoc tx-data
:ChainID to-chain-id
:Symbol token-id
:Recipient to-address
:Amount amount-in)))))
(defn multi-transaction-command
[{:keys [from-address to-address from-asset to-asset amount-out multi-transaction-type]
:or {multi-transaction-type constants/multi-transaction-type-unknown}}]
{:fromAddress from-address
:toAddress to-address
:fromAsset from-asset
:toAsset to-asset
:fromAmount amount-out
:type multi-transaction-type})

View File

@ -11,7 +11,7 @@
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.address :as address] [utils.address :as address]
[utils.hex :as utils.hex] [utils.hex :as utils.hex]
[utils.money :as money] [utils.money :as utils.money]
[utils.number] [utils.number]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -582,124 +582,6 @@
[{:ms 20 [{:ms 20
:dispatch [:wallet/clean-up-transaction-flow]}]]]}))) :dispatch [:wallet/clean-up-transaction-flow]}]]]})))
(defn- transaction-data
[{:keys [from-address to-address token-address route data eth-transfer?]}]
(let [{:keys [amount-in gas-amount gas-fees]} route
eip-1559-enabled? (:eip-1559-enabled gas-fees)
{:keys [gas-price max-fee-per-gas-medium
max-priority-fee-per-gas]} gas-fees]
(cond-> {:From from-address
:To (or token-address to-address)
:Gas (money/to-hex gas-amount)
:Value (when eth-transfer? amount-in)
:Nonce nil
:Input ""
:Data (or data "0x")}
eip-1559-enabled? (assoc
:TxType "0x02"
:MaxFeePerGas
(money/to-hex
(money/->wei
:gwei
max-fee-per-gas-medium))
:MaxPriorityFeePerGas
(money/to-hex
(money/->wei
:gwei
max-priority-fee-per-gas)))
(not eip-1559-enabled?) (assoc :TxType "0x00"
:GasPrice
(money/to-hex
(money/->wei
:gwei
gas-price))))))
(defn- approval-path
[{:keys [route from-address to-address token-address]}]
(let [{:keys [from]} route
from-chain-id (:chain-id from)
approval-amount-required (:approval-amount-required route)
approval-amount-required-sanitized (-> approval-amount-required
(utils.hex/normalize-hex)
(native-module/hex-to-number))
approval-contract-address (:approval-contract-address route)
data (native-module/encode-function-call
constants/contract-function-signature-erc20-approve
[approval-contract-address
approval-amount-required-sanitized])
tx-data (transaction-data {:from-address from-address
:to-address to-address
:token-address token-address
:route route
:data data
:eth-transfer? false})]
{:BridgeName constants/bridge-name-transfer
:ChainID from-chain-id
:TransferTx tx-data}))
(defn- transaction-path
[{:keys [from-address to-address token-id token-address route data eth-transfer?]}]
(let [{:keys [bridge-name amount-in bonder-fees from
to]} route
tx-data (transaction-data {:from-address from-address
:to-address to-address
:token-address token-address
:route route
:data data
:eth-transfer? eth-transfer?})
to-chain-id (:chain-id to)
from-chain-id (:chain-id from)]
(cond-> {:BridgeName bridge-name
:ChainID from-chain-id}
(= bridge-name constants/bridge-name-erc-721-transfer)
(assoc :ERC721TransferTx
(assoc tx-data
:Recipient to-address
:TokenID token-id
:ChainID to-chain-id))
(= bridge-name constants/bridge-name-erc-1155-transfer)
(assoc :ERC1155TransferTx
(assoc tx-data
:Recipient to-address
:TokenID token-id
:ChainID to-chain-id
:Amount amount-in))
(= bridge-name constants/bridge-name-transfer)
(assoc :TransferTx tx-data)
(= bridge-name constants/bridge-name-hop)
(assoc :HopTx
(assoc tx-data
:ChainID from-chain-id
:ChainIDTo to-chain-id
:Symbol token-id
:Recipient to-address
:Amount amount-in
:BonderFee bonder-fees))
(not (or (= bridge-name constants/bridge-name-erc-721-transfer)
(= bridge-name constants/bridge-name-transfer)
(= bridge-name constants/bridge-name-hop)))
(assoc :CbridgeTx
(assoc tx-data
:ChainID to-chain-id
:Symbol token-id
:Recipient to-address
:Amount amount-in)))))
(defn- multi-transaction-command
[{:keys [from-address to-address from-asset to-asset amount-out multi-transaction-type]
:or {multi-transaction-type constants/multi-transaction-type-unknown}}]
{:fromAddress from-address
:toAddress to-address
:fromAsset from-asset
:toAsset to-asset
:fromAmount amount-out
:type multi-transaction-type})
(rf/reg-event-fx :wallet/send-transaction (rf/reg-event-fx :wallet/send-transaction
(fn [{:keys [db]} [sha3-pwd]] (fn [{:keys [db]} [sha3-pwd]]
(let [routes (get-in db [:wallet :ui :send :route]) (let [routes (get-in db [:wallet :ui :send :route])
@ -731,7 +613,7 @@
(native-module/encode-transfer (native-module/encode-transfer
(address/normalized-hex to-address) (address/normalized-hex to-address)
(:amount-in route))) (:amount-in route)))
base-path (transaction-path base-path (utils/transaction-path
{:to-address to-address {:to-address to-address
:from-address from-address :from-address from-address
:route route :route route
@ -743,15 +625,15 @@
:data data :data data
:eth-transfer? eth-transfer?})] :eth-transfer? eth-transfer?})]
(if approval-required? (if approval-required?
[(approval-path {:route route [(utils/approval-path {:route route
:token-address token-address :token-address token-address
:from-address from-address :from-address from-address
:to-address to-address}) :to-address to-address})
base-path] base-path]
[base-path])))) [base-path]))))
routes) routes)
request-params request-params
[(multi-transaction-command [(utils/multi-transaction-command
{:from-address from-address {:from-address from-address
:to-address to-address :to-address to-address
:from-asset token-id :from-asset token-id

View File

@ -1,6 +1,7 @@
(ns status-im.contexts.wallet.signals (ns status-im.contexts.wallet.signals
(:require (:require
[oops.core :as oops] [oops.core :as oops]
[status-im.constants :as constants]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
@ -8,9 +9,21 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet/pending-transaction-status-changed-received :wallet/pending-transaction-status-changed-received
(fn [{:keys [db]} [{:keys [message]}]] (fn [{:keys [db]} [{:keys [message]}]]
(let [details (transforms/json->clj message) (let [details (transforms/json->clj message)
tx-hash (:hash details)] tx-hash (:hash details)
{:db (update-in db [:wallet :transactions tx-hash] assoc :status :confirmed :blocks 1)}))) tx-status (:status details)
status (cond
(= tx-status constants/transaction-status-success)
:confirmed
(= tx-status constants/transaction-status-pending)
:pending
(= tx-status constants/transaction-status-failed)
:failed)
swap-approval-transaction-id (get-in db [:wallet :ui :swap :approval-transaction-id])
swap-approval-transaction? (= swap-approval-transaction-id tx-hash)]
(cond-> {:db (update-in db [:wallet :transactions tx-hash] assoc :status status)}
swap-approval-transaction?
(assoc :fx [[:dispatch [:wallet.swap/approve-transaction-update status]]])))))
(rf/reg-event-fx (rf/reg-event-fx
:wallet/signal-received :wallet/signal-received

View File

@ -1,9 +1,13 @@
(ns status-im.contexts.wallet.swap.events (ns status-im.contexts.wallet.swap.events
(:require [re-frame.core :as rf] (:require [native-module.core :as native-module]
[re-frame.core :as rf]
[status-im.constants :as constants] [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.send.utils :as send-utils]
[status-im.contexts.wallet.sheets.network-selection.view :as network-selection] [status-im.contexts.wallet.sheets.network-selection.view :as network-selection]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.address :as address]
[utils.i18n :as i18n]
[utils.number])) [utils.number]))
(rf/reg-event-fx :wallet.swap/start (rf/reg-event-fx :wallet.swap/start
@ -152,8 +156,123 @@
:last-request-uuid :last-request-uuid
:swap-proposal :swap-proposal
:error-response :error-response
:loading-swap-proposal?)})) :loading-swap-proposal?
:approval-transaction-id)}))
(rf/reg-event-fx :wallet/clean-swap (rf/reg-event-fx :wallet/clean-swap
(fn [{:keys [db]}] (fn [{:keys [db]}]
{:db (update-in db [:wallet :ui] dissoc :swap)})) {: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 swap-proposal network
approval-transaction-id]} (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 (:symbol asset-to-pay)
erc20-transfer? (and asset-to-pay (not= token-id "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 token-id
:data data
:eth-transfer? eth-transfer?})])
request-params [(utils/multi-transaction-command
{:from-address wallet-address
:to-address wallet-address
:from-asset token-id
:to-asset token-id
: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
(rf/dispatch [:wallet.swap/add-authorized-transaction
{:transaction result
:approval-transaction? approval-required?}])
(rf/dispatch [:dismiss-modal
:screen/wallet.swap-set-spending-cap])
(rf/dispatch [:hide-bottom-sheet])))
:on-error (fn [error]
(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 approval-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)]
{:db (cond-> db
:always (assoc-in [:wallet :transactions] transaction-details)
:always (assoc-in [:wallet :ui :swap :transaction-ids] transaction-ids)
approval-transaction? (assoc-in [:wallet :ui :swap :approval-transaction-id]
(first transaction-ids)))})))
(rf/reg-event-fx :wallet.swap/approve-transaction-update
(fn [{:keys [db]} [status]]
(let [{:keys [amount asset-to-pay swap-proposal]} (get-in db [:wallet :ui :swap])
provider-name (:bridge-name swap-proposal)
token-symbol (:symbol asset-to-pay)
current-viewing-account-address (get-in db
[:wallet :current-viewing-account-address])
account-name (get-in db
[:wallet :accounts
current-viewing-account-address :name])
transaction-confirmed-or-failed? (#{:confirmed :failed} status)
transaction-confirmed? (= status :confirmed)]
(when transaction-confirmed-or-failed?
(cond-> {:fx
[[:dispatch
[:toasts/upsert
{:id :approve-transaction-update
:type (if transaction-confirmed? :positive :negative)
:text (if transaction-confirmed?
(i18n/label :t/spending-cap-set
{:amount amount
:token-symbol token-symbol
:provider-name provider-name
:account-name account-name})
(i18n/label :t/spending-cap-failed
{:amount amount
:token-symbol token-symbol
:provider-name provider-name
:account-name account-name}))}]]]}
(not transaction-confirmed?)
(assoc :db (update-in db [:wallet :ui :swap] dissoc :approval-transaction-id)))))))

View File

@ -1,5 +1,6 @@
(ns status-im.contexts.wallet.swap.set-spending-cap.view (ns status-im.contexts.wallet.swap.set-spending-cap.view
(:require (:require
[native-module.core :as native-module]
[quo.core :as quo] [quo.core :as quo]
[quo.foundations.resources :as resources] [quo.foundations.resources :as resources]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
@ -7,84 +8,123 @@
[status-im.common.events-helper :as events-helper] [status-im.common.events-helper :as events-helper]
[status-im.common.floating-button-page.view :as floating-button-page] [status-im.common.floating-button-page.view :as floating-button-page]
[status-im.common.standard-authentication.core :as standard-auth] [status-im.common.standard-authentication.core :as standard-auth]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils.external-links :as external-links] [status-im.contexts.wallet.common.utils.external-links :as external-links]
[status-im.contexts.wallet.swap.set-spending-cap.style :as style] [status-im.contexts.wallet.swap.set-spending-cap.style :as style]
[utils.address :as address-utils] [utils.address :as address-utils]
[utils.hex :as hex]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.number :as number]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn- swap-title (defn- swap-title
[{:keys [pay-token-symbol pay-amount account provider]}] []
[rn/view {:style style/content-container} (let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
[rn/view {:style {:flex-direction :row}} amount-in (rf/sub [:wallet/swap-proposal-amount-in])
[quo/text account (rf/sub [:wallet/current-viewing-account])
{:size :heading-1 provider (rf/sub [:wallet/swap-proposal-provider])
:weight :semi-bold pay-token-symbol (:symbol asset-to-pay)
:style style/title-container pay-token-decimals (:decimals asset-to-pay)
:accessibility-label :set-spending-cap-of} pay-amount (when amount-in
(i18n/label :t/set-spending-cap-of)]] (number/convert-to-whole-number
[rn/view {:style style/title-line-with-margin-top} (native-module/hex-to-number
[quo/summary-tag (hex/normalize-hex
{:token pay-token-symbol amount-in))
:label (str pay-amount " " pay-token-symbol) pay-token-decimals))]
:type :token}] [rn/view {:style style/content-container}
[quo/text [rn/view {:style {:flex-direction :row}}
{:size :heading-1 [quo/text
:weight :semi-bold {:size :heading-1
:style style/title-container :weight :semi-bold
:accessibility-label :for} :style style/title-container
(i18n/label :t/for)]] :accessibility-label :set-spending-cap-of}
[rn/view {:style style/title-line-with-margin-top} (i18n/label :t/set-spending-cap-of)]]
[quo/summary-tag [rn/view {:style style/title-line-with-margin-top}
{:label (:full-name provider) [quo/summary-tag
:type :network {:token pay-token-symbol
:image-source (resources/get-network (:name provider)) :label (str pay-amount " " pay-token-symbol)
:customization-color (:color provider)}] :type :token}]
[quo/text [quo/text
{:size :heading-1 {:size :heading-1
:weight :semi-bold :weight :semi-bold
:style style/title-container :style style/title-container
:accessibility-label :on} :accessibility-label :for}
(i18n/label :t/on)]] (i18n/label :t/for)]]
[rn/view {:style style/title-line-with-margin-top} [rn/view {:style style/title-line-with-margin-top}
[quo/summary-tag [quo/summary-tag
{:label (:name account) {:label (:full-name provider)
:type :account :type :network
:emoji (:emoji account) :image-source (resources/get-network (:name provider))
:customization-color (:color account)}]]]) :customization-color (:color provider)}]
[quo/text
{:size :heading-1
:weight :semi-bold
:style style/title-container
:accessibility-label :on}
(i18n/label :t/on)]]
[rn/view {:style style/title-line-with-margin-top}
[quo/summary-tag
{:label (:name account)
:type :account
:emoji (:emoji account)
:customization-color (:color account)}]]]))
(defn- spending-cap-section (defn- spending-cap-section
[{:keys [theme amount token-symbol]}] []
[rn/view {:style style/summary-section-container} (let [theme (quo.theme/use-theme)
[quo/text asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
{:size :paragraph-2 amount-in (rf/sub [:wallet/swap-proposal-amount-in])
:weight :medium pay-token-symbol (:symbol asset-to-pay)
:style (style/section-label theme) pay-token-decimals (:decimals asset-to-pay)
:accessibility-label :spending-cap-label} pay-amount (when amount-in
(i18n/label :t/spending-cap)] (number/convert-to-whole-number
[quo/approval-info (native-module/hex-to-number
{:type :spending-cap (hex/normalize-hex
:unlimited-icon? false amount-in))
:label (str amount " " token-symbol) pay-token-decimals))]
:avatar-props {:token token-symbol}}]]) [rn/view {:style style/summary-section-container}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label :spending-cap-label}
(i18n/label :t/spending-cap)]
[quo/approval-info
{:type :spending-cap
:unlimited-icon? false
:label (str pay-amount " " pay-token-symbol)
:avatar-props {:token pay-token-symbol}}]]))
(defn- account-section (defn- account-section
[{:keys [theme account pay-token-symbol pay-token-amount]}] []
[rn/view {:style style/summary-section-container} (let [theme (quo.theme/use-theme)
[quo/text asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
{:size :paragraph-2 amount-in (rf/sub [:wallet/swap-proposal-amount-in])
:weight :medium account (rf/sub [:wallet/current-viewing-account])
:style (style/section-label theme) pay-token-symbol (:symbol asset-to-pay)
:accessibility-label :account-label} pay-token-decimals (:decimals asset-to-pay)
(i18n/label :t/account)] pay-amount (when amount-in
[quo/approval-info (number/convert-to-whole-number
{:type :account (native-module/hex-to-number
:unlimited-icon? false (hex/normalize-hex
:label (:name account) amount-in))
:description (address-utils/get-short-wallet-address (:address account)) pay-token-decimals))]
:tag-label (str pay-token-amount " " pay-token-symbol) [rn/view {:style style/summary-section-container}
:avatar-props {:emoji (:emoji account) [quo/text
:customization-color (:color account)}}]]) {:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label :account-label}
(i18n/label :t/account)]
[quo/approval-info
{:type :account
:unlimited-icon? false
:label (:name account)
:description (address-utils/get-short-wallet-address (:address account))
:tag-label (str pay-amount " " pay-token-symbol)
:avatar-props {:emoji (:emoji account)
:customization-color (:color account)}}]]))
(defn- on-option-press (defn- on-option-press
[{:keys [chain-id contract-address]}] [{:keys [chain-id contract-address]}]
@ -103,42 +143,52 @@
:right-icon :i/external}]]])}])) :right-icon :i/external}]]])}]))
(defn- token-section (defn- token-section
[{:keys [theme token-address token-symbol network-chain-id]}] []
[rn/view {:style style/summary-section-container} (let [theme (quo.theme/use-theme)
[quo/text asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
{:size :paragraph-2 network (rf/sub [:wallet/swap-network])
:weight :medium pay-token-symbol (:symbol asset-to-pay)
:style (style/section-label theme) network-chain-id (:chain-id network)
:accessibility-label :token-label} pay-token-address (get-in asset-to-pay [:balances-per-chain network-chain-id :address])]
(i18n/label :t/token)] [rn/view {:style style/summary-section-container}
[quo/approval-info [quo/text
{:type :token-contract {:size :paragraph-2
:option-icon :i/options :weight :medium
:on-option-press #(on-option-press {:chain-id network-chain-id :style (style/section-label theme)
:contract-address token-address}) :accessibility-label :token-label}
:unlimited-icon? false (i18n/label :t/token)]
:label token-symbol [quo/approval-info
:description (address-utils/get-short-wallet-address token-address) {:type :token-contract
:avatar-props {:token token-symbol}}]]) :option-icon :i/options
:on-option-press #(on-option-press {:chain-id network-chain-id
:contract-address pay-token-address})
:unlimited-icon? false
:label pay-token-symbol
:description (address-utils/get-short-wallet-address pay-token-address)
:avatar-props {:token pay-token-symbol}}]]))
(defn- spender-contract-section (defn- spender-contract-section
[{:keys [theme provider network-chain-id]}] []
[rn/view {:style style/summary-section-container} (let [theme (quo.theme/use-theme)
[quo/text network (rf/sub [:wallet/swap-network])
{:size :paragraph-2 provider (rf/sub [:wallet/swap-proposal-provider])
:weight :medium network-chain-id (:chain-id network)]
:style (style/section-label theme) [rn/view {:style style/summary-section-container}
:accessibility-label :spender-contract-label} [quo/text
(i18n/label :t/spender-contract)] {:size :paragraph-2
[quo/approval-info :weight :medium
{:type :token-contract :style (style/section-label theme)
:option-icon :i/options :accessibility-label :spender-contract-label}
:on-option-press #(on-option-press {:chain-id network-chain-id (i18n/label :t/spender-contract)]
:contract-address (:contract-address provider)}) [quo/approval-info
:unlimited-icon? false {:type :token-contract
:label (:full-name provider) :option-icon :i/options
:description (address-utils/get-short-wallet-address (:contract-address provider)) :on-option-press #(on-option-press {:chain-id network-chain-id
:avatar-props {:image (resources/get-network (:name provider))}}]]) :contract-address (:contract-address provider)})
:unlimited-icon? false
:label (:full-name provider)
:description (address-utils/get-short-wallet-address (:contract-address provider))
:avatar-props {:image (resources/get-network (:name provider))}}]]))
(defn- data-item (defn- data-item
[{:keys [network-image title subtitle size loading?]}] [{:keys [network-image title subtitle size loading?]}]
@ -154,56 +204,52 @@
:size size}]) :size size}])
(defn- transaction-details (defn- transaction-details
[{:keys [estimated-time-min max-fees network loading-fees?]}] []
[rn/view {:style style/details-container} (let [network (rf/sub [:wallet/swap-network])
[:<> max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted
[data-item constants/token-for-fees-symbol])
{:title (i18n/label :t/network) loading-fees? (rf/sub [:wallet/swap-loading-fees?])
:subtitle (:full-name network) estimated-time (rf/sub [:wallet/swap-proposal-estimated-time])]
:network-image (:source network)}] [rn/view {:style style/details-container}
[data-item [:<>
{:title (i18n/label :t/est-time) [data-item
:subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time-min)})}] {:title (i18n/label :t/network)
[data-item :subtitle (:full-name network)
{:title (i18n/label :t/max-fees) :network-image (:source network)}]
:subtitle max-fees [data-item
:loading? loading-fees? {:title (i18n/label :t/est-time)
:size :small}]]]) :subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})}]
[data-item
{:title (i18n/label :t/max-fees)
:subtitle max-fees
:loading? loading-fees?
:size :small}]]]))
(defn footer (defn- slide-button
[{:keys [estimated-time-min native-currency-symbol network theme account-color loading-fees?]}] []
(let [fee-formatted (rf/sub [:wallet/wallet-send-fee-fiat-formatted native-currency-symbol]) (let [loading-fees? (rf/sub [:wallet/swap-loading-fees?])
on-auth-success (rn/use-callback #(js/alert "Not implemented yet"))] account (rf/sub [:wallet/current-viewing-account])
[rn/view {:style {:margin-bottom -10}} on-auth-success (rn/use-callback #(rf/dispatch
[transaction-details [:wallet/swap-transaction
{:estimated-time-min estimated-time-min (security/safe-unmask-data %)]))]
:max-fees fee-formatted [standard-auth/slide-button
:network network {:size :size-48
:loading-fees? loading-fees? :track-text (i18n/label :t/slide-to-swap)
:theme theme}] :container-style {:z-index 2}
[standard-auth/slide-button :customization-color (:color account)
{:size :size-48 :disabled? loading-fees?
:track-text (i18n/label :t/slide-to-swap) :on-auth-success on-auth-success
:container-style {:z-index 2} :auth-button-label (i18n/label :t/confirm)}]))
:customization-color account-color
:disabled? loading-fees? (defn- footer
:on-auth-success on-auth-success []
:auth-button-label (i18n/label :t/confirm)}]])) [rn/view {:style {:margin-bottom -10}}
[transaction-details]
[slide-button]])
(defn view (defn view
[] []
(let [theme (quo.theme/use-theme) (let [account (rf/sub [:wallet/current-viewing-account])]
swap-transaction-data (rf/sub [:wallet/swap])
{:keys [asset-to-pay network pay-amount
providers swap-proposal
loading-fees?]} swap-transaction-data
estimated-time-min (:estimated-time swap-proposal)
pay-token-symbol (:symbol asset-to-pay)
pay-token-address (:address asset-to-pay)
native-currency-symbol (get-in swap-proposal [:from :native-currency-symbol])
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
provider (first providers)]
[rn/view {:style style/container} [rn/view {:style style/container}
[floating-button-page/view [floating-button-page/view
{:footer-container-padding 0 {:footer-container-padding 0
@ -213,37 +259,12 @@
:margin-top 8 :margin-top 8
:background :blur :background :blur
:accessibility-label :top-bar}] :accessibility-label :top-bar}]
:footer [footer :footer [footer]
{:estimated-time-min estimated-time-min
:native-currency-symbol native-currency-symbol
:network network
:account-color account-color
:provider provider
:loading-fees? loading-fees?
:theme theme}]
:gradient-cover? true :gradient-cover? true
:customization-color account-color} :customization-color (:color account)}
[:<> [:<>
[swap-title [swap-title]
{:pay-token-symbol pay-token-symbol [spending-cap-section]
:pay-amount pay-amount [account-section]
:account account [token-section]
:provider provider}] [spender-contract-section]]]]))
[spending-cap-section
{:token-symbol pay-token-symbol
:amount pay-amount
:theme theme}]
[account-section
{:account account
:pay-token-symbol pay-token-symbol
:pay-token-amount pay-amount
:theme theme}]
[token-section
{:token-symbol pay-token-symbol
:token-address pay-token-address
:network-chain-id (:chain-id network)
:theme theme}]
[spender-contract-section
{:provider provider
:network-chain-id (:chain-id network)
:theme theme}]]]]))

View File

@ -76,6 +76,7 @@
approval-required (rf/sub [:wallet/swap-proposal-approval-required]) approval-required (rf/sub [:wallet/swap-proposal-approval-required])
approval-amount-required (rf/sub [:wallet/swap-proposal-approval-amount-required]) approval-amount-required (rf/sub [:wallet/swap-proposal-approval-amount-required])
currency-symbol (rf/sub [:profile/currency-symbol]) currency-symbol (rf/sub [:profile/currency-symbol])
approval-transaction-status (rf/sub [:wallet/swap-approval-transaction-status])
pay-input-num-value (controlled-input/numeric-value input-state) pay-input-num-value (controlled-input/numeric-value input-state)
pay-input-amount (controlled-input/input-value input-state) pay-input-amount (controlled-input/input-value input-state)
pay-token-symbol (:symbol asset-to-pay) pay-token-symbol (:symbol asset-to-pay)
@ -141,7 +142,11 @@
{:number available-crypto-limit {:number available-crypto-limit
:token-symbol pay-token-symbol}) :token-symbol pay-token-symbol})
:networks [{:source (:source network)}]} :networks [{:source (:source network)}]}
:approval-label-props {:status :approve :approval-label-props {:status (case approval-transaction-status
:pending :approving
:confirmed :approved
:finalised :approved
:approve)
:token-value approval-amount-required-num :token-value approval-amount-required-num
:button-props {:on-press on-approve-press} :button-props {:on-press on-approve-press}
:customization-color account-color :customization-color account-color
@ -199,16 +204,18 @@
(defn- action-button (defn- action-button
[{:keys [on-press]}] [{:keys [on-press]}]
(let [account-color (rf/sub [:wallet/current-viewing-account-color]) (let [account-color (rf/sub [:wallet/current-viewing-account-color])
swap-proposal (rf/sub [:wallet/swap-proposal]) swap-proposal (rf/sub [:wallet/swap-proposal])
loading-fees? (rf/sub [:wallet/swap-loading-fees?]) loading-fees? (rf/sub [:wallet/swap-loading-fees?])
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
approval-required? (rf/sub [:wallet/swap-proposal-approval-required])] approval-required? (rf/sub [:wallet/swap-proposal-approval-required])
approval-transaction-status (rf/sub [:wallet/swap-approval-transaction-status])]
[quo/bottom-actions [quo/bottom-actions
{:actions :one-action {:actions :one-action
:button-one-label (i18n/label :t/review-swap) :button-one-label (i18n/label :t/review-swap)
:button-one-props {:disabled? (or (not swap-proposal) :button-one-props {:disabled? (or (not swap-proposal)
approval-required? (and approval-required?
(not= approval-transaction-status :confirmed))
loading-swap-proposal? loading-swap-proposal?
loading-fees?) loading-fees?)
:customization-color account-color :customization-color account-color
@ -262,7 +269,7 @@
:on-max-press #(set-pay-input-state pay-token-balance-selected-chain) :on-max-press #(set-pay-input-state pay-token-balance-selected-chain)
:input-focused? pay-input-focused? :input-focused? pay-input-focused?
:on-token-press #(js/alert "Token Pressed") :on-token-press #(js/alert "Token Pressed")
:on-approve-press #(js/alert "Approve Pressed") :on-approve-press #(rf/dispatch [:open-modal :screen/wallet.swap-set-spending-cap])
:on-input-focus #(set-pay-input-focused? true)}] :on-input-focus #(set-pay-input-focused? true)}]
[swap-order-button {:on-press #(js/alert "Swap Order Pressed")}] [swap-order-button {:on-press #(js/alert "Swap Order Pressed")}]
[receive-token-input [receive-token-input

View File

@ -80,6 +80,18 @@
:<- [:wallet/swap] :<- [:wallet/swap]
:-> :loading-fees?) :-> :loading-fees?)
(rf/reg-sub
:wallet/swap-approval-transaction-id
:<- [:wallet/swap]
:-> :approval-transaction-id)
(rf/reg-sub
:wallet/swap-approval-transaction-status
:<- [:wallet/transactions]
:<- [:wallet/swap-approval-transaction-id]
(fn [[transactions approval-transaction-id]]
(get-in transactions [approval-transaction-id :status])))
(rf/reg-sub (rf/reg-sub
:wallet/swap-proposal :wallet/swap-proposal
:<- [:wallet/swap] :<- [:wallet/swap]
@ -95,6 +107,19 @@
:<- [:wallet/swap-proposal] :<- [:wallet/swap-proposal]
:-> :amount-out) :-> :amount-out)
(rf/reg-sub
:wallet/swap-proposal-amount-in
:<- [:wallet/swap-proposal]
:-> :amount-in)
(rf/reg-sub
:wallet/swap-proposal-provider
:<- [:wallet/swap-proposal]
(fn [swap-proposal]
(let [bridge-name (:bridge-name swap-proposal)
provider-key (keyword (string/lower-case bridge-name))]
(get constants/swap-providers provider-key))))
(rf/reg-sub (rf/reg-sub
:wallet/swap-proposal-approval-required :wallet/swap-proposal-approval-required
:<- [:wallet/swap-proposal] :<- [:wallet/swap-proposal]
@ -105,6 +130,11 @@
:<- [:wallet/swap-proposal] :<- [:wallet/swap-proposal]
:-> :approval-amount-required) :-> :approval-amount-required)
(rf/reg-sub
:wallet/swap-proposal-estimated-time
:<- [:wallet/swap-proposal]
:-> :estimated-time)
(rf/reg-sub (rf/reg-sub
:wallet/wallet-swap-proposal-fee-fiat-formatted :wallet/wallet-swap-proposal-fee-fiat-formatted
:<- [:wallet/current-viewing-account] :<- [:wallet/current-viewing-account]

View File

@ -2313,6 +2313,8 @@
"specify-symbol": "Specify a symbol", "specify-symbol": "Specify a symbol",
"spender-contract": "Spender contract", "spender-contract": "Spender contract",
"spending-cap": "Spending cap", "spending-cap": "Spending cap",
"spending-cap-failed": "Spending cap failed: {{amount}} {{token-symbol}} for {{provider-name}} in {{account-name}}",
"spending-cap-set": "Spending cap set: {{amount}} {{token-symbol}} for {{provider-name}} in {{account-name}}",
"start-chat": "Start chat", "start-chat": "Start chat",
"start-conversation": "Start conversation", "start-conversation": "Start conversation",
"start-group-chat": "Start group chat", "start-group-chat": "Start group chat",