[#21557] feat: show from account page in swap flow (#21611)

* [#21557] feat: show from account page in swap flow

* [#21557] feat: add disabled state to account-item

* [#21557] fix: apply suggestion

* [#21557] fix: add format address util and rename screen

* [#21557] fix: check and count account with token balance

* [#21557] fix: check for root screen
This commit is contained in:
Mohsen 2024-11-22 16:12:00 +03:00 committed by GitHub
parent 26ae330476
commit 21c2a525cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 242 additions and 41 deletions

View File

@ -23,7 +23,7 @@
[:map
[:type {:optional true}
[:enum :default :tag :action :balance-neutral :balance-negative :balance-positive]]
[:state {:optional true} [:enum :default :selected :active]]
[:state {:optional true} [:enum :default :selected :active :disabled]]
[:blur? {:optional true} [:maybe :boolean]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:on-press {:optional true} [:maybe fn?]]

View File

@ -12,12 +12,13 @@
:else :transparent))
(defn container
[props]
[{:keys [state] :as props}]
{:height 56
:border-radius 12
:background-color (background-color props)
:flex-direction :row
:align-items :center
:opacity (if (= state :disabled) 0.3 1)
:padding-horizontal 12
:padding-vertical 6
:justify-content :space-between})

View File

@ -112,6 +112,7 @@
:pressed? pressed?})
:on-press-in on-press-in
:on-press on-press
:disabled (= state :disabled)
:on-press-out on-press-out
:accessibility-label :container}
[account-view props]

View File

@ -193,7 +193,7 @@
(defn token-balance-display-for-network
"Formats a token balance for a specific chain and rounds it to a specified number of decimals.
If the balance is less than the smallest representable value based on rounding decimals,
If the balance is less than the smallest representable value based on rounding decimals,
a threshold value is displayed instead."
[token chain-id rounding-decimals]
(let [token-decimals (:decimals token)
@ -548,3 +548,27 @@
(:less-than-three-minutes constants/wallet-transaction-estimation) "1-3"
(:less-than-five-minutes constants/wallet-transaction-estimation) "3-5"
">5"))
(defn get-accounts-with-token-balance
[accounts token]
(let [operable-account (filter :operable? (vals accounts))
positive-balance-in-any-chain? (fn [{:keys [balances-per-chain]}]
(->> balances-per-chain
(map (comp :raw-balance val))
(some pos?)))
addresses-tokens-with-positive-balance (as-> operable-account $
(group-by :address $)
(update-vals $
#(filter positive-balance-in-any-chain?
(:tokens (first %)))))]
(if-let [asset-symbol (:symbol token)]
(let [addresses-with-asset (as-> addresses-tokens-with-positive-balance $
(update-vals $ #(set (map :symbol %)))
(keep (fn [[address token-symbols]]
(when (token-symbols asset-symbol) address))
$)
(set $))]
(filter #(addresses-with-asset (:address %)) operable-account))
(filter (fn [{:keys [tokens]}]
(some positive-balance-in-any-chain? tokens))
operable-account))))

View File

@ -7,6 +7,7 @@
[utils.number]))
(def ^:private last-comma-followed-by-text-to-end-regex #",\s(?=[^,]+$)")
(def ^:private max-network-prefixes 2)
(def id->network
{constants/ethereum-mainnet-chain-id constants/mainnet-network-name
@ -185,3 +186,11 @@
:related-chain-id related-chain-id
:layer layer)))
(sort-by (juxt :layer :short-name))))
(defn format-address
[address network-preferences]
(let [short-names (map network->short-name network-preferences)
prefix (when (<= (count short-names) max-network-prefixes)
(short-names->network-preference-prefix short-names))
transformed-address (str prefix address)]
transformed-address))

View File

@ -269,3 +269,68 @@
expected "0"]
(is (= (utils/token-balance-display-for-network token chain-id rounding-decimals)
expected)))))
(def mock-accounts
{:0xfc6327a092f6232e158a0dd1d6d967a2e1c65cd5
{:address "0xfc6327a092f6232e158a0dd1d6d967a2e1c65cd5"
:operable? true
:tokens [{:symbol "ETH"
:balances-per-chain {1 {:raw-balance 1000000
:balance 1.0}}}
{:symbol "USDC"
:balances-per-chain {1 {:raw-balance 0
:balance 0}}}]}
:0xbce36f66a8cd99f1d6489cb9585146e3f3b893be
{:address "0xbce36f66a8cd99f1d6489cb9585146e3f3b893be"
:operable? true
:tokens [{:symbol "ETH"
:balances-per-chain {1 {:raw-balance 0
:balance 0}}}]}})
(deftest get-accounts-with-token-balance-test
(testing "Positive token balance for a specific token"
(let [accounts mock-accounts
token {:symbol "ETH"}
expected [{:address "0xfc6327a092f6232e158a0dd1d6d967a2e1c65cd5"
:operable? true
:tokens [{:symbol "ETH"
:balances-per-chain {1 {:raw-balance 1000000
:balance 1.0}}}
{:symbol "USDC"
:balances-per-chain {1 {:raw-balance 0
:balance 0}}}]}]]
(is (= (utils/get-accounts-with-token-balance accounts token)
expected))))
(testing "No token symbol provided, return all tokens with positive balance"
(let [accounts mock-accounts
token {}
expected [{:address "0xfc6327a092f6232e158a0dd1d6d967a2e1c65cd5"
:operable? true
:tokens [{:symbol "ETH"
:balances-per-chain {1 {:raw-balance 1000000
:balance 1.0}}}
{:symbol "USDC"
:balances-per-chain {1 {:raw-balance 0
:balance 0}}}]}]]
(is (= (utils/get-accounts-with-token-balance accounts token)
expected))))
(testing "No matching token found for a specific token"
(let [accounts mock-accounts
token {:symbol "DAI"}
expected []]
(is (= (utils/get-accounts-with-token-balance accounts token)
expected))))
(testing "No operable accounts"
(let [accounts {:0xfc6327a092f6232e158a0dd1d6d967a2e1c65cd5
{:address "0xfc6327a092f6232e158a0dd1d6d967a2e1c65cd5"
:operable? false
:tokens [{:symbol "ETH"
:balances-per-chain {1 {:raw-balance 1000000
:balance 1.0}}}]}}
token {}
expected []]
(is (= (utils/get-accounts-with-token-balance accounts token)
expected)))))

View File

@ -14,11 +14,12 @@
[utils.number :as number]))
(rf/reg-event-fx :wallet.swap/start
(fn [{:keys [db]} [{:keys [network asset-to-receive open-new-screen?] :as data}]]
(fn [{:keys [db]} [{:keys [network asset-to-receive open-new-screen? from-account] :as data}]]
(let [{:keys [wallet]} db
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
view-id (:view-id db)
account (swap-utils/wallet-account wallet)
root-screen? (or (= view-id :wallet-stack) (nil? view-id))
account (or from-account (swap-utils/wallet-account wallet))
asset-to-pay (if (get-in data [:asset-to-pay :networks])
(:asset-to-pay data)
(swap-utils/select-asset-to-pay-by-symbol
@ -26,6 +27,10 @@
:account account
:test-networks-enabled? test-networks-enabled?
:token-symbol (get-in data [:asset-to-pay :symbol])}))
multi-account-balance? (-> (utils/get-accounts-with-token-balance (:accounts wallet)
asset-to-pay)
(count)
(> 1))
network' (or network
(swap-utils/select-network asset-to-pay))
start-point (if open-new-screen? :action-menu :swap-button)]
@ -35,34 +40,38 @@
(assoc-in [:wallet :ui :swap :network] network')
(assoc-in [:wallet :ui :swap :launch-screen] view-id)
(assoc-in [:wallet :ui :swap :start-point] start-point))
:fx (if network'
[[:dispatch [:wallet/switch-current-viewing-account (:address account)]]
[:dispatch
(if open-new-screen?
[:open-modal :screen/wallet.setup-swap]
[:navigate-to-within-stack
[:screen/wallet.setup-swap :screen/wallet.swap-select-asset-to-pay]])]
[:dispatch
[:centralized-metrics/track :metric/swap-start
{:network (:chain-id network)
:pay_token (:symbol asset-to-pay)
:receive_token (:symbol asset-to-receive)
:start_point start-point
:launch_screen view-id}]]
[:dispatch [:wallet.swap/set-default-slippage]]]
[[:dispatch
[:show-bottom-sheet
{:content (fn []
[network-selection/view
{:token-symbol (:symbol asset-to-pay)
:on-select-network (fn [network]
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch
[:wallet.swap/start
{:asset-to-pay asset-to-pay
:asset-to-receive asset-to-receive
:network network
:open-new-screen? open-new-screen?}]))}])}]]])})))
:fx (if (and multi-account-balance? root-screen? (not from-account))
[[:dispatch [:open-modal :screen/wallet.swap-select-account]]]
(if network'
[[:dispatch [:wallet/switch-current-viewing-account (:address account)]]
[:dispatch
(if open-new-screen?
[:open-modal :screen/wallet.setup-swap]
[:navigate-to-within-stack
[:screen/wallet.setup-swap :screen/wallet.swap-select-asset-to-pay]])]
[:dispatch
[:centralized-metrics/track :metric/swap-start
{:network (:chain-id network)
:pay_token (:symbol asset-to-pay)
:receive_token (:symbol asset-to-receive)
:start_point start-point
:launch_screen view-id}]]
[:dispatch [:wallet.swap/set-default-slippage]]]
[[:dispatch
[:show-bottom-sheet
{:content (fn []
[network-selection/view
{:token-symbol (:symbol asset-to-pay)
:on-select-network (fn [network]
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch
[:wallet.swap/start
{:asset-to-pay asset-to-pay
:asset-to-receive asset-to-receive
:network network
:open-new-screen?
open-new-screen?
:from-account from-account}]))}])}]]]))})))
(rf/reg-event-fx :wallet.swap/select-asset-to-pay
(fn [{:keys [db]} [{:keys [token]}]]
@ -527,3 +536,15 @@
[:dispatch [:wallet/navigate-to-account-within-stack address]])
[:dispatch [:wallet/fetch-activities-for-current-account]]
[:dispatch [:wallet/select-account-tab :activity]]]})))
(rf/reg-event-fx :wallet.swap/start-from-account
(fn [{:keys [db]} [account]]
(let [asset-to-pay (get-in db [:wallet :ui :swap :asset-to-pay])
asset-to-receive (get-in db [:wallet :ui :swap :asset-to-receive])]
{:fx [[:dispatch [:dismiss-modal :screen/wallet.swap-select-account]]
[:dispatch
[:wallet.swap/start
{:asset-to-pay asset-to-pay
:asset-to-receive asset-to-receive
:open-new-screen? true
:from-account account}]]]})))

View File

@ -0,0 +1,7 @@
(ns status-im.contexts.wallet.swap.select-account.style)
(def accounts-list
{:padding-bottom 12})
(def accounts-list-container
{:padding-horizontal 8})

View File

@ -0,0 +1,52 @@
(ns status-im.contexts.wallet.swap.select-account.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[status-im.common.floating-button-page.view :as floating-button-page]
[status-im.contexts.wallet.swap.select-account.style :as style]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf]))
(defn- on-account-press
[account]
(rf/dispatch [:wallet.swap/start-from-account account]))
(defn- render-fn
[item _ _]
(let [has-balance (money/above-zero? (:asset-pay-balance item))]
[quo/account-item
{:type (if has-balance :tag :default)
:on-press #(on-account-press item)
:state (if has-balance :default :disabled)
:token-props {:symbol (:asset-pay-symbol item)
:value (:asset-pay-balance item)}
:account-props (assoc item
:address (:formatted-address item)
:full-address? true)}]))
(defn- on-close
[]
(rf/dispatch [:wallet/clean-current-viewing-account])
(events-helper/navigate-back))
(defn view
[]
(let [accounts (rf/sub [:wallet/accounts-with-balances])]
[floating-button-page/view
{:footer-container-padding 0
:header [quo/page-nav
{:margin-top (safe-area/get-top)
:icon-name :i/close
:on-press on-close}]}
[quo/page-top
{:title (i18n/label :t/from-label)
:title-accessibility-label :title-label}]
[rn/flat-list
{:style style/accounts-list
:content-container-style style/accounts-list-container
:data accounts
:render-fn render-fn
:shows-horizontal-scroll-indicator false}]]))

View File

@ -134,6 +134,7 @@
[status-im.contexts.wallet.send.send-amount.view :as wallet-send-input-amount]
[status-im.contexts.wallet.send.transaction-confirmation.view :as wallet-transaction-confirmation]
[status-im.contexts.wallet.send.transaction-progress.view :as wallet-transaction-progress]
[status-im.contexts.wallet.swap.select-account.view :as wallet-swap-select-account]
[status-im.contexts.wallet.swap.select-asset-to-pay.view :as wallet-swap-select-asset-to-pay]
[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]
@ -635,6 +636,12 @@
:insets {:top? true}}
:component wallet-swap-select-asset-to-pay/view}
{:name :screen/wallet.swap-select-account
:metrics {:track? true}
:options {:modalPresentationStyle :overCurrentContext
:insets {:bottom? true}}
:component wallet-swap-select-account/view}
{:name :screen/wallet.setup-swap
:metrics {:track? true
:alias-id :wallet-swap.input-amount-to-swap}

View File

@ -4,8 +4,6 @@
[utils.money :as money]
[utils.number :as number]))
(def max-network-prefixes 2)
(re-frame/reg-sub
:wallet/networks
:<- [:wallet]
@ -53,12 +51,7 @@
(re-frame/reg-sub
:wallet/account-address
(fn [_ [_ address network-preferences]]
(let [short-names (map network-utils/network->short-name network-preferences)
prefix (when (<= (count short-names) max-network-prefixes)
(network-utils/short-names->network-preference-prefix
short-names))
transformed-address (str prefix address)]
transformed-address)))
(network-utils/format-address address network-preferences)))
(re-frame/reg-sub
:wallet/network-values

View File

@ -3,6 +3,7 @@
[re-frame.core :as rf]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[status-im.contexts.wallet.send.utils :as send-utils]
[utils.money :as money]
[utils.number :as number]))
@ -315,3 +316,23 @@
:token token-for-fees})]
fee-in-fiat)))
(rf/reg-sub
:wallet/accounts-with-balances
:<- [:wallet/operable-accounts]
:<- [:wallet/swap-asset-to-pay]
(fn [[accounts asset-to-pay]]
(let [token-symbol (:symbol asset-to-pay)]
(map
(fn [account]
(let [tokens (:tokens account)
filtered-tokens (filter #(= (:symbol %) token-symbol) tokens)
asset-pay-balance (utils/calculate-total-token-balance filtered-tokens)
formatted-address (network-utils/format-address (:address account)
(:network-preferences-names account))]
(assoc account
:formatted-address formatted-address
:asset-pay-balance (utils/sanitized-token-amount-to-display
asset-pay-balance
constants/min-token-decimals-to-display)
:asset-pay-symbol token-symbol)))
accounts))))