diff --git a/src/app/common/crypto-assets/stacks-crypto-asset.utils.spec.ts b/src/app/common/crypto-assets/stacks-crypto-asset.utils.spec.ts index 1def2e0bb81..ae948f06fa1 100644 --- a/src/app/common/crypto-assets/stacks-crypto-asset.utils.spec.ts +++ b/src/app/common/crypto-assets/stacks-crypto-asset.utils.spec.ts @@ -1,4 +1,5 @@ import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model'; +import { createMoney } from '@shared/models/money.model'; import { isFtNameLikeStx, @@ -31,6 +32,7 @@ describe(isTransferableStacksFungibleTokenAsset.name, () => { canTransfer: true, hasMemo: true, imageCanonicalUri: '', + price: createMoney(0, 'USD'), symbol: 'CAT', }; expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy(); @@ -47,6 +49,7 @@ describe(isTransferableStacksFungibleTokenAsset.name, () => { canTransfer: true, hasMemo: true, imageCanonicalUri: '', + price: createMoney(0, 'USD'), symbol: 'CAT', }; expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy(); diff --git a/src/app/pages/swap/hooks/use-alex-sdk-fiat-price.tsx b/src/app/common/hooks/use-alex-sdk.ts similarity index 62% rename from src/app/pages/swap/hooks/use-alex-sdk-fiat-price.tsx rename to src/app/common/hooks/use-alex-sdk.ts index 067538e9df7..bb69ed7386f 100644 --- a/src/app/pages/swap/hooks/use-alex-sdk-fiat-price.tsx +++ b/src/app/common/hooks/use-alex-sdk.ts @@ -1,4 +1,4 @@ -import { Money, createMoney } from '@shared/models/money.model'; +import { type Money, createMoney } from '@shared/models/money.model'; import { isUndefined } from '@shared/utils'; import { useConvertAlexSdkCurrencyToFiatAmount } from '@app/common/hooks/use-convert-to-fiat-amount'; @@ -7,32 +7,32 @@ import { unitToFractionalUnit } from '@app/common/money/unit-conversion'; export function useAlexSdkAmountAsFiat(balance?: Money, price?: Money, value?: string) { const convertAlexSdkCurrencyToUsd = useConvertAlexSdkCurrencyToFiatAmount( - // @ts-expect-error TODO: balance?.symbol should be of a Cryptocurrency type. balance?.symbol ?? '', price ?? createMoney(0, 'USD') ); - if (isUndefined(balance) || isUndefined(price) || isUndefined(value)) return ''; + if (isUndefined(balance) || isUndefined(price) || isUndefined(value)) return; const convertedAmountAsMoney = convertAlexSdkCurrencyToUsd( createMoney(unitToFractionalUnit(balance.decimals)(value), balance.symbol, balance.decimals) ); - return convertedAmountAsMoney.amount.isNaN() ? '' : i18nFormatCurrency(convertedAmountAsMoney); + if (convertedAmountAsMoney.amount.isNaN()) return; + return i18nFormatCurrency(convertedAmountAsMoney); } -export function useAlexSdkBalanceAsFiat(balance?: Money, price?: Money) { +export function useAlexSdkBalanceAsFiat(balance: Money, price?: Money | null) { const convertAlexSdkCurrencyToUsd = useConvertAlexSdkCurrencyToFiatAmount( - // @ts-expect-error TODO: balance?.symbol should be of a Cryptocurrency type. - balance?.symbol ?? '', + balance.symbol, price ?? createMoney(0, 'USD') ); - if (isUndefined(balance) || isUndefined(price)) return ''; + if (isUndefined(balance) || isUndefined(price)) return; const convertedBalanceAsMoney = convertAlexSdkCurrencyToUsd( createMoney(balance.amount, balance.symbol, balance.decimals) ); - return convertedBalanceAsMoney.amount.isNaN() ? '' : i18nFormatCurrency(convertedBalanceAsMoney); + if (convertedBalanceAsMoney.amount.isNaN() || convertedBalanceAsMoney.amount.isEqualTo(0)) return; + return i18nFormatCurrency(convertedBalanceAsMoney); } diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx b/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx index 644eb3b000f..3106f3e1d68 100644 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx @@ -2,6 +2,7 @@ import { styled } from 'leather-styles/jsx'; import { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; +import { useAlexSdkBalanceAsFiat } from '@app/common/hooks/use-alex-sdk'; import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; @@ -17,6 +18,7 @@ export function StacksFungibleTokenAssetItemLayout({ assetBalance, onClick, }: StacksFungibleTokenAssetItemLayoutProps) { + const balanceAsFiat = useAlexSdkBalanceAsFiat(assetBalance.balance, assetBalance.asset.price); const { amount, avatar, caption, dataTestId, formattedBalance, imageCanonicalUri, title } = parseStacksFungibleTokenAssetBalance(assetBalance); @@ -45,6 +47,7 @@ export function StacksFungibleTokenAssetItemLayout({ } + captionRight={balanceAsFiat} /> ); diff --git a/src/app/features/asset-list/components/stacks-balance-list-item.tsx b/src/app/features/asset-list/components/stacks-balance-list-item.tsx index 8cb7bf8a39a..bab8510c5b7 100644 --- a/src/app/features/asset-list/components/stacks-balance-list-item.tsx +++ b/src/app/features/asset-list/components/stacks-balance-list-item.tsx @@ -6,12 +6,12 @@ interface StacksBalanceListItemProps { address: string; } export function StacksBalanceListItem({ address }: StacksBalanceListItemProps) { - const balaceDetails = useStxBalance(); + const balanceDetails = useStxBalance(); return ( ); } diff --git a/src/app/pages/fund/components/fund.layout.tsx b/src/app/pages/fund/components/fund.layout.tsx index 8c5013f9b17..983b4309347 100644 --- a/src/app/pages/fund/components/fund.layout.tsx +++ b/src/app/pages/fund/components/fund.layout.tsx @@ -1,27 +1,15 @@ import { Stack, styled } from 'leather-styles/jsx'; +import type { Blockchains } from '@shared/models/blockchain.model'; import { CryptoCurrencies } from '@shared/models/currencies.model'; import { HasChildren } from '@app/common/has-children'; -const nameMap: Record = { - BTC: { - name: 'Bitcoin', - symbol: 'BTC', - }, - STX: { - name: 'Stacks', - symbol: 'STX', - }, -}; - interface FundLayoutProps extends HasChildren { + blockchain: Blockchains; symbol: CryptoCurrencies; } - -export function FundLayout({ symbol, children }: FundLayoutProps) { - const name = nameMap[symbol].name; - const nameAbbr = nameMap[symbol].symbol; +export function FundLayout({ blockchain, symbol, children }: FundLayoutProps) { return ( - Choose an exchange to fund your account with {name} ({nameAbbr}) or deposit from elsewhere. - Exchanges with “Fast checkout” make it easier to purchase {nameAbbr} for direct deposit into - your wallet with a credit card. + Choose an exchange to fund your account with{' '} + {blockchain} ({symbol}) or deposit + from elsewhere. Exchanges with “Fast checkout” make it easier to purchase {symbol} for + direct deposit into your wallet with a credit card. {children} diff --git a/src/app/pages/fund/fiat-providers-list.tsx b/src/app/pages/fund/fiat-providers-list.tsx index 446abe09cf3..fde08d62d44 100644 --- a/src/app/pages/fund/fiat-providers-list.tsx +++ b/src/app/pages/fund/fiat-providers-list.tsx @@ -1,4 +1,3 @@ -import { useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { Grid } from 'leather-styles/jsx'; @@ -30,14 +29,7 @@ export function FiatProvidersList(props: FiatProvidersProps) { const analytics = useAnalytics(); const location = useLocation(); - const routeToQr = useMemo(() => { - switch (symbol) { - case 'BTC': - return RouteUrls.ReceiveBtc; - case 'STX': - return RouteUrls.ReceiveStx; - } - }, [symbol]); + const routeToQr = symbol === 'BTC' ? RouteUrls.ReceiveBtc : RouteUrls.ReceiveStx; const goToProviderExternalWebsite = (provider: string, providerUrl: string) => { void analytics.track('select_buy_option', { provider }); diff --git a/src/app/pages/fund/fund.tsx b/src/app/pages/fund/fund.tsx index 1dfdf95e892..5c36aaf4095 100644 --- a/src/app/pages/fund/fund.tsx +++ b/src/app/pages/fund/fund.tsx @@ -1,6 +1,12 @@ import { Outlet, useParams } from 'react-router-dom'; -import { isCryptoCurrency } from '@shared/models/currencies.model'; +import type { Blockchains } from '@shared/models/blockchain.model'; +import type { + BitcoinCryptoCurrencyAssetBalance, + StacksCryptoCurrencyAssetBalance, +} from '@shared/models/crypto-asset-balance.model'; +import type { CryptoCurrencies } from '@shared/models/currencies.model'; +import { RouteUrls } from '@shared/route-urls'; import { useBtcCryptoCurrencyAssetBalance } from '@app/common/hooks/balance/btc/use-btc-crypto-currency-asset-balance'; import { useStxCryptoCurrencyAssetBalance } from '@app/common/hooks/balance/stx/use-stx-crypto-currency-asset-balance'; @@ -11,44 +17,46 @@ import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/s import { FundLayout } from './components/fund.layout'; import { FiatProvidersList } from './fiat-providers-list'; +interface FundCryptoCurrencyInfo { + address?: string; + balance?: BitcoinCryptoCurrencyAssetBalance | StacksCryptoCurrencyAssetBalance; + blockchain: Blockchains; + route: string; + symbol: CryptoCurrencies; +} + export function FundPage() { const currentStxAccount = useCurrentStacksAccount(); const bitcoinSigner = useCurrentAccountNativeSegwitIndexZeroSignerNullable(); const btcCryptoCurrencyAssetBalance = useBtcCryptoCurrencyAssetBalance(); const stxCryptoCurrencyAssetBalance = useStxCryptoCurrencyAssetBalance(); - const { currency } = useParams(); - - function getSymbol() { - if (isCryptoCurrency(currency)) { - return currency; - } - return 'STX'; - } - function getAddress() { - switch (symbol) { - case 'BTC': - return bitcoinSigner?.address; - case 'STX': - return currentStxAccount?.address; - } - } - function getBalance() { - switch (symbol) { - case 'BTC': - return btcCryptoCurrencyAssetBalance; - case 'STX': - return stxCryptoCurrencyAssetBalance; - } - } - - const symbol = getSymbol(); - const address = getAddress(); - const balance = getBalance(); + const { currency = 'STX' } = useParams(); + + const fundCryptoCurrencyMap: Record = { + BTC: { + address: bitcoinSigner?.address, + balance: btcCryptoCurrencyAssetBalance?.btcBalance, + blockchain: 'Bitcoin', + route: RouteUrls.ReceiveBtc, + symbol: currency, + }, + STX: { + address: currentStxAccount?.address, + balance: stxCryptoCurrencyAssetBalance, + blockchain: 'Stacks', + route: RouteUrls.ReceiveStx, + symbol: currency, + }, + }; + + const { address, balance, blockchain, symbol } = + fundCryptoCurrencyMap[currency as CryptoCurrencies]; if (!address || !balance) return ; + return ( <> - + diff --git a/src/app/pages/swap/alex-swap-container.tsx b/src/app/pages/swap/alex-swap-container.tsx index a86d90648f9..0107298857c 100644 --- a/src/app/pages/swap/alex-swap-container.tsx +++ b/src/app/pages/swap/alex-swap-container.tsx @@ -14,6 +14,7 @@ import BigNumber from 'bignumber.js'; import { logger } from '@shared/logger'; import { RouteUrls } from '@shared/route-urls'; import { isDefined, isUndefined } from '@shared/utils'; +import { alex } from '@shared/utils/alex-sdk'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; import { useWalletType } from '@app/common/use-wallet-type'; @@ -55,7 +56,6 @@ function AlexSwapContainer() { }); const { - alexSDK, fetchToAmount, createSwapAssetFromAlexCurrency, isFetchingExchangeRate, @@ -66,7 +66,7 @@ function AlexSwapContainer() { swapSubmissionData, } = useAlexSwap(); - const broadcastAlexSwap = useAlexBroadcastSwap(alexSDK); + const broadcastAlexSwap = useAlexBroadcastSwap(); const broadcastStacksSwap = useStacksBroadcastSwap(); const swappableAssets: SwapAsset[] = useMemo( @@ -84,8 +84,8 @@ function AlexSwapContainer() { } const [router, lpFee] = await Promise.all([ - alexSDK.getRouter(values.swapAssetFrom.currency, values.swapAssetTo.currency), - alexSDK.getFeeRate(values.swapAssetFrom.currency, values.swapAssetTo.currency), + alex.getRouter(values.swapAssetFrom.currency, values.swapAssetTo.currency), + alex.getFeeRate(values.swapAssetFrom.currency, values.swapAssetTo.currency), ]); onSetSwapSubmissionData({ @@ -141,7 +141,7 @@ function AlexSwapContainer() { .toString() ); - const tx = alexSDK.runSwap( + const tx = alex.runSwap( currentAccount?.address, swapSubmissionData.swapAssetFrom.currency, swapSubmissionData.swapAssetTo.currency, diff --git a/src/app/pages/swap/components/swap-amount-field.tsx b/src/app/pages/swap/components/swap-amount-field.tsx index dbd0a5e5425..c747ba8d336 100644 --- a/src/app/pages/swap/components/swap-amount-field.tsx +++ b/src/app/pages/swap/components/swap-amount-field.tsx @@ -22,7 +22,7 @@ function getPlaceholderValue(name: string, values: SwapFormValues) { } interface SwapAmountFieldProps { - amountAsFiat: string; + amountAsFiat?: string; isDisabled?: boolean; name: string; } diff --git a/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-item.tsx b/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-item.tsx index 7fabdbcd131..8e56372da2b 100644 --- a/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-item.tsx +++ b/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-item.tsx @@ -1,15 +1,14 @@ import { SwapSelectors } from '@tests/selectors/swap.selectors'; +import { useAlexSdkBalanceAsFiat } from '@app/common/hooks/use-alex-sdk'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; +import type { SwapAsset } from '@app/pages/swap/hooks/use-swap-form'; import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query'; import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils'; import { Avatar, defaultFallbackDelay, getAvatarFallback } from '@app/ui/components/avatar/avatar'; import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; import { Pressable } from '@app/ui/pressable/pressable'; -import { useAlexSdkBalanceAsFiat } from '../../../hooks/use-alex-sdk-fiat-price'; -import { SwapAsset } from '../../../hooks/use-swap-form'; - interface SwapAssetItemProps { asset: SwapAsset; onClick(): void; diff --git a/src/app/pages/swap/components/swap-selected-asset-from.tsx b/src/app/pages/swap/components/swap-selected-asset-from.tsx index 3a493dadef4..7dac171bcff 100644 --- a/src/app/pages/swap/components/swap-selected-asset-from.tsx +++ b/src/app/pages/swap/components/swap-selected-asset-from.tsx @@ -5,10 +5,10 @@ import { createMoney } from '@shared/models/money.model'; import { isUndefined } from '@shared/utils'; import { useShowFieldError } from '@app/common/form-utils'; +import { useAlexSdkAmountAsFiat } from '@app/common/hooks/use-alex-sdk'; import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; -import { useAlexSdkAmountAsFiat } from '../hooks/use-alex-sdk-fiat-price'; import { SwapFormValues } from '../hooks/use-swap-form'; import { useSwapContext } from '../swap.context'; import { SwapAmountField } from './swap-amount-field'; diff --git a/src/app/pages/swap/components/swap-selected-asset-to.tsx b/src/app/pages/swap/components/swap-selected-asset-to.tsx index 7e2c94f7049..284f9e0b823 100644 --- a/src/app/pages/swap/components/swap-selected-asset-to.tsx +++ b/src/app/pages/swap/components/swap-selected-asset-to.tsx @@ -1,9 +1,9 @@ import { useField } from 'formik'; +import { useAlexSdkAmountAsFiat } from '@app/common/hooks/use-alex-sdk'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; import { LoadingSpinner } from '@app/components/loading-spinner'; -import { useAlexSdkAmountAsFiat } from '../hooks/use-alex-sdk-fiat-price'; import { useSwapContext } from '../swap.context'; import { SwapAmountField } from './swap-amount-field'; import { SwapSelectedAssetLayout } from './swap-selected-asset.layout'; diff --git a/src/app/pages/swap/hooks/use-alex-broadcast-swap.ts b/src/app/pages/swap/hooks/use-alex-broadcast-swap.ts index efc5eaafbb1..df4b7814b82 100644 --- a/src/app/pages/swap/hooks/use-alex-broadcast-swap.ts +++ b/src/app/pages/swap/hooks/use-alex-broadcast-swap.ts @@ -1,22 +1,23 @@ import { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { AlexSDK, SponsoredTxError } from 'alex-sdk'; +import { SponsoredTxError } from 'alex-sdk'; import { logger } from '@shared/logger'; import { RouteUrls } from '@shared/route-urls'; import { delay } from '@shared/utils'; +import { alex } from '@shared/utils/alex-sdk'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; -export function useAlexBroadcastSwap(alexSDK: AlexSDK) { +export function useAlexBroadcastSwap() { const { setIsIdle } = useLoading(LoadingKeys.SUBMIT_SWAP_TRANSACTION); const navigate = useNavigate(); return useCallback( async (txRaw: string) => { try { - const txId = await alexSDK.broadcastSponsoredTx(txRaw); + const txId = await alex.broadcastSponsoredTx(txRaw); logger.info('transaction:', txId); await delay(1000); setIsIdle(); @@ -31,6 +32,6 @@ export function useAlexBroadcastSwap(alexSDK: AlexSDK) { }); } }, - [alexSDK, navigate, setIsIdle] + [navigate, setIsIdle] ); } diff --git a/src/app/pages/swap/hooks/use-alex-swap.tsx b/src/app/pages/swap/hooks/use-alex-swap.tsx index 21f95a8bd08..71034ec39c5 100644 --- a/src/app/pages/swap/hooks/use-alex-swap.tsx +++ b/src/app/pages/swap/hooks/use-alex-swap.tsx @@ -1,16 +1,17 @@ import { useCallback, useState } from 'react'; -import { useAsync } from 'react-async-hook'; -import { AlexSDK, Currency, TokenInfo } from 'alex-sdk'; +import { Currency, TokenInfo } from 'alex-sdk'; import BigNumber from 'bignumber.js'; import { logger } from '@shared/logger'; import { createMoney } from '@shared/models/money.model'; +import { alex } from '@shared/utils/alex-sdk'; import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money'; import { pullContractIdFromIdentity } from '@app/common/utils'; -import { useSwappableCurrencyQuery } from '@app/query/common/alex-swaps/swappable-currency.query'; +import { useAlexSdkLatestPricesQuery } from '@app/query/common/alex-sdk/latest-prices.query'; +import { useAlexSdkSwappableCurrencyQuery } from '@app/query/common/alex-sdk/swappable-currency.query'; import { useTransferableStacksFungibleTokenAssetBalances } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -20,12 +21,11 @@ import { SwapAsset } from './use-swap-form'; export const oneHundredMillion = 100_000_000; export function useAlexSwap() { - const alexSDK = useState(() => new AlexSDK())[0]; const [swapSubmissionData, setSwapSubmissionData] = useState(); const [slippage, _setSlippage] = useState(0.04); const [isFetchingExchangeRate, setIsFetchingExchangeRate] = useState(false); - const { data: supportedCurrencies = [] } = useSwappableCurrencyQuery(alexSDK); - const { result: prices } = useAsync(async () => await alexSDK.getLatestPrices(), [alexSDK]); + const { data: supportedCurrencies = [] } = useAlexSdkSwappableCurrencyQuery(); + const { data: prices } = useAlexSdkLatestPricesQuery(); const { availableBalance: availableStxBalance } = useStxBalance(); const account = useCurrentStacksAccount(); const stacksFtAssetBalances = useTransferableStacksFungibleTokenAssetBalances( @@ -79,7 +79,7 @@ export function useAlexSwap() { const amountAsBigInt = isNaN(Number(amount)) ? BigInt(0) : BigInt(amount); try { setIsFetchingExchangeRate(true); - const result = await alexSDK.getAmountTo(from.currency, amountAsBigInt, to.currency); + const result = await alex.getAmountTo(from.currency, amountAsBigInt, to.currency); setIsFetchingExchangeRate(false); return new BigNumber(Number(result)).dividedBy(oneHundredMillion).toString(); } catch (e) { @@ -90,7 +90,6 @@ export function useAlexSwap() { } return { - alexSDK, fetchToAmount, createSwapAssetFromAlexCurrency, isFetchingExchangeRate, diff --git a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts new file mode 100644 index 00000000000..5add887bfbf --- /dev/null +++ b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts @@ -0,0 +1,35 @@ +import { useCallback } from 'react'; + +import { type Currency } from 'alex-sdk'; +import BigNumber from 'bignumber.js'; + +import { logger } from '@shared/logger'; +import { createMoney } from '@shared/models/money.model'; +import { isDefined } from '@shared/utils'; + +import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money'; +import { pullContractIdFromIdentity } from '@app/common/utils'; + +import { useAlexSdkLatestPricesQuery } from './latest-prices.query'; +import { useAlexSdkSwappableCurrencyQuery } from './swappable-currency.query'; + +export function useAlexSdKCurrencyPriceAsMoney() { + const { data: supportedCurrencies = [] } = useAlexSdkSwappableCurrencyQuery(); + const { data: prices } = useAlexSdkLatestPricesQuery(); + + return useCallback( + (principal: string) => { + if (!prices) { + logger.error('Latest prices could not be found'); + return null; + } + const tokenInfo = supportedCurrencies + .filter(isDefined) + .find(token => pullContractIdFromIdentity(token.contractAddress) === principal); + const currency = tokenInfo?.id as Currency; + const price = convertAmountToFractionalUnit(new BigNumber(prices[currency] ?? 0), 2); + return createMoney(price, 'USD'); + }, + [prices, supportedCurrencies] + ); +} diff --git a/src/app/query/common/alex-sdk/latest-prices.query.ts b/src/app/query/common/alex-sdk/latest-prices.query.ts new file mode 100644 index 00000000000..2cc29031120 --- /dev/null +++ b/src/app/query/common/alex-sdk/latest-prices.query.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; + +import { alex } from '@shared/utils/alex-sdk'; + +export function useAlexSdkLatestPricesQuery() { + return useQuery(['alex-sdk-latest-prices'], async () => alex.getLatestPrices(), { + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryDelay: 1000 * 60, + staleTime: 1000 * 60 * 10, + }); +} diff --git a/src/app/query/common/alex-sdk/swappable-currency.query.ts b/src/app/query/common/alex-sdk/swappable-currency.query.ts new file mode 100644 index 00000000000..792b60b603e --- /dev/null +++ b/src/app/query/common/alex-sdk/swappable-currency.query.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; + +import { alex } from '@shared/utils/alex-sdk'; + +export function useAlexSdkSwappableCurrencyQuery() { + return useQuery(['alex-sdk-swappable-currencies'], async () => alex.fetchSwappableCurrency(), { + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryDelay: 1000 * 60, + staleTime: 1000 * 60 * 10, + }); +} diff --git a/src/app/query/common/alex-swaps/swappable-currency.query.ts b/src/app/query/common/alex-swaps/swappable-currency.query.ts deleted file mode 100644 index 415bbcc09df..00000000000 --- a/src/app/query/common/alex-swaps/swappable-currency.query.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { AlexSDK } from 'alex-sdk'; - -export function useSwappableCurrencyQuery(alexSDK: AlexSDK) { - return useQuery( - ['alex-supported-swap-currencies'], - async () => alexSDK.fetchSwappableCurrency(), - { - refetchOnMount: false, - refetchOnReconnect: false, - refetchOnWindowFocus: false, - retryDelay: 1000 * 60, - staleTime: 1000 * 60 * 10, - } - ); -} diff --git a/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts b/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts index f7059f30df3..1dcd2a36d37 100644 --- a/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts +++ b/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts @@ -7,6 +7,7 @@ import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asse import { formatContractId } from '@app/common/utils'; import { useToast } from '@app/features/toasts/use-toast'; +import { useAlexSdKCurrencyPriceAsMoney } from '@app/query/common/alex-sdk/alex-sdk.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useGetFungibleTokenMetadataListQuery } from '../tokens/fungible-tokens/fungible-token-metadata.query'; @@ -35,6 +36,7 @@ function useStacksFungibleTokenAssetBalances(address: string) { export function useStacksFungibleTokenAssetBalancesWithMetadata(address: string) { const { data: initializedAssetBalances = [] } = useStacksFungibleTokenAssetBalances(address); + const priceAsMoney = useAlexSdKCurrencyPriceAsMoney(); const ftAssetsMetadata = useGetFungibleTokenMetadataListQuery( initializedAssetBalances.map(assetBalance => @@ -49,7 +51,10 @@ export function useStacksFungibleTokenAssetBalancesWithMetadata(address: string) if (!(metadata && isFtAsset(metadata))) return assetBalance; return addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( assetBalance, - metadata + metadata, + priceAsMoney( + formatContractId(assetBalance.asset.contractAddress, assetBalance.asset.contractName) + ) ); }), // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/app/query/stacks/balance/stacks-ft-balances.utils.ts b/src/app/query/stacks/balance/stacks-ft-balances.utils.ts index b93f7bd0211..159d0af3409 100644 --- a/src/app/query/stacks/balance/stacks-ft-balances.utils.ts +++ b/src/app/query/stacks/balance/stacks-ft-balances.utils.ts @@ -7,7 +7,7 @@ import type { StacksCryptoCurrencyAssetBalance, StacksFungibleTokenAssetBalance, } from '@shared/models/crypto-asset-balance.model'; -import { createMoney } from '@shared/models/money.model'; +import { type Money, createMoney } from '@shared/models/money.model'; import { isTransferableStacksFungibleTokenAsset } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts'; @@ -47,6 +47,7 @@ export function createStacksFtCryptoAssetBalanceTypeWrapper( hasMemo: false, imageCanonicalUri: '', name: '', + price: null, symbol: '', }, }; @@ -68,7 +69,8 @@ export function convertFtBalancesToStacksFungibleTokenAssetBalanceType( export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( assetBalance: StacksFungibleTokenAssetBalance, - metadata: FtMetadataResponse + metadata: FtMetadataResponse, + price: Money | null ) { return { ...assetBalance, @@ -84,6 +86,7 @@ export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( hasMemo: isTransferableStacksFungibleTokenAsset(assetBalance.asset), imageCanonicalUri: metadata.image_canonical_uri ?? '', name: metadata.name ?? '', + price, symbol: metadata.symbol ?? '', }, }; diff --git a/src/shared/models/crypto-asset.model.ts b/src/shared/models/crypto-asset.model.ts index 057282fc6c0..c91dc8d6f5b 100644 --- a/src/shared/models/crypto-asset.model.ts +++ b/src/shared/models/crypto-asset.model.ts @@ -1,3 +1,5 @@ +import type { Money } from './money.model'; + export interface BitcoinCryptoCurrencyAsset { decimals: number; hasMemo: boolean; @@ -22,6 +24,7 @@ export interface StacksFungibleTokenAsset { hasMemo: boolean; imageCanonicalUri: string; name: string; + price: Money | null; symbol: string; } diff --git a/src/shared/models/currencies.model.ts b/src/shared/models/currencies.model.ts index 115968881a5..ffbdfdf0045 100644 --- a/src/shared/models/currencies.model.ts +++ b/src/shared/models/currencies.model.ts @@ -1,9 +1,6 @@ -const CRYPTO_CURRENCIES_ARRAY = ['BTC', 'STX'] as const; +import type { LiteralUnion } from 'leather-styles/types'; -export type CryptoCurrencies = (typeof CRYPTO_CURRENCIES_ARRAY)[number]; - -export const isCryptoCurrency = (value: unknown): value is CryptoCurrencies => - CRYPTO_CURRENCIES_ARRAY.some(cryptocurrency => cryptocurrency === value); +export type CryptoCurrencies = LiteralUnion<'BTC' | 'STX', string>; export type FiatCurrencies = 'USD' | string; diff --git a/src/shared/utils/alex-sdk.ts b/src/shared/utils/alex-sdk.ts new file mode 100644 index 00000000000..6f77af74c4d --- /dev/null +++ b/src/shared/utils/alex-sdk.ts @@ -0,0 +1,3 @@ +import { AlexSDK } from 'alex-sdk'; + +export const alex = new AlexSDK();