From 0942131c63c7869349e6a63dcb101e4518271f4d Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Mon, 8 Apr 2024 13:49:54 -0500 Subject: [PATCH 01/11] feat: brc-20 token balances as fiat, #4408 --- .../hooks/balance/btc/use-btc-balance.ts | 4 +- .../hooks/balance/stx/use-stx-balance.ts | 4 +- .../hooks/balance/use-total-balance.tsx | 6 +-- src/app/common/hooks/use-alex-sdk.ts | 38 ------------------- src/app/common/hooks/use-bitcoin-contracts.ts | 4 +- .../hooks/use-convert-to-fiat-amount.ts | 21 ++-------- src/app/common/money/calculate-money.ts | 3 +- src/app/common/money/fiat-conversion.ts | 18 +++++++++ .../hooks/use-bitcoin-custom-fee.tsx | 4 +- .../use-bitcoin-fees-list.ts | 4 +- .../brc20-token-asset-item.layout.tsx | 20 ++++++++-- .../fungible-token-asset.utils.ts | 19 ++++++++++ ...tacks-fungible-token-asset-item.layout.tsx | 14 +++++-- .../hooks/use-stacks-transaction-summary.ts | 4 +- .../bitcoin-contract-list-item-layout.tsx | 4 +- .../rpc-send-transfer-confirmation.tsx | 4 +- .../pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx | 4 +- .../hooks/use-send-inscription-fees-list.ts | 4 +- .../form/btc/btc-send-form-confirmation.tsx | 4 +- .../form/btc/btc-send-form.tsx | 4 +- .../form/stx/stx-send-form.tsx | 4 +- .../components/swap-asset-item.tsx | 14 +++++-- .../swap-asset-select-base.tsx | 20 +++++----- .../swap-asset-select-quote.tsx | 23 +++++++---- src/app/pages/swap/swap.utils.ts | 19 +++++++++- src/app/query/bitcoin/bitcoin-client.ts | 1 + .../ordinals/brc20/brc20-tokens.query.ts | 10 ++--- .../common/market-data/market-data.hooks.ts | 4 +- 28 files changed, 161 insertions(+), 121 deletions(-) delete mode 100644 src/app/common/hooks/use-alex-sdk.ts create mode 100644 src/app/common/money/fiat-conversion.ts diff --git a/src/app/common/hooks/balance/btc/use-btc-balance.ts b/src/app/common/hooks/balance/btc/use-btc-balance.ts index 688d39d5852..3012db712a8 100644 --- a/src/app/common/hooks/balance/btc/use-btc-balance.ts +++ b/src/app/common/hooks/balance/btc/use-btc-balance.ts @@ -4,10 +4,10 @@ import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; import { i18nFormatCurrency } from '@app/common/money/format-money'; import { createBitcoinCryptoCurrencyAssetTypeWrapper } from '@app/query/bitcoin/address/address.utils'; import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; export function useBtcAssetBalance(btcAddress: string) { - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const { btcBalance: btcAssetBalance, isLoading, diff --git a/src/app/common/hooks/balance/stx/use-stx-balance.ts b/src/app/common/hooks/balance/stx/use-stx-balance.ts index 5a7312902f7..76789b68c61 100644 --- a/src/app/common/hooks/balance/stx/use-stx-balance.ts +++ b/src/app/common/hooks/balance/stx/use-stx-balance.ts @@ -5,7 +5,7 @@ import { isDefined } from '@shared/utils'; import { baseCurrencyAmountInQuote, subtractMoney } from '@app/common/money/calculate-money'; import { i18nFormatCurrency } from '@app/common/money/format-money'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useCurrentAccountMempoolTransactionsBalance } from '@app/query/stacks/mempool/mempool.hooks'; @@ -15,7 +15,7 @@ export function useStxBalance() { const totalBalance = stxBalanceQuery.data?.stx.balance; const unlockedStxBalance = stxBalanceQuery.data?.stx.unlockedStx; - const stxMarketData = useCryptoCurrencyMarketData('STX'); + const stxMarketData = useCryptoCurrencyMarketDataMeanAverage('STX'); const pendingTxsBalance = useCurrentAccountMempoolTransactionsBalance(); const stxEffectiveBalance = isDefined(totalBalance) diff --git a/src/app/common/hooks/balance/use-total-balance.tsx b/src/app/common/hooks/balance/use-total-balance.tsx index ac6c01904cc..91b54228fc3 100644 --- a/src/app/common/hooks/balance/use-total-balance.tsx +++ b/src/app/common/hooks/balance/use-total-balance.tsx @@ -4,7 +4,7 @@ import { createMoney } from '@shared/models/money.model'; import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; import { i18nFormatCurrency } from '@app/common/money/format-money'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { useStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useBtcAssetBalance } from './btc/use-btc-balance'; @@ -16,8 +16,8 @@ interface UseTotalBalanceArgs { export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) { // get market data - const btcMarketData = useCryptoCurrencyMarketData('BTC'); - const stxMarketData = useCryptoCurrencyMarketData('STX'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); + const stxMarketData = useCryptoCurrencyMarketDataMeanAverage('STX'); // get stx balance const { data: balances, isLoading, isInitialLoading } = useStacksAccountBalances(stxAddress); diff --git a/src/app/common/hooks/use-alex-sdk.ts b/src/app/common/hooks/use-alex-sdk.ts deleted file mode 100644 index bb69ed7386f..00000000000 --- a/src/app/common/hooks/use-alex-sdk.ts +++ /dev/null @@ -1,38 +0,0 @@ -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'; -import { i18nFormatCurrency } from '@app/common/money/format-money'; -import { unitToFractionalUnit } from '@app/common/money/unit-conversion'; - -export function useAlexSdkAmountAsFiat(balance?: Money, price?: Money, value?: string) { - const convertAlexSdkCurrencyToUsd = useConvertAlexSdkCurrencyToFiatAmount( - balance?.symbol ?? '', - price ?? createMoney(0, 'USD') - ); - - if (isUndefined(balance) || isUndefined(price) || isUndefined(value)) return; - - const convertedAmountAsMoney = convertAlexSdkCurrencyToUsd( - createMoney(unitToFractionalUnit(balance.decimals)(value), balance.symbol, balance.decimals) - ); - - if (convertedAmountAsMoney.amount.isNaN()) return; - return i18nFormatCurrency(convertedAmountAsMoney); -} - -export function useAlexSdkBalanceAsFiat(balance: Money, price?: Money | null) { - const convertAlexSdkCurrencyToUsd = useConvertAlexSdkCurrencyToFiatAmount( - balance.symbol, - price ?? createMoney(0, 'USD') - ); - - if (isUndefined(balance) || isUndefined(price)) return; - - const convertedBalanceAsMoney = convertAlexSdkCurrencyToUsd( - createMoney(balance.amount, balance.symbol, balance.decimals) - ); - - if (convertedBalanceAsMoney.amount.isNaN() || convertedBalanceAsMoney.amount.isEqualTo(0)) return; - return i18nFormatCurrency(convertedBalanceAsMoney); -} diff --git a/src/app/common/hooks/use-bitcoin-contracts.ts b/src/app/common/hooks/use-bitcoin-contracts.ts index 8bec3e13ca6..5a75f6bd570 100644 --- a/src/app/common/hooks/use-bitcoin-contracts.ts +++ b/src/app/common/hooks/use-bitcoin-contracts.ts @@ -16,7 +16,7 @@ import { makeRpcErrorResponse, makeRpcSuccessResponse } from '@shared/rpc/rpc-me import { sendAcceptedBitcoinContractOfferToProtocolWallet } from '@app/query/bitcoin/contract/send-accepted-bitcoin-contract-offer'; import { useCalculateBitcoinFiatValue, - useCryptoCurrencyMarketData, + useCryptoCurrencyMarketDataMeanAverage, } from '@app/query/common/market-data/market-data.hooks'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { @@ -57,7 +57,7 @@ export interface BitcoinContractOfferDetails { export function useBitcoinContracts() { const navigate = useNavigate(); const defaultParams = useDefaultRequestParams(); - const bitcoinMarketData = useCryptoCurrencyMarketData('BTC'); + const bitcoinMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const calculateFiatValue = useCalculateBitcoinFiatValue(); const bitcoinAccountDetails = useCurrentAccountNativeSegwitIndexZeroSigner(); diff --git a/src/app/common/hooks/use-convert-to-fiat-amount.ts b/src/app/common/hooks/use-convert-to-fiat-amount.ts index f38c9dae623..5d3cc82e6f3 100644 --- a/src/app/common/hooks/use-convert-to-fiat-amount.ts +++ b/src/app/common/hooks/use-convert-to-fiat-amount.ts @@ -1,30 +1,17 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import { CryptoCurrencies } from '@shared/models/currencies.model'; -import { createMarketData, createMarketPair } from '@shared/models/market.model'; -import type { Money } from '@shared/models/money.model'; +import { type Money } from '@shared/models/money.model'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { baseCurrencyAmountInQuote } from '../money/calculate-money'; export function useConvertCryptoCurrencyToFiatAmount(currency: CryptoCurrencies) { - const cryptoCurrencyMarketData = useCryptoCurrencyMarketData(currency); + const cryptoCurrencyMarketData = useCryptoCurrencyMarketDataMeanAverage(currency); return useCallback( (value: Money) => baseCurrencyAmountInQuote(value, cryptoCurrencyMarketData), [cryptoCurrencyMarketData] ); } - -export function useConvertAlexSdkCurrencyToFiatAmount(currency: CryptoCurrencies, price: Money) { - const alexCurrencyMarketData = useMemo( - () => createMarketData(createMarketPair(currency, 'USD'), price), - [currency, price] - ); - - return useCallback( - (value: Money) => baseCurrencyAmountInQuote(value, alexCurrencyMarketData), - [alexCurrencyMarketData] - ); -} diff --git a/src/app/common/money/calculate-money.ts b/src/app/common/money/calculate-money.ts index 8063e2d0ce1..bc332c82d70 100644 --- a/src/app/common/money/calculate-money.ts +++ b/src/app/common/money/calculate-money.ts @@ -9,7 +9,7 @@ import { formatMoney } from './format-money'; import { isMoney } from './is-money'; export function baseCurrencyAmountInQuote(quantity: Money, { pair, price }: MarketData) { - if (quantity.symbol !== pair.base) + if (quantity.symbol.toLowerCase() !== pair.base.toLowerCase()) throw new Error( `Cannot calculate value of ${formatMoney(quantity)} with market pair of ${formatMarketPair( pair @@ -39,7 +39,6 @@ export function convertToMoneyTypeWithDefaultOfZero( return createMoney(initBigNumber(num ?? 0), symbol.toUpperCase(), decimals); } -// ts-unused-exports:disable-next-line export function convertAmountToBaseUnit(num: Money | BigNumber, decimals?: number) { if (isMoney(num)) return num.amount.shiftedBy(-num.decimals); if (!isNumber(decimals)) throw new Error('Must define decimal of given currency'); diff --git a/src/app/common/money/fiat-conversion.ts b/src/app/common/money/fiat-conversion.ts new file mode 100644 index 00000000000..0159e7aeffe --- /dev/null +++ b/src/app/common/money/fiat-conversion.ts @@ -0,0 +1,18 @@ +import type { CryptoCurrencies } from '@shared/models/currencies.model'; +import { createMarketData, createMarketPair } from '@shared/models/market.model'; +import { type Money } from '@shared/models/money.model'; + +import { baseCurrencyAmountInQuote } from './calculate-money'; + +export function checkIsMoneyAmountGreaterThanZero(money: Money) { + return !(money.amount.isNaN() || money.amount.isZero()); +} + +export function convertCryptoCurrencyMoneyToFiat( + currency: CryptoCurrencies, + price: Money, + money: Money +) { + const cryptoCurrencyMarketData = createMarketData(createMarketPair(currency, 'USD'), price); + return baseCurrencyAmountInQuote(money, cryptoCurrencyMarketData); +} diff --git a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx index 7982b0dccd9..2d4c757c11e 100644 --- a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx +++ b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx @@ -13,7 +13,7 @@ import { } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; export const MAX_FEE_RATE_MULTIPLIER = 50; @@ -25,7 +25,7 @@ interface UseBitcoinCustomFeeArgs { export function useBitcoinCustomFee({ amount, isSendingMax, recipient }: UseBitcoinCustomFeeArgs) { const { balance } = useCurrentNativeSegwitAddressBalance(); const { data: utxos = [] } = useCurrentNativeSegwitUtxos(); - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); return useCallback( (feeRate: number) => { diff --git a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts index 14fe826a0c8..b7e90374b4b 100644 --- a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts +++ b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts @@ -13,7 +13,7 @@ import { import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client'; import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { FeesListItem } from './bitcoin-fees-list'; @@ -44,7 +44,7 @@ export function useBitcoinFeesList({ utxos, }: UseBitcoinFeesListArgs) { const { balance } = useCurrentNativeSegwitAddressBalance(); - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const { data: feeRates, isLoading } = useAverageBitcoinFeeRates(); const feesList: FeesListItem[] = useMemo(() => { diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx index 84924791aaa..0ce58463fb0 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx @@ -3,6 +3,11 @@ import { styled } from 'leather-styles/jsx'; import { createMoney } from '@shared/models/money.model'; import { formatBalance } from '@app/common/format-balance'; +import { + checkIsMoneyAmountGreaterThanZero, + convertCryptoCurrencyMoneyToFiat, +} from '@app/common/money/fiat-conversion'; +import { i18nFormatCurrency } from '@app/common/money/format-money'; import { Brc20Token } from '@app/query/bitcoin/bitcoin-client'; import { Brc20AvatarIcon } from '@app/ui/components/avatar/brc20-avatar-icon'; import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; @@ -14,8 +19,16 @@ interface Brc20TokenAssetItemLayoutProps { onClick?(): void; } export function Brc20TokenAssetItemLayout({ onClick, token }: Brc20TokenAssetItemLayoutProps) { - const balance = createMoney(Number(token.overall_balance), token.ticker, 0).amount.toString(); - const formattedBalance = formatBalance(balance); + const balanceAsMoney = createMoney(Number(token.overall_balance), token.ticker, token.decimals); + const balanceAsString = balanceAsMoney.amount.toString(); + const formattedBalance = formatBalance(balanceAsString); + const priceAsMoney = createMoney(token.min_listed_unit_price, 'USD'); + const showFiatBalance = checkIsMoneyAmountGreaterThanZero(priceAsMoney); + const balanceAsFiat = showFiatBalance + ? i18nFormatCurrency( + convertCryptoCurrencyMoneyToFiat(token.ticker, priceAsMoney, balanceAsMoney) + ) + : ''; return ( @@ -26,7 +39,7 @@ export function Brc20TokenAssetItemLayout({ onClick, token }: Brc20TokenAssetIte titleRight={ @@ -34,6 +47,7 @@ export function Brc20TokenAssetItemLayout({ onClick, token }: Brc20TokenAssetIte } + captionRight={balanceAsFiat} /> ); diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts index 06336273364..ada3faf3059 100644 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts +++ b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts @@ -4,6 +4,11 @@ import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asse import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; import { formatBalance } from '@app/common/format-balance'; +import { + checkIsMoneyAmountGreaterThanZero, + convertCryptoCurrencyMoneyToFiat, +} from '@app/common/money/fiat-conversion'; +import { i18nFormatCurrency } from '@app/common/money/format-money'; import { ftDecimals } from '@app/common/stacks-utils'; import { formatContractId, getTicker } from '@app/common/utils'; import { spamFilter } from '@app/common/utils/spam-filter'; @@ -29,9 +34,23 @@ export function parseStacksFungibleTokenAssetBalance( const caption = symbol || getTicker(friendlyName); const title = spamFilter(friendlyName); + const showFiatBalance = + assetBalance.asset.price && checkIsMoneyAmountGreaterThanZero(assetBalance.asset.price); + const balanceAsFiat = showFiatBalance + ? assetBalance.asset.price && + i18nFormatCurrency( + convertCryptoCurrencyMoneyToFiat( + assetBalance.balance.symbol, + assetBalance.asset.price, + assetBalance.balance + ) + ) + : ''; + return { amount, avatar, + balanceAsFiat, caption, dataTestId, formattedBalance, 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 3106f3e1d68..cf66b4cced8 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,7 +2,6 @@ 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'; @@ -18,9 +17,16 @@ export function StacksFungibleTokenAssetItemLayout({ assetBalance, onClick, }: StacksFungibleTokenAssetItemLayoutProps) { - const balanceAsFiat = useAlexSdkBalanceAsFiat(assetBalance.balance, assetBalance.asset.price); - const { amount, avatar, caption, dataTestId, formattedBalance, imageCanonicalUri, title } = - parseStacksFungibleTokenAssetBalance(assetBalance); + const { + amount, + avatar, + balanceAsFiat, + caption, + dataTestId, + formattedBalance, + imageCanonicalUri, + title, + } = parseStacksFungibleTokenAssetBalance(assetBalance); return ( diff --git a/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts b/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts index 309f3b9acca..16fbd3c7af1 100644 --- a/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts +++ b/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts @@ -21,13 +21,13 @@ import { import { formatMoney, i18nFormatCurrency } from '@app/common/money/format-money'; import { getEstimatedConfirmationTime } from '@app/common/transactions/stacks/transaction.utils'; import { removeTrailingNullCharacters } from '@app/common/utils'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { useStacksBlockTime } from '@app/query/stacks/info/info.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { microStxToStx } from '@app/ui/utils/micro-stx-to-stx'; export function useStacksTransactionSummary(token: CryptoCurrencies) { - const tokenMarketData = useCryptoCurrencyMarketData(token); + const tokenMarketData = useCryptoCurrencyMarketDataMeanAverage(token); const { isTestnet } = useCurrentNetworkState(); const { data: blockTime } = useStacksBlockTime(); diff --git a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx b/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx index 3d5d9161ed9..b7549ec79cd 100644 --- a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx +++ b/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx @@ -8,7 +8,7 @@ import { useBitcoinExplorerLink } from '@app/common/hooks/use-bitcoin-explorer-l import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; import { i18nFormatCurrency } from '@app/common/money/format-money'; import { satToBtc } from '@app/common/money/unit-conversion'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { Flag } from '@app/ui/components/flag/flag'; import { Caption } from '@app/ui/components/typography/caption'; import { BitcoinContractIcon } from '@app/ui/icons/bitcoin-contract-icon'; @@ -26,7 +26,7 @@ export function BitcoinContractListItemLayout({ txid, }: BitcoinContractListItemLayoutProps) { const { handleOpenBitcoinTxLink: handleOpenTxLink } = useBitcoinExplorerLink(); - const bitcoinMarketData = useCryptoCurrencyMarketData('BTC'); + const bitcoinMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const getFiatValue = useCallback( (value: string) => diff --git a/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx b/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx index ea506cdf7fe..c67f39b2c2e 100644 --- a/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx +++ b/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx @@ -22,7 +22,7 @@ import { import { InfoCardFooter } from '@app/components/info-card/info-card'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { Button } from '@app/ui/components/button/button'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; @@ -51,7 +51,7 @@ export function RpcSendTransferConfirmation() { const bitcoinAddress = useCurrentAccountNativeSegwitAddressIndexZero(); const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction(); const { refetch } = useCurrentNativeSegwitUtxos(); - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const psbt = decodeBitcoinTx(tx); const transferAmount = sumMoney(recipients.map(r => r.amount)); diff --git a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx index 0ed40f404d2..79963033b10 100644 --- a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx +++ b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx @@ -19,7 +19,7 @@ import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction'; import { useCalculateBitcoinFiatValue, - useCryptoCurrencyMarketData, + useCryptoCurrencyMarketDataMeanAverage, } from '@app/query/common/market-data/market-data.hooks'; import { useGetAssumedZeroIndexSigningConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; @@ -36,7 +36,7 @@ export function useRpcSignPsbt() { const { signPsbt, getPsbtAsTransaction } = usePsbtSigner(); const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction(); const { refetch } = useCurrentNativeSegwitUtxos(); - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const calculateBitcoinFiatValue = useCalculateBitcoinFiatValue(); const getDefaultSigningConfig = useGetAssumedZeroIndexSigningConfig(); diff --git a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-fees-list.ts b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-fees-list.ts index 52435aa4d3f..4f077a65ddc 100644 --- a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-fees-list.ts +++ b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-fees-list.ts @@ -10,7 +10,7 @@ import { FeesListItem } from '@app/components/bitcoin-fees-list/bitcoin-fees-lis import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { UtxoWithDerivationPath } from '@app/query/bitcoin/bitcoin-client'; import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useGenerateUnsignedOrdinalTx } from './use-generate-ordinal-tx'; @@ -29,7 +29,7 @@ export function useSendInscriptionFeesList({ const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner(); const { data: nativeSegwitUtxos } = useCurrentNativeSegwitUtxos(); - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const { data: feeRates, isLoading } = useAverageBitcoinFeeRates(); const { coverFeeFromAdditionalUtxos } = useGenerateUnsignedOrdinalTx(utxo); diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx index d061ecf82b8..7d0f68256fe 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx @@ -25,7 +25,7 @@ import { } from '@app/components/info-card/info-card'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { Button } from '@app/ui/components/button/button'; import { Footer } from '@app/ui/components/containers/footers/footer'; import { Card } from '@app/ui/layout/card/card'; @@ -53,7 +53,7 @@ export function BtcSendFormConfirmation() { const { refetch } = useCurrentNativeSegwitUtxos(); const analytics = useAnalytics(); - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction(); const transaction = btc.Transaction.fromRaw(hexToBytes(tx)); diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx index e7b99a86efc..13731614514 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx @@ -10,7 +10,7 @@ import { CryptoCurrencies } from '@shared/models/currencies.model'; import { formatMoney } from '@app/common/money/format-money'; import { HighFeeDialog } from '@app/features/dialogs/high-fee-dialog/high-fee-dialog'; import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; import { Button } from '@app/ui/components/button/button'; @@ -34,7 +34,7 @@ const symbol: CryptoCurrencies = 'BTC'; export function BtcSendForm() { const routeState = useSendFormRouteState(); - const btcMarketData = useCryptoCurrencyMarketData(symbol); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage(symbol); const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); const { btcBalance } = useNativeSegwitBalance(nativeSegwitSigner.address); diff --git a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx index 0e95742f203..f663c70878a 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx @@ -1,6 +1,6 @@ import { CryptoCurrencies } from '@shared/models/currencies.model'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; import { AmountField } from '../../components/amount-field'; @@ -13,7 +13,7 @@ import { useStxSendForm } from './use-stx-send-form'; const symbol: CryptoCurrencies = 'STX'; export function StxSendForm() { - const stxMarketData = useCryptoCurrencyMarketData(symbol); + const stxMarketData = useCryptoCurrencyMarketDataMeanAverage(symbol); const { availableStxBalance, diff --git a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx index 951e4b7c7ab..2b191fd32bf 100644 --- a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx +++ b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx @@ -1,7 +1,10 @@ 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 { + checkIsMoneyAmountGreaterThanZero, + convertCryptoCurrencyMoneyToFiat, +} from '@app/common/money/fiat-conversion'; +import { formatMoneyWithoutSymbol, i18nFormatCurrency } 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'; @@ -14,12 +17,17 @@ interface SwapAssetItemProps { onClick(): void; } export function SwapAssetItem({ asset, onClick }: SwapAssetItemProps) { - const balanceAsFiat = useAlexSdkBalanceAsFiat(asset.balance, asset.price); const { data: ftMetadata } = useGetFungibleTokenMetadataQuery(asset.principal); const ftMetadataName = ftMetadata && isFtAsset(ftMetadata) ? ftMetadata.name : asset.name; const displayName = asset.displayName ?? ftMetadataName; const fallback = getAvatarFallback(asset.name); + const showFiatBalance = checkIsMoneyAmountGreaterThanZero(asset.price); + const balanceAsFiat = showFiatBalance + ? i18nFormatCurrency( + convertCryptoCurrencyMoneyToFiat(asset.balance.symbol, asset.price, asset.balance) + ) + : ''; return ( diff --git a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx index 154674fae2a..94221abaa7b 100644 --- a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx +++ b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx @@ -3,16 +3,16 @@ import { useField, useFormikContext } from 'formik'; import { createMoney } from '@shared/models/money.model'; import { RouteUrls } from '@shared/route-urls'; -import { isUndefined } from '@shared/utils'; +import { isDefined, 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 { formatMoneyWithoutSymbol, i18nFormatCurrency } from '@app/common/money/format-money'; import { SwapFormValues } from '../../hooks/use-swap-form'; import { useSwapNavigate } from '../../hooks/use-swap-navigate'; import { useSwapContext } from '../../swap.context'; +import { convertInputAmountValueToFiat } from '../../swap.utils'; import { SwapAmountField } from './components/swap-amount-field'; import { SwapAssetSelectLayout } from './components/swap-asset-select.layout'; @@ -30,11 +30,13 @@ export function SwapAssetSelectBase() { const [assetField] = useField('swapAssetBase'); const navigate = useSwapNavigate(); - const amountAsFiat = useAlexSdkAmountAsFiat( - assetField.value.balance, - assetField.value.price, - amountField.value - ); + const amountAsFiat = + isDefined(assetField.value && amountField.value) && + convertInputAmountValueToFiat( + assetField.value.balance, + assetField.value.price, + amountField.value + ); const formattedBalance = formatMoneyWithoutSymbol(assetField.value.balance); const isSwapAssetBaseBalanceGreaterThanZero = values.swapAssetBase?.balance.amount.isGreaterThan(0); @@ -74,7 +76,7 @@ export function SwapAssetSelectBase() { showError={!!(showError && values.swapAssetQuote)} swapAmountInput={ diff --git a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx index 5a74b2e3a20..16ff6822f9c 100644 --- a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx +++ b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx @@ -1,13 +1,14 @@ import { useField } from 'formik'; import { RouteUrls } from '@shared/route-urls'; +import { isDefined } from '@shared/utils'; -import { useAlexSdkAmountAsFiat } from '@app/common/hooks/use-alex-sdk'; -import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; +import { formatMoneyWithoutSymbol, i18nFormatCurrency } from '@app/common/money/format-money'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { useSwapNavigate } from '../../hooks/use-swap-navigate'; import { useSwapContext } from '../../swap.context'; +import { convertInputAmountValueToFiat } from '../../swap.utils'; import { SwapAmountField } from './components/swap-amount-field'; import { SwapAssetSelectLayout } from './components/swap-asset-select.layout'; @@ -17,11 +18,13 @@ export function SwapAssetSelectQuote() { const [assetField] = useField('swapAssetQuote'); const navigate = useSwapNavigate(); - const amountAsFiat = useAlexSdkAmountAsFiat( - assetField.value?.balance, - assetField.value?.price, - amountField.value - ); + const amountAsFiat = + isDefined(assetField.value && amountField.value) && + convertInputAmountValueToFiat( + assetField.value.balance, + assetField.value.price, + amountField.value + ); return ( ) : ( - + ) } symbol={assetField.value?.name ?? 'Select asset'} diff --git a/src/app/pages/swap/swap.utils.ts b/src/app/pages/swap/swap.utils.ts index 00ec3bf69c5..e05a135802d 100644 --- a/src/app/pages/swap/swap.utils.ts +++ b/src/app/pages/swap/swap.utils.ts @@ -1,4 +1,10 @@ -import { createMoney } from '@shared/models/money.model'; +import { type Money, createMoney } from '@shared/models/money.model'; + +import { + checkIsMoneyAmountGreaterThanZero, + convertCryptoCurrencyMoneyToFiat, +} from '@app/common/money/fiat-conversion'; +import { unitToFractionalUnit } from '@app/common/money/unit-conversion'; import { SwapAsset } from './hooks/use-swap-form'; @@ -30,3 +36,14 @@ export function migratePositiveBalancesToTop(swappableAssets: SwapAsset[]) { const assetsWithZeroBalance = swappableAssets.filter(asset => asset.balance.amount.isEqualTo(0)); return [...assetsWithPositiveBalance, ...assetsWithZeroBalance]; } + +export function convertInputAmountValueToFiat(balance: Money, price: Money, value: string) { + const valueAsMoney = createMoney( + unitToFractionalUnit(balance.decimals)(value), + balance.symbol, + balance.decimals + ); + + if (!checkIsMoneyAmountGreaterThanZero(valueAsMoney)) return; + return convertCryptoCurrencyMoneyToFiat(balance.symbol, price, valueAsMoney); +} diff --git a/src/app/query/bitcoin/bitcoin-client.ts b/src/app/query/bitcoin/bitcoin-client.ts index 44796947f62..1629779f3ba 100644 --- a/src/app/query/bitcoin/bitcoin-client.ts +++ b/src/app/query/bitcoin/bitcoin-client.ts @@ -70,6 +70,7 @@ interface Brc20TokenResponse { available_balance: string; transferrable_balance: string; image_url: string | null; + min_listed_unit_price: number; } export interface Brc20Token extends Brc20TokenResponse { diff --git a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts index bb01f0a222b..4df6782d3ca 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts @@ -51,18 +51,18 @@ export function useGetBrc20TokensQuery() { } const brc20TokensPromises = addressesData.map(async address => { - const brc20Tokens = await client.HiroApi.getBrc20Balance(address); + const brc20Tokens = await client.BestinslotApi.getBrc20Balance(address); const tickerPromises = await Promise.all( - brc20Tokens.results.map(token => { - return client.HiroApi.getBrc20TickerData(token.ticker); + brc20Tokens.data.map(token => { + return client.BestinslotApi.getBrc20TickerData(token.ticker); }) ); - return brc20Tokens.results.map((token, index) => { + return brc20Tokens.data.map((token, index) => { return { ...token, - decimals: tickerPromises[index].results[0].decimals, + decimals: tickerPromises[index].data.decimals, holderAddress: address, }; }); diff --git a/src/app/query/common/market-data/market-data.hooks.ts b/src/app/query/common/market-data/market-data.hooks.ts index 499fd612837..1c4d63a9ca3 100644 --- a/src/app/query/common/market-data/market-data.hooks.ts +++ b/src/app/query/common/market-data/market-data.hooks.ts @@ -37,7 +37,7 @@ function pullPriceDataFromAvailableResponses(responses: MarketDataVendorWithPric .map(val => convertAmountToFractionalUnit(val, currencyDecimalsMap.USD)); } -export function useCryptoCurrencyMarketData(currency: CryptoCurrencies): MarketData { +export function useCryptoCurrencyMarketDataMeanAverage(currency: CryptoCurrencies): MarketData { const { data: coingecko } = useCoinGeckoMarketDataQuery(currency); const { data: coincap } = useCoincapMarketDataQuery(currency); const { data: binance } = useBinanceMarketDataQuery(currency); @@ -55,7 +55,7 @@ export function useCryptoCurrencyMarketData(currency: CryptoCurrencies): MarketD } export function useCalculateBitcoinFiatValue() { - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); return useCallback( (value: Money) => baseCurrencyAmountInQuote(value, btcMarketData), From 773421befe38e506d0d61a2cc6524dcd2c4f898b Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Fri, 12 Apr 2024 12:08:21 -0500 Subject: [PATCH 02/11] refactor: price as market data --- src/app/common/asset-utils.ts | 39 +++++++ .../stacks-crypto-asset.utils.spec.ts | 5 +- src/app/common/hooks/use-brc20-tokens.ts | 11 -- src/app/common/money/calculate-money.ts | 2 +- src/app/common/money/fiat-conversion.ts | 18 --- .../money/{is-money.ts => money.utils.ts} | 7 ++ src/app/common/utils/sort-assets-by-symbol.ts | 23 ---- .../hooks/use-bitcoin-custom-fee.tsx | 2 +- ...e-bitcoin-fees-list-multiple-recipients.ts | 4 +- .../brc20-token-asset-item.layout.tsx | 27 ++--- .../brc20-token-asset-list.tsx | 4 +- .../src20-token-asset-list.tsx | 4 +- .../fungible-token-asset.utils.ts | 23 +--- .../loaders/brc20-tokens-loader.tsx | 2 +- .../receive/components/receive-tokens.tsx | 18 ++- .../components/send-fiat-value.tsx | 11 +- .../form/brc-20/brc-20-choose-fee.tsx | 2 +- .../brc-20/brc20-send-form-confirmation.tsx | 2 +- .../form/brc-20/brc20-send-form.tsx | 38 ++++--- .../form/brc-20/use-brc20-send-form.tsx | 24 +--- .../sip10-token-send-form-container.tsx | 8 ++ .../form/stacks-sip10/use-sip10-send-form.tsx | 2 + src/app/pages/swap/alex-swap-container.tsx | 34 ++---- .../components/swap-asset-item.tsx | 16 +-- .../components/swap-asset-list.tsx | 37 ++++--- .../components/swap-amount-field.tsx | 4 +- .../components/swap-toggle-button.tsx | 19 ++-- .../swap-asset-select-base.tsx | 6 +- .../swap-asset-select-quote.tsx | 2 +- src/app/pages/swap/hooks/use-alex-swap.tsx | 76 ++----------- src/app/pages/swap/hooks/use-swap-form.tsx | 16 +-- src/app/pages/swap/swap.context.ts | 6 +- src/app/pages/swap/swap.tsx | 14 ++- src/app/pages/swap/swap.utils.ts | 44 +------- src/app/query/bitcoin/bitcoin-client.ts | 59 ++++------ .../{use-brc-20.ts => brc20-tokens.hooks.ts} | 35 ++++++ .../ordinals/brc20/brc20-tokens.query.ts | 9 +- ...ery.ts => alex-sdk-latest-prices.query.ts} | 0 .../alex-sdk-swappable-currency.query.ts | 25 +++++ .../query/common/alex-sdk/alex-sdk.hooks.ts | 103 ++++++++++++++++-- .../alex-sdk/swappable-currency.query.ts | 13 --- .../balance/stacks-ft-balances.hooks.ts | 9 +- .../balance/stacks-ft-balances.utils.ts | 9 +- src/shared/models/crypto-asset.model.ts | 4 +- 44 files changed, 401 insertions(+), 415 deletions(-) create mode 100644 src/app/common/asset-utils.ts delete mode 100644 src/app/common/hooks/use-brc20-tokens.ts delete mode 100644 src/app/common/money/fiat-conversion.ts rename src/app/common/money/{is-money.ts => money.utils.ts} (54%) delete mode 100644 src/app/common/utils/sort-assets-by-symbol.ts rename src/app/query/bitcoin/ordinals/brc20/{use-brc-20.ts => brc20-tokens.hooks.ts} (67%) rename src/app/query/common/alex-sdk/{latest-prices.query.ts => alex-sdk-latest-prices.query.ts} (100%) create mode 100644 src/app/query/common/alex-sdk/alex-sdk-swappable-currency.query.ts delete mode 100644 src/app/query/common/alex-sdk/swappable-currency.query.ts diff --git a/src/app/common/asset-utils.ts b/src/app/common/asset-utils.ts new file mode 100644 index 00000000000..12a2534e89d --- /dev/null +++ b/src/app/common/asset-utils.ts @@ -0,0 +1,39 @@ +import type { MarketData } from '@shared/models/market.model'; +import type { Money } from '@shared/models/money.model'; + +import { baseCurrencyAmountInQuote } from './money/calculate-money'; +import { i18nFormatCurrency } from './money/format-money'; +import { isMoneyGreaterThanZero } from './money/money.utils'; + +export function sortAssetsByName(assets: T) { + return assets + .sort((a, b) => { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }) + .sort((a, b) => { + if (a.name === 'STX') return -1; + if (b.name !== 'STX') return 1; + return 0; + }) + .sort((a, b) => { + if (a.name === 'BTC') return -1; + if (b.name !== 'BTC') return 1; + return 0; + }); +} + +export function migratePositiveAssetBalancesToTop(assets: T) { + const assetsWithPositiveBalance = assets.filter(asset => asset.balance.amount.isGreaterThan(0)); + const assetsWithZeroBalance = assets.filter(asset => asset.balance.amount.isEqualTo(0)); + return [...assetsWithPositiveBalance, ...assetsWithZeroBalance] as T; +} + +export function convertAssetBalanceToFiat< + T extends { balance: Money | null; marketData: MarketData | null }, +>(asset: T) { + if (!asset.marketData || !asset.balance || !isMoneyGreaterThanZero(asset.marketData.price)) + return ''; + return i18nFormatCurrency(baseCurrencyAmountInQuote(asset.balance, asset.marketData)); +} 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 ae948f06fa1..d8812ce8e34 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,5 +1,4 @@ import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model'; -import { createMoney } from '@shared/models/money.model'; import { isFtNameLikeStx, @@ -32,7 +31,7 @@ describe(isTransferableStacksFungibleTokenAsset.name, () => { canTransfer: true, hasMemo: true, imageCanonicalUri: '', - price: createMoney(0, 'USD'), + marketData: null, symbol: 'CAT', }; expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy(); @@ -49,7 +48,7 @@ describe(isTransferableStacksFungibleTokenAsset.name, () => { canTransfer: true, hasMemo: true, imageCanonicalUri: '', - price: createMoney(0, 'USD'), + marketData: null, symbol: 'CAT', }; expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy(); diff --git a/src/app/common/hooks/use-brc20-tokens.ts b/src/app/common/hooks/use-brc20-tokens.ts deleted file mode 100644 index b11a1c19a35..00000000000 --- a/src/app/common/hooks/use-brc20-tokens.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useGetBrc20TokensQuery } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; - -export function useBrc20Tokens() { - const { data: allBrc20TokensResponse } = useGetBrc20TokensQuery(); - const brc20Tokens = allBrc20TokensResponse?.pages - .flatMap(page => page.brc20Tokens) - .filter(token => token.length > 0) - .flatMap(token => token); - - return brc20Tokens ?? []; -} diff --git a/src/app/common/money/calculate-money.ts b/src/app/common/money/calculate-money.ts index bc332c82d70..fc44e6dee9a 100644 --- a/src/app/common/money/calculate-money.ts +++ b/src/app/common/money/calculate-money.ts @@ -6,7 +6,7 @@ import { isNumber } from '@shared/utils'; import { initBigNumber, sumNumbers } from '../math/helpers'; import { formatMoney } from './format-money'; -import { isMoney } from './is-money'; +import { isMoney } from './money.utils'; export function baseCurrencyAmountInQuote(quantity: Money, { pair, price }: MarketData) { if (quantity.symbol.toLowerCase() !== pair.base.toLowerCase()) diff --git a/src/app/common/money/fiat-conversion.ts b/src/app/common/money/fiat-conversion.ts deleted file mode 100644 index 0159e7aeffe..00000000000 --- a/src/app/common/money/fiat-conversion.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CryptoCurrencies } from '@shared/models/currencies.model'; -import { createMarketData, createMarketPair } from '@shared/models/market.model'; -import { type Money } from '@shared/models/money.model'; - -import { baseCurrencyAmountInQuote } from './calculate-money'; - -export function checkIsMoneyAmountGreaterThanZero(money: Money) { - return !(money.amount.isNaN() || money.amount.isZero()); -} - -export function convertCryptoCurrencyMoneyToFiat( - currency: CryptoCurrencies, - price: Money, - money: Money -) { - const cryptoCurrencyMarketData = createMarketData(createMarketPair(currency, 'USD'), price); - return baseCurrencyAmountInQuote(money, cryptoCurrencyMarketData); -} diff --git a/src/app/common/money/is-money.ts b/src/app/common/money/money.utils.ts similarity index 54% rename from src/app/common/money/is-money.ts rename to src/app/common/money/money.utils.ts index 9afe70b80fd..7a83e86239a 100644 --- a/src/app/common/money/is-money.ts +++ b/src/app/common/money/money.utils.ts @@ -1,3 +1,5 @@ +import BigNumber from 'bignumber.js'; + import { Money } from '@shared/models/money.model'; import { isObject } from '@shared/utils'; @@ -5,3 +7,8 @@ export function isMoney(val: unknown): val is Money { if (!isObject(val)) return false; return 'amount' in val && 'symbol' in val && 'decimals' in val; } + +export function isMoneyGreaterThanZero(money: Money) { + if (!BigNumber.isBigNumber(money.amount)) return; + return !(money.amount.isNaN() || money.amount.isZero()); +} diff --git a/src/app/common/utils/sort-assets-by-symbol.ts b/src/app/common/utils/sort-assets-by-symbol.ts deleted file mode 100644 index 20e1baa3fa4..00000000000 --- a/src/app/common/utils/sort-assets-by-symbol.ts +++ /dev/null @@ -1,23 +0,0 @@ -interface Asset { - name: string; - icon: string; -} - -export function sortAssetsBySymbol(assets: Asset[]) { - return assets - .sort((a, b) => { - if (a.name < b.name) return -1; - if (a.name > b.name) return 1; - return 0; - }) - .sort((a, b) => { - if (a.name === 'STX') return -1; - if (b.name !== 'STX') return 1; - return 0; - }) - .sort((a, b) => { - if (a.name === 'BTC') return -1; - if (b.name !== 'BTC') return 1; - return 0; - }); -} diff --git a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx index 2d4c757c11e..710f1e4feb3 100644 --- a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx +++ b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx @@ -67,7 +67,7 @@ export function useBitcoinCustomFeeMultipleRecipients({ }: UseBitcoinCustomFeeArgsMultipleRecipients) { const { balance } = useCurrentNativeSegwitAddressBalance(); const { data: utxos = [] } = useCurrentNativeSegwitUtxos(); - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); return useCallback( (feeRate: number) => { diff --git a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list-multiple-recipients.ts b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list-multiple-recipients.ts index cab6be7e977..7532c67a224 100644 --- a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list-multiple-recipients.ts +++ b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list-multiple-recipients.ts @@ -13,7 +13,7 @@ import { } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection'; import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client'; import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks'; -import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks'; import { FeesListItem } from './bitcoin-fees-list'; @@ -41,7 +41,7 @@ export function useBitcoinFeesListMultipleRecipients({ recipients, utxos, }: UseBitcoinFeesListArgs) { - const btcMarketData = useCryptoCurrencyMarketData('BTC'); + const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); const { data: feeRates, isLoading } = useAverageBitcoinFeeRates(); const feesList: FeesListItem[] = useMemo(() => { diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx index 0ce58463fb0..b2f15ced399 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-item.layout.tsx @@ -1,13 +1,9 @@ +import BigNumber from 'bignumber.js'; import { styled } from 'leather-styles/jsx'; -import { createMoney } from '@shared/models/money.model'; - +import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; import { formatBalance } from '@app/common/format-balance'; -import { - checkIsMoneyAmountGreaterThanZero, - convertCryptoCurrencyMoneyToFiat, -} from '@app/common/money/fiat-conversion'; -import { i18nFormatCurrency } from '@app/common/money/format-money'; +import { convertAmountToBaseUnit } from '@app/common/money/calculate-money'; import { Brc20Token } from '@app/query/bitcoin/bitcoin-client'; import { Brc20AvatarIcon } from '@app/ui/components/avatar/brc20-avatar-icon'; import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; @@ -19,16 +15,11 @@ interface Brc20TokenAssetItemLayoutProps { onClick?(): void; } export function Brc20TokenAssetItemLayout({ onClick, token }: Brc20TokenAssetItemLayoutProps) { - const balanceAsMoney = createMoney(Number(token.overall_balance), token.ticker, token.decimals); - const balanceAsString = balanceAsMoney.amount.toString(); - const formattedBalance = formatBalance(balanceAsString); - const priceAsMoney = createMoney(token.min_listed_unit_price, 'USD'); - const showFiatBalance = checkIsMoneyAmountGreaterThanZero(priceAsMoney); - const balanceAsFiat = showFiatBalance - ? i18nFormatCurrency( - convertCryptoCurrencyMoneyToFiat(token.ticker, priceAsMoney, balanceAsMoney) - ) - : ''; + const balanceAsString = token.balance?.amount.toString(); + const formattedBalance = formatBalance( + convertAmountToBaseUnit(token.balance ?? new BigNumber(0)).toString() + ); + const balanceAsFiat = convertAssetBalanceToFiat(token); return ( @@ -39,7 +30,7 @@ export function Brc20TokenAssetItemLayout({ onClick, token }: Brc20TokenAssetIte titleRight={ diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx index 066739c9966..62046fc2291 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx @@ -25,9 +25,9 @@ export function Brc20TokenAssetList({ brc20Tokens, variant }: Brc20TokenAssetLis variant === 'send' && btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0); function navigateToBrc20SendForm(token: Brc20Token) { - const { ticker, available_balance, decimals, holderAddress } = token; + const { ticker, balance, holderAddress, marketData } = token; navigate(RouteUrls.SendBrc20SendForm.replace(':ticker', ticker), { - state: { balance: available_balance, ticker, decimals, holderAddress }, + state: { balance, ticker, holderAddress, marketData }, }); } diff --git a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx index 04a8983a483..022aec7a158 100644 --- a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx +++ b/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx @@ -6,5 +6,7 @@ interface Src20TokenAssetListProps { src20Tokens: Src20Token[]; } export function Src20TokenAssetList({ src20Tokens }: Src20TokenAssetListProps) { - return src20Tokens.map(token => ); + return src20Tokens.map((token, i) => ( + + )); } diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts index ada3faf3059..78414d38f51 100644 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts +++ b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts @@ -2,13 +2,9 @@ import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; +import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; import { formatBalance } from '@app/common/format-balance'; -import { - checkIsMoneyAmountGreaterThanZero, - convertCryptoCurrencyMoneyToFiat, -} from '@app/common/money/fiat-conversion'; -import { i18nFormatCurrency } from '@app/common/money/format-money'; import { ftDecimals } from '@app/common/stacks-utils'; import { formatContractId, getTicker } from '@app/common/utils'; import { spamFilter } from '@app/common/utils/spam-filter'; @@ -33,19 +29,10 @@ export function parseStacksFungibleTokenAssetBalance( const imageCanonicalUri = getImageCanonicalUri(asset.imageCanonicalUri, asset.name); const caption = symbol || getTicker(friendlyName); const title = spamFilter(friendlyName); - - const showFiatBalance = - assetBalance.asset.price && checkIsMoneyAmountGreaterThanZero(assetBalance.asset.price); - const balanceAsFiat = showFiatBalance - ? assetBalance.asset.price && - i18nFormatCurrency( - convertCryptoCurrencyMoneyToFiat( - assetBalance.balance.symbol, - assetBalance.asset.price, - assetBalance.balance - ) - ) - : ''; + const balanceAsFiat = convertAssetBalanceToFiat({ + ...assetBalance.asset, + balance: assetBalance.balance, + }); return { amount, diff --git a/src/app/components/loaders/brc20-tokens-loader.tsx b/src/app/components/loaders/brc20-tokens-loader.tsx index ab57777495d..b5769cd74da 100644 --- a/src/app/components/loaders/brc20-tokens-loader.tsx +++ b/src/app/components/loaders/brc20-tokens-loader.tsx @@ -1,5 +1,5 @@ -import { useBrc20Tokens } from '@app/common/hooks/use-brc20-tokens'; import { Brc20Token } from '@app/query/bitcoin/bitcoin-client'; +import { useBrc20Tokens } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; interface Brc20TokensLoaderProps { children(brc20Tokens: Brc20Token[]): React.ReactNode; diff --git a/src/app/pages/receive/components/receive-tokens.tsx b/src/app/pages/receive/components/receive-tokens.tsx index 1c8f47ee75e..62356b724db 100644 --- a/src/app/pages/receive/components/receive-tokens.tsx +++ b/src/app/pages/receive/components/receive-tokens.tsx @@ -4,15 +4,12 @@ import { HomePageSelectors } from '@tests/selectors/home.selectors'; import { css } from 'leather-styles/css'; import { Stack } from 'leather-styles/jsx'; -import { isDefined } from '@shared/utils'; - import { copyToClipboard } from '@app/common/utils/copy-to-clipboard'; -import { sortAssetsBySymbol } from '@app/common/utils/sort-assets-by-symbol'; import { useToast } from '@app/features/toasts/use-toast'; -import { useAlexSdkSwappableCurrencyQuery } from '@app/query/common/alex-sdk/swappable-currency.query'; +import { useAlexSwappableAssets } from '@app/query/common/alex-sdk/alex-sdk.hooks'; import { useConfigRunesEnabled } from '@app/query/common/remote-config/remote-config.query'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; -import { Avatar, defaultFallbackDelay, getAvatarFallback } from '@app/ui/components/avatar/avatar'; +import { Avatar, defaultFallbackDelay } from '@app/ui/components/avatar/avatar'; import { Brc20AvatarIcon } from '@app/ui/components/avatar/brc20-avatar-icon'; import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; import { RunesAvatarIcon } from '@app/ui/components/avatar/runes-avatar-icon'; @@ -39,20 +36,19 @@ export function ReceiveTokens({ const toast = useToast(); const network = useCurrentNetwork(); const runesEnabled = useConfigRunesEnabled(); - const { data: supportedCurrencies = [] } = useAlexSdkSwappableCurrencyQuery(); + const { data: swapAssets = [] } = useAlexSwappableAssets(); const receivableAssets = useMemo( () => - sortAssetsBySymbol(supportedCurrencies.filter(isDefined)) + swapAssets .filter(asset => asset.name !== 'STX') .map(asset => ({ + ...asset, address: stxAddress, - fallback: getAvatarFallback(asset.name), - icon: asset.icon, - name: asset.name, })), - [stxAddress, supportedCurrencies] + [stxAddress, swapAssets] ); + return ( (createMoneyFromDecimal(0, assetSymbol)); + const [assetValue, setAssetValue] = useState( + createMoneyFromDecimal(0, assetSymbol, assetDecimals) + ); useEffect(() => { let amount = Number(field.value); @@ -26,9 +29,9 @@ export function SendFiatValue({ marketData, assetSymbol = '' }: SendFiatInputPro amount = 0; } - const assetAmount = createMoneyFromDecimal(amount, assetSymbol); + const assetAmount = createMoneyFromDecimal(amount, assetSymbol, assetDecimals); setAssetValue(assetAmount); - }, [field.value, assetSymbol]); + }, [field.value, assetSymbol, assetDecimals]); return ( diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx index 9cc70a9332d..b1a70c81976 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx @@ -21,7 +21,7 @@ import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choos import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; import { useToast } from '@app/features/toasts/use-toast'; import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client'; -import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/use-brc-20'; +import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; import { useSignBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container'; diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form-confirmation.tsx index 7fa0810da20..d6d176f3e5b 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form-confirmation.tsx @@ -17,7 +17,7 @@ import { InfoCardSeparator, } from '@app/components/info-card/info-card'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; -import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/use-brc-20'; +import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction'; import { Button } from '@app/ui/components/button/button'; import { Footer } from '@app/ui/components/containers/footers/footer'; diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form.tsx index 8bfb2839be0..060f7b33a81 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc20-send-form.tsx @@ -5,6 +5,10 @@ import { Form, Formik } from 'formik'; import { Box, styled } from 'leather-styles/jsx'; import get from 'lodash.get'; +import type { MarketData } from '@shared/models/market.model'; +import type { Money } from '@shared/models/money.model'; + +import { convertAmountToBaseUnit } from '@app/common/money/calculate-money'; import { formatMoney } from '@app/common/money/format-money'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; import { Brc20AvatarIcon } from '@app/ui/components/avatar/brc20-avatar-icon'; @@ -18,6 +22,7 @@ import { CardContent } from '@app/ui/layout/card/card-content'; import { AmountField } from '../../components/amount-field'; import { SelectedAssetField } from '../../components/selected-asset-field'; +import { SendFiatValue } from '../../components/send-fiat-value'; import { SendMaxButton } from '../../components/send-max-button'; import { defaultSendFormFormikProps } from '../../send-form.utils'; import { useBrc20SendForm } from './use-brc20-send-form'; @@ -25,23 +30,17 @@ import { useBrc20SendForm } from './use-brc20-send-form'; function useBrc20SendFormRouteState() { const { state } = useLocation(); return { - balance: get(state, 'balance', '') as string, + balance: get(state, 'balance', '') as Money, ticker: get(state, 'ticker', '') as string, - decimals: get(state, 'decimals', '') as number, holderAddress: get(state, 'holderAddress', '') as string, + marketData: get(state, 'marketData') as MarketData, }; } export function Brc20SendForm() { - const { balance, ticker, decimals, holderAddress } = useBrc20SendFormRouteState(); - const { - initialValues, - chooseTransactionFee, - validationSchema, - formRef, - onFormStateChange, - moneyBalance, - } = useBrc20SendForm({ balance, ticker, decimals, holderAddress }); + const { balance, ticker, holderAddress, marketData } = useBrc20SendFormRouteState(); + const { initialValues, chooseTransactionFee, validationSchema, formRef, onFormStateChange } = + useBrc20SendForm({ balance, ticker, holderAddress }); return ( @@ -67,7 +66,7 @@ export function Brc20SendForm() { Continue @@ -75,14 +74,23 @@ export function Brc20SendForm() { > } autoComplete="off" + switchableAmount={ + marketData ? ( + + ) : undefined + } /> } name={ticker} symbol={ticker} /> diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/use-brc20-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc-20/use-brc20-send-form.tsx index f907b5ac9b4..dbc9fee1f70 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc-20/use-brc20-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc-20/use-brc20-send-form.tsx @@ -1,17 +1,15 @@ import { useRef } from 'react'; import { useNavigate } from 'react-router-dom'; -import BigNumber from 'bignumber.js'; import { FormikHelpers, FormikProps } from 'formik'; import * as yup from 'yup'; import { logger } from '@shared/logger'; -import { createMoney } from '@shared/models/money.model'; +import { type Money } from '@shared/models/money.model'; import { RouteUrls } from '@shared/route-urls'; import { noop } from '@shared/utils'; import { useOnMount } from '@app/common/hooks/use-on-mount'; -import { unitToFractionalUnit } from '@app/common/money/unit-conversion'; import { useWalletType } from '@app/common/use-wallet-type'; import { btcAddressNetworkValidator, @@ -33,18 +31,12 @@ interface Brc20SendFormValues { } interface UseBrc20SendFormArgs { - balance: string; + balance: Money; ticker: string; - decimals: number; holderAddress: string; } -export function useBrc20SendForm({ - balance, - ticker, - decimals, - holderAddress, -}: UseBrc20SendFormArgs) { +export function useBrc20SendForm({ balance, ticker, holderAddress }: UseBrc20SendFormArgs) { const formRef = useRef>(null); const { whenWallet } = useWalletType(); const navigate = useNavigate(); @@ -63,10 +55,7 @@ export function useBrc20SendForm({ }); const validationSchema = yup.object({ - amount: yup - .number() - .concat(currencyAmountValidator()) - .concat(tokenAmountValidator(createMoney(new BigNumber(balance), ticker, 0))), + amount: yup.number().concat(currencyAmountValidator()).concat(tokenAmountValidator(balance)), recipient: yup .string() .concat(btcAddressValidator()) @@ -96,10 +85,5 @@ export function useBrc20SendForm({ validationSchema, formRef, onFormStateChange, - moneyBalance: createMoney( - unitToFractionalUnit(decimals)(new BigNumber(balance)), - ticker, - decimals - ), }; } diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/sip10-token-send-form-container.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/sip10-token-send-form-container.tsx index 607a132e312..643935c0717 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/sip10-token-send-form-container.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/sip10-token-send-form-container.tsx @@ -3,6 +3,7 @@ import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; import { AmountField } from '../../components/amount-field'; import { SelectedAssetField } from '../../components/selected-asset-field'; +import { SendFiatValue } from '../../components/send-fiat-value'; import { SendMaxButton } from '../../components/send-max-button'; import { StacksCommonSendForm } from '../stacks/stacks-common-send-form'; import { useSip10SendForm } from './use-sip10-send-form'; @@ -23,6 +24,8 @@ export function Sip10TokenSendFormContainer({ stacksFtFees: fees, validationSchema, avatar, + marketData, + decimals, } = useSip10SendForm({ symbol, contractId }); const amountField = ( @@ -33,6 +36,11 @@ export function Sip10TokenSendFormContainer({ } tokenSymbol={symbol} autoComplete="off" + switchableAmount={ + marketData ? ( + + ) : undefined + } /> ); const selectedAssetField = ( diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx index 10fe5b0a81b..31d63e80522 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks-sip10/use-sip10-send-form.tsx @@ -60,6 +60,8 @@ export function useSip10SendForm({ symbol, contractId }: UseSip10SendFormArgs) { sendMaxBalance, stacksFtFees, symbol, + decimals: assetBalance.asset.decimals, + marketData: assetBalance.asset.marketData, avatar: createFtAvatar(), validationSchema: yup.object({ amount: stacksFungibleTokenAmountValidator(availableTokenBalance), diff --git a/src/app/pages/swap/alex-swap-container.tsx b/src/app/pages/swap/alex-swap-container.tsx index 0b19faf9676..d96bd033350 100644 --- a/src/app/pages/swap/alex-swap-container.tsx +++ b/src/app/pages/swap/alex-swap-container.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { Outlet } from 'react-router-dom'; import { bytesToHex } from '@stacks/common'; @@ -16,9 +16,11 @@ import { RouteUrls } from '@shared/route-urls'; import { isDefined, isUndefined } from '@shared/utils'; import { alex } from '@shared/utils/alex-sdk'; +import { migratePositiveAssetBalancesToTop } from '@app/common/asset-utils'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; import { useWalletType } from '@app/common/use-wallet-type'; import { NonceSetter } from '@app/components/nonce-setter'; +import { defaultSwapFee } from '@app/query/common/alex-sdk/alex-sdk.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useGenerateStacksContractCallUnsignedTx } from '@app/store/transactions/contract-call.hooks'; import { useSignStacksTransaction } from '@app/store/transactions/transaction.hooks'; @@ -29,14 +31,9 @@ import { generateSwapRoutes } from './generate-swap-routes'; import { useAlexBroadcastSwap } from './hooks/use-alex-broadcast-swap'; import { oneHundredMillion, useAlexSwap } from './hooks/use-alex-swap'; import { useStacksBroadcastSwap } from './hooks/use-stacks-broadcast-swap'; -import { SwapAsset, SwapFormValues } from './hooks/use-swap-form'; +import { SwapFormValues } from './hooks/use-swap-form'; import { useSwapNavigate } from './hooks/use-swap-navigate'; import { SwapContext, SwapProvider } from './swap.context'; -import { - defaultSwapFee, - migratePositiveBalancesToTop, - sortSwappableAssetsBySymbol, -} from './swap.utils'; export const alexSwapRoutes = generateSwapRoutes(); @@ -57,27 +54,18 @@ function AlexSwapContainer() { }); const { - fetchToAmount, - createSwapAssetFromAlexCurrency, + fetchQuoteAmount, isFetchingExchangeRate, onSetIsFetchingExchangeRate, onSetSwapSubmissionData, slippage, - supportedCurrencies, + swapAssets, swapSubmissionData, } = useAlexSwap(); const broadcastAlexSwap = useAlexBroadcastSwap(); const broadcastStacksSwap = useStacksBroadcastSwap(); - const swappableAssets: SwapAsset[] = useMemo( - () => - sortSwappableAssetsBySymbol( - supportedCurrencies.map(createSwapAssetFromAlexCurrency).filter(isDefined) - ), - [createSwapAssetFromAlexCurrency, supportedCurrencies] - ); - async function onSubmitSwapForReview(values: SwapFormValues) { if (isUndefined(values.swapAssetBase) || isUndefined(values.swapAssetQuote)) { logger.error('Error submitting swap for review'); @@ -96,9 +84,7 @@ function AlexSwapContainer() { liquidityFee: new BigNumber(Number(lpFee)).dividedBy(oneHundredMillion).toNumber(), nonce: values.nonce, protocol: 'ALEX', - router: router - .map(x => createSwapAssetFromAlexCurrency(supportedCurrencies.find(y => y.id === x))) - .filter(isDefined), + router: router.map(x => swapAssets.find(asset => asset.currency === x)).filter(isDefined), slippage, sponsored: isSponsoredByAlex, swapAmountBase: values.swapAmountBase, @@ -191,15 +177,15 @@ function AlexSwapContainer() { } const swapContextValue: SwapContext = { - fetchToAmount, + fetchQuoteAmount, isFetchingExchangeRate, isSendingMax, onSetIsFetchingExchangeRate, onSetIsSendingMax: value => setIsSendingMax(value), onSubmitSwapForReview, onSubmitSwap, - swappableAssetsBase: migratePositiveBalancesToTop(swappableAssets), - swappableAssetsQuote: swappableAssets, + swappableAssetsBase: migratePositiveAssetBalancesToTop(swapAssets), + swappableAssetsQuote: swapAssets, swapSubmissionData, }; diff --git a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx index 2b191fd32bf..5ae67e717e5 100644 --- a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx +++ b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-item.tsx @@ -1,11 +1,8 @@ import { SwapSelectors } from '@tests/selectors/swap.selectors'; -import { - checkIsMoneyAmountGreaterThanZero, - convertCryptoCurrencyMoneyToFiat, -} from '@app/common/money/fiat-conversion'; -import { formatMoneyWithoutSymbol, i18nFormatCurrency } from '@app/common/money/format-money'; -import type { SwapAsset } from '@app/pages/swap/hooks/use-swap-form'; +import { convertAssetBalanceToFiat } from '@app/common/asset-utils'; +import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; +import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks'; 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'; @@ -22,12 +19,7 @@ export function SwapAssetItem({ asset, onClick }: SwapAssetItemProps) { const ftMetadataName = ftMetadata && isFtAsset(ftMetadata) ? ftMetadata.name : asset.name; const displayName = asset.displayName ?? ftMetadataName; const fallback = getAvatarFallback(asset.name); - const showFiatBalance = checkIsMoneyAmountGreaterThanZero(asset.price); - const balanceAsFiat = showFiatBalance - ? i18nFormatCurrency( - convertCryptoCurrencyMoneyToFiat(asset.balance.symbol, asset.price, asset.balance) - ) - : ''; + const balanceAsFiat = convertAssetBalanceToFiat(asset); return ( diff --git a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx index 736c2152b17..1c2cf94a93f 100644 --- a/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx +++ b/src/app/pages/swap/components/swap-asset-dialog/components/swap-asset-list.tsx @@ -12,8 +12,9 @@ import { isUndefined } from '@shared/utils'; import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; import { useSwapContext } from '@app/pages/swap/swap.context'; +import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks'; -import { SwapAsset, SwapFormValues } from '../../../hooks/use-swap-form'; +import { SwapFormValues } from '../../../hooks/use-swap-form'; import { SwapAssetItem } from './swap-asset-item'; interface SwapAssetList { @@ -21,7 +22,7 @@ interface SwapAssetList { type: string; } export function SwapAssetList({ assets, type }: SwapAssetList) { - const { fetchToAmount } = useSwapContext(); + const { fetchQuoteAmount } = useSwapContext(); const { setFieldError, setFieldValue, values } = useFormikContext(); const navigate = useNavigate(); const { base, quote } = useParams(); @@ -35,33 +36,33 @@ export function SwapAssetList({ assets, type }: SwapAssetList) { ); async function onSelectAsset(asset: SwapAsset) { - let from: SwapAsset | undefined; - let to: SwapAsset | undefined; + let baseAsset: SwapAsset | undefined; + let quoteAsset: SwapAsset | undefined; if (isBaseList) { - from = asset; - to = values.swapAssetQuote; + baseAsset = asset; + quoteAsset = values.swapAssetQuote; await setFieldValue('swapAssetBase', asset); - navigate(RouteUrls.Swap.replace(':base', from.name).replace(':quote', quote ?? '')); + navigate(RouteUrls.Swap.replace(':base', baseAsset.name).replace(':quote', quote ?? '')); } else if (isQuoteList) { - from = values.swapAssetBase; - to = asset; + baseAsset = values.swapAssetBase; + quoteAsset = asset; await setFieldValue('swapAssetQuote', asset); setFieldError('swapAssetQuote', undefined); - navigate(RouteUrls.Swap.replace(':base', base ?? '').replace(':quote', to.name)); + navigate(RouteUrls.Swap.replace(':base', base ?? '').replace(':quote', quoteAsset.name)); } - if (from && to && values.swapAmountBase) { - const toAmount = await fetchToAmount(from, to, values.swapAmountBase); - if (isUndefined(toAmount)) { + if (baseAsset && quoteAsset && values.swapAmountBase) { + const quoteAmount = await fetchQuoteAmount(baseAsset, quoteAsset, values.swapAmountBase); + if (isUndefined(quoteAmount)) { await setFieldValue('swapAmountQuote', ''); return; } - const toAmountAsMoney = createMoney( - convertAmountToFractionalUnit(new BigNumber(toAmount), to?.balance.decimals), - to?.balance.symbol ?? '', - to?.balance.decimals + const quoteAmountAsMoney = createMoney( + convertAmountToFractionalUnit(new BigNumber(quoteAmount), quoteAsset?.balance.decimals), + quoteAsset?.balance.symbol ?? '', + quoteAsset?.balance.decimals ); - await setFieldValue('swapAmountQuote', formatMoneyWithoutSymbol(toAmountAsMoney)); + await setFieldValue('swapAmountQuote', formatMoneyWithoutSymbol(quoteAmountAsMoney)); setFieldError('swapAmountQuote', undefined); } } diff --git a/src/app/pages/swap/components/swap-asset-select/components/swap-amount-field.tsx b/src/app/pages/swap/components/swap-asset-select/components/swap-amount-field.tsx index f3db4fe81d1..e47d3a5df55 100644 --- a/src/app/pages/swap/components/swap-asset-select/components/swap-amount-field.tsx +++ b/src/app/pages/swap/components/swap-asset-select/components/swap-amount-field.tsx @@ -27,7 +27,7 @@ interface SwapAmountFieldProps { name: string; } export function SwapAmountField({ amountAsFiat, isDisabled, name }: SwapAmountFieldProps) { - const { fetchToAmount, isFetchingExchangeRate, onSetIsSendingMax } = useSwapContext(); + const { fetchQuoteAmount, isFetchingExchangeRate, onSetIsSendingMax } = useSwapContext(); const { setFieldError, setFieldValue, values } = useFormikContext(); const [field] = useField(name); const showError = useShowFieldError(name) && name === 'swapAmountBase' && values.swapAssetQuote; @@ -37,7 +37,7 @@ export function SwapAmountField({ amountAsFiat, isDisabled, name }: SwapAmountFi if (isUndefined(swapAssetBase) || isUndefined(swapAssetQuote)) return; onSetIsSendingMax(false); const value = event.currentTarget.value; - const toAmount = await fetchToAmount(swapAssetBase, swapAssetQuote, value); + const toAmount = await fetchQuoteAmount(swapAssetBase, swapAssetQuote, value); if (isUndefined(toAmount)) { await setFieldValue('swapAmountQuote', ''); return; diff --git a/src/app/pages/swap/components/swap-asset-select/components/swap-toggle-button.tsx b/src/app/pages/swap/components/swap-asset-select/components/swap-toggle-button.tsx index e6db08a873d..dafd83d09a2 100644 --- a/src/app/pages/swap/components/swap-asset-select/components/swap-toggle-button.tsx +++ b/src/app/pages/swap/components/swap-asset-select/components/swap-toggle-button.tsx @@ -12,8 +12,8 @@ import { SwapFormValues } from '../../../hooks/use-swap-form'; import { useSwapContext } from '../../../swap.context'; export function SwapToggleButton() { - const { fetchToAmount, isFetchingExchangeRate, onSetIsSendingMax } = useSwapContext(); - const { setFieldValue, validateForm, values } = useFormikContext(); + const { fetchQuoteAmount, isFetchingExchangeRate, onSetIsSendingMax } = useSwapContext(); + const { setFieldValue, values } = useFormikContext(); const navigate = useNavigate(); async function onToggleSwapAssets() { @@ -24,21 +24,20 @@ export function SwapToggleButton() { const prevAssetBase = values.swapAssetBase; const prevAssetQuote = values.swapAssetQuote; - await setFieldValue('swapAssetBase', prevAssetQuote); - await setFieldValue('swapAssetQuote', prevAssetBase); - await setFieldValue('swapAmountBase', prevAmountQuote); + void setFieldValue('swapAssetBase', prevAssetQuote); + void setFieldValue('swapAssetQuote', prevAssetBase); + void setFieldValue('swapAmountBase', prevAmountQuote); if (isDefined(prevAssetBase) && isDefined(prevAssetQuote)) { - const quoteAmount = await fetchToAmount(prevAssetQuote, prevAssetBase, prevAmountQuote); + const quoteAmount = await fetchQuoteAmount(prevAssetQuote, prevAssetBase, prevAmountQuote); if (isUndefined(quoteAmount)) { - await setFieldValue('swapAmountQuote', ''); + void setFieldValue('swapAmountQuote', ''); return; } - await setFieldValue('swapAmountQuote', Number(quoteAmount)); + void setFieldValue('swapAmountQuote', Number(quoteAmount)); } else { - await setFieldValue('swapAmountQuote', Number(prevAmountBase)); + void setFieldValue('swapAmountQuote', Number(prevAmountBase)); } - await validateForm(); navigate( RouteUrls.Swap.replace(':base', prevAssetQuote?.name ?? '').replace( ':quote', diff --git a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx index 94221abaa7b..21084aa4e94 100644 --- a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx +++ b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-base.tsx @@ -22,7 +22,7 @@ const maxAvailableTooltip = const sendingMaxTooltip = 'When sending max, this amount is affected by the fee you choose.'; export function SwapAssetSelectBase() { - const { fetchToAmount, isFetchingExchangeRate, isSendingMax, onSetIsSendingMax } = + const { fetchQuoteAmount, isFetchingExchangeRate, isSendingMax, onSetIsSendingMax } = useSwapContext(); const { setFieldValue, setFieldError, values } = useFormikContext(); const [amountField, amountFieldMeta, amountFieldHelpers] = useField('swapAmountBase'); @@ -34,7 +34,7 @@ export function SwapAssetSelectBase() { isDefined(assetField.value && amountField.value) && convertInputAmountValueToFiat( assetField.value.balance, - assetField.value.price, + assetField.value.marketData, amountField.value ); const formattedBalance = formatMoneyWithoutSymbol(assetField.value.balance); @@ -48,7 +48,7 @@ export function SwapAssetSelectBase() { await amountFieldHelpers.setValue(Number(formattedBalance)); await amountFieldHelpers.setTouched(true); if (isUndefined(swapAssetQuote)) return; - const toAmount = await fetchToAmount(swapAssetBase, swapAssetQuote, formattedBalance); + const toAmount = await fetchQuoteAmount(swapAssetBase, swapAssetQuote, formattedBalance); if (isUndefined(toAmount)) { await setFieldValue('swapAmountQuote', ''); return; diff --git a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx index 16ff6822f9c..3af59a73a89 100644 --- a/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx +++ b/src/app/pages/swap/components/swap-asset-select/swap-asset-select-quote.tsx @@ -22,7 +22,7 @@ export function SwapAssetSelectQuote() { isDefined(assetField.value && amountField.value) && convertInputAmountValueToFiat( assetField.value.balance, - assetField.value.price, + assetField.value.marketData, amountField.value ); diff --git a/src/app/pages/swap/hooks/use-alex-swap.tsx b/src/app/pages/swap/hooks/use-alex-swap.tsx index 71034ec39c5..d66bc80cd59 100644 --- a/src/app/pages/swap/hooks/use-alex-swap.tsx +++ b/src/app/pages/swap/hooks/use-alex-swap.tsx @@ -1,22 +1,13 @@ -import { useCallback, useState } from 'react'; +import { useState } from 'react'; -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 { 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'; +import { type SwapAsset, useAlexSwappableAssets } from '@app/query/common/alex-sdk/alex-sdk.hooks'; import { SwapSubmissionData } from '../swap.context'; -import { SwapAsset } from './use-swap-form'; export const oneHundredMillion = 100_000_000; @@ -24,62 +15,18 @@ export function useAlexSwap() { const [swapSubmissionData, setSwapSubmissionData] = useState(); const [slippage, _setSlippage] = useState(0.04); const [isFetchingExchangeRate, setIsFetchingExchangeRate] = useState(false); - const { data: supportedCurrencies = [] } = useAlexSdkSwappableCurrencyQuery(); - const { data: prices } = useAlexSdkLatestPricesQuery(); - const { availableBalance: availableStxBalance } = useStxBalance(); - const account = useCurrentStacksAccount(); - const stacksFtAssetBalances = useTransferableStacksFungibleTokenAssetBalances( - account?.address ?? '' - ); + const { data: swapAssets = [] } = useAlexSwappableAssets(); - const createSwapAssetFromAlexCurrency = useCallback( - (tokenInfo?: TokenInfo) => { - if (!prices) return; - if (!tokenInfo) { - logger.error('No token data found to swap'); - return; - } - - const currency = tokenInfo.id as Currency; - const price = convertAmountToFractionalUnit(new BigNumber(prices[currency] ?? 0), 2); - const swapAsset = { - currency, - icon: tokenInfo.icon, - name: tokenInfo.name, - price: createMoney(price, 'USD'), - principal: pullContractIdFromIdentity(tokenInfo.contractAddress), - }; - - if (currency === Currency.STX) { - return { - ...swapAsset, - balance: availableStxBalance, - displayName: 'Stacks', - }; - } - - const fungibleTokenBalance = - stacksFtAssetBalances.find(x => tokenInfo.contractAddress === x.asset.contractId) - ?.balance ?? createMoney(0, tokenInfo.name, tokenInfo.decimals); - - return { - ...swapAsset, - balance: fungibleTokenBalance, - }; - }, - [availableStxBalance, prices, stacksFtAssetBalances] - ); - - async function fetchToAmount( - from: SwapAsset, - to: SwapAsset, - fromAmount: string + async function fetchQuoteAmount( + base: SwapAsset, + quote: SwapAsset, + baseAmount: string ): Promise { - const amount = new BigNumber(fromAmount).multipliedBy(oneHundredMillion).dp(0).toString(); + const amount = new BigNumber(baseAmount).multipliedBy(oneHundredMillion).dp(0).toString(); const amountAsBigInt = isNaN(Number(amount)) ? BigInt(0) : BigInt(amount); try { setIsFetchingExchangeRate(true); - const result = await alex.getAmountTo(from.currency, amountAsBigInt, to.currency); + const result = await alex.getAmountTo(base.currency, amountAsBigInt, quote.currency); setIsFetchingExchangeRate(false); return new BigNumber(Number(result)).dividedBy(oneHundredMillion).toString(); } catch (e) { @@ -90,13 +37,12 @@ export function useAlexSwap() { } return { - fetchToAmount, - createSwapAssetFromAlexCurrency, + fetchQuoteAmount, isFetchingExchangeRate, onSetIsFetchingExchangeRate: (value: boolean) => setIsFetchingExchangeRate(value), onSetSwapSubmissionData: (value: SwapSubmissionData) => setSwapSubmissionData(value), slippage, - supportedCurrencies, + swapAssets, swapSubmissionData, }; } diff --git a/src/app/pages/swap/hooks/use-swap-form.tsx b/src/app/pages/swap/hooks/use-swap-form.tsx index 0121f80b6de..e01e395495f 100644 --- a/src/app/pages/swap/hooks/use-swap-form.tsx +++ b/src/app/pages/swap/hooks/use-swap-form.tsx @@ -1,24 +1,16 @@ -import { Currency } from 'alex-sdk'; import BigNumber from 'bignumber.js'; import * as yup from 'yup'; import { FormErrorMessages } from '@shared/error-messages'; import { FeeTypes } from '@shared/models/fees/fees.model'; import { StacksTransactionFormValues } from '@shared/models/form.model'; -import { Money, createMoney } from '@shared/models/money.model'; +import { createMoney } from '@shared/models/money.model'; import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money'; +import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; -export interface SwapAsset { - balance: Money; - currency: Currency; - displayName?: string; - icon: string; - name: string; - price: Money; - principal: string; -} +import { useSwapContext } from '../swap.context'; export interface SwapFormValues extends StacksTransactionFormValues { swapAmountBase: string; @@ -28,6 +20,7 @@ export interface SwapFormValues extends StacksTransactionFormValues { } export function useSwapForm() { + const { isFetchingExchangeRate } = useSwapContext(); const { data: nextNonce } = useNextNonce(); const initialValues: SwapFormValues = { @@ -49,6 +42,7 @@ export function useSwapForm() { .test({ message: 'Insufficient balance', test(value) { + if (isFetchingExchangeRate) return true; const { swapAssetBase } = this.parent; const valueInFractionalUnit = convertAmountToFractionalUnit( createMoney( diff --git a/src/app/pages/swap/swap.context.ts b/src/app/pages/swap/swap.context.ts index 2b98312c02d..deec0ddbd6f 100644 --- a/src/app/pages/swap/swap.context.ts +++ b/src/app/pages/swap/swap.context.ts @@ -1,6 +1,8 @@ import { createContext, useContext } from 'react'; -import { SwapAsset, SwapFormValues } from './hooks/use-swap-form'; +import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks'; + +import { SwapFormValues } from './hooks/use-swap-form'; export interface SwapSubmissionData extends SwapFormValues { liquidityFee: number; @@ -12,7 +14,7 @@ export interface SwapSubmissionData extends SwapFormValues { } export interface SwapContext { - fetchToAmount(from: SwapAsset, to: SwapAsset, fromAmount: string): Promise; + fetchQuoteAmount(from: SwapAsset, to: SwapAsset, fromAmount: string): Promise; isFetchingExchangeRate: boolean; isSendingMax: boolean; onSetIsFetchingExchangeRate(value: boolean): void; diff --git a/src/app/pages/swap/swap.tsx b/src/app/pages/swap/swap.tsx index 48565753c00..1a2f81fd5a3 100644 --- a/src/app/pages/swap/swap.tsx +++ b/src/app/pages/swap/swap.tsx @@ -19,7 +19,8 @@ import { useSwapContext } from './swap.context'; export function Swap() { const { isFetchingExchangeRate, swappableAssetsBase, swappableAssetsQuote } = useSwapContext(); - const { dirty, isValid, setFieldValue, values } = useFormikContext(); + const { dirty, isValid, setFieldValue, values, validateForm } = + useFormikContext(); const { base, quote } = useParams(); useEffect(() => { @@ -33,7 +34,16 @@ export function Swap() { 'swapAssetQuote', swappableAssetsQuote.find(asset => asset.name === quote) ); - }, [base, quote, setFieldValue, swappableAssetsBase, swappableAssetsQuote, values.swapAssetBase]); + void validateForm(); + }, [ + base, + quote, + setFieldValue, + swappableAssetsBase, + swappableAssetsQuote, + validateForm, + values.swapAssetBase, + ]); if (isUndefined(values.swapAssetBase)) return ; diff --git a/src/app/pages/swap/swap.utils.ts b/src/app/pages/swap/swap.utils.ts index e05a135802d..c31910c2e93 100644 --- a/src/app/pages/swap/swap.utils.ts +++ b/src/app/pages/swap/swap.utils.ts @@ -1,49 +1,17 @@ +import type { MarketData } from '@shared/models/market.model'; import { type Money, createMoney } from '@shared/models/money.model'; -import { - checkIsMoneyAmountGreaterThanZero, - convertCryptoCurrencyMoneyToFiat, -} from '@app/common/money/fiat-conversion'; +import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; +import { isMoneyGreaterThanZero } from '@app/common/money/money.utils'; import { unitToFractionalUnit } from '@app/common/money/unit-conversion'; -import { SwapAsset } from './hooks/use-swap-form'; - -export const defaultSwapFee = createMoney(1000000, 'STX'); - -export function sortSwappableAssetsBySymbol(swappableAssets: SwapAsset[]) { - return swappableAssets - .sort((a, b) => { - if (a.name < b.name) return -1; - if (a.name > b.name) return 1; - return 0; - }) - .sort((a, b) => { - if (a.name === 'STX') return -1; - if (b.name !== 'STX') return 1; - return 0; - }) - .sort((a, b) => { - if (a.name === 'BTC') return -1; - if (b.name !== 'BTC') return 1; - return 0; - }); -} - -export function migratePositiveBalancesToTop(swappableAssets: SwapAsset[]) { - const assetsWithPositiveBalance = swappableAssets.filter(asset => - asset.balance.amount.isGreaterThan(0) - ); - const assetsWithZeroBalance = swappableAssets.filter(asset => asset.balance.amount.isEqualTo(0)); - return [...assetsWithPositiveBalance, ...assetsWithZeroBalance]; -} - -export function convertInputAmountValueToFiat(balance: Money, price: Money, value: string) { +export function convertInputAmountValueToFiat(balance: Money, price: MarketData, value: string) { const valueAsMoney = createMoney( unitToFractionalUnit(balance.decimals)(value), balance.symbol, balance.decimals ); - if (!checkIsMoneyAmountGreaterThanZero(valueAsMoney)) return; - return convertCryptoCurrencyMoneyToFiat(balance.symbol, price, valueAsMoney); + if (!isMoneyGreaterThanZero(valueAsMoney)) return; + return baseCurrencyAmountInQuote(valueAsMoney, price); } diff --git a/src/app/query/bitcoin/bitcoin-client.ts b/src/app/query/bitcoin/bitcoin-client.ts index 1629779f3ba..fa5ea9c7019 100644 --- a/src/app/query/bitcoin/bitcoin-client.ts +++ b/src/app/query/bitcoin/bitcoin-client.ts @@ -5,9 +5,8 @@ import { BESTINSLOT_API_BASE_URL_MAINNET, BESTINSLOT_API_BASE_URL_TESTNET, type BitcoinNetworkModes, - HIRO_API_BASE_URL_MAINNET, } from '@shared/constants'; -import { Paginated } from '@shared/models/api-types'; +import type { MarketData } from '@shared/models/market.model'; import type { Money } from '@shared/models/money.model'; import type { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; @@ -64,21 +63,17 @@ interface BestinslotInscriptionsByTxIdResponse { blockHeight: number; } -interface Brc20TokenResponse { +/* BRC-20 */ +interface Brc20Balance { ticker: string; overall_balance: string; available_balance: string; transferrable_balance: string; image_url: string | null; - min_listed_unit_price: number; + min_listed_unit_price: number | null; } -export interface Brc20Token extends Brc20TokenResponse { - decimals: number; - holderAddress: string; -} - -interface Brc20TokenTicker { +interface Brc20TickerInfo { id: string; number: number; block_height: number; @@ -93,16 +88,23 @@ interface Brc20TokenTicker { tx_count: number; } -interface Brc20TickerResponse { - data: Brc20TokenTicker; +interface Brc20TickerInfoResponse { block_height: number; + data: Brc20TickerInfo; } -interface BestinslotBrc20AddressBalanceResponse { +interface Brc20WalletBalancesResponse { block_height: number; - data: Brc20TokenResponse[]; + data: Brc20Balance[]; } +export interface Brc20Token extends Brc20Balance, Brc20TickerInfo { + balance: Money | null; + holderAddress: string; + marketData: MarketData | null; +} + +/* RUNES */ export interface RuneBalance { pkscript: string; rune_id: string; @@ -207,8 +209,9 @@ class BestinslotApi { return resp.data; } - async getBrc20Balance(address: string) { - const resp = await axios.get( + /* BRC-20 */ + async getBrc20Balances(address: string) { + const resp = await axios.get( `${this.url}/brc20/wallet_balances?address=${address}`, { ...this.defaultOptions, @@ -217,8 +220,8 @@ class BestinslotApi { return resp.data; } - async getBrc20TickerData(ticker: string) { - const resp = await axios.get( + async getBrc20TickerInfo(ticker: string) { + const resp = await axios.get( `${this.url}/brc20/ticker_info?ticker=${ticker}`, { ...this.defaultOptions, @@ -285,24 +288,6 @@ class BestinslotApi { } } -class HiroApi { - url = HIRO_API_BASE_URL_MAINNET; - - async getBrc20Balance(address: string) { - const resp = await axios.get>( - `${this.url}/ordinals/v1/brc-20/balances/${address}` - ); - return resp.data; - } - - async getBrc20TickerData(ticker: string) { - const resp = await axios.get>( - `${this.url}/ordinals/v1/brc-20/tokens?ticker=${ticker}` - ); - return resp.data; - } -} - class AddressApi { rateLimiter: PQueue; constructor(public configuration: Configuration) { @@ -421,7 +406,6 @@ export class BitcoinClient { feeEstimatesApi: FeeEstimatesApi; transactionsApi: TransactionsApi; BestinslotApi: BestinslotApi; - HiroApi: HiroApi; constructor(basePath: string) { this.configuration = new Configuration(basePath); @@ -429,6 +413,5 @@ export class BitcoinClient { this.feeEstimatesApi = new FeeEstimatesApi(this.configuration); this.transactionsApi = new TransactionsApi(this.configuration); this.BestinslotApi = new BestinslotApi(this.configuration); - this.HiroApi = new HiroApi(); } } diff --git a/src/app/query/bitcoin/ordinals/brc20/use-brc-20.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts similarity index 67% rename from src/app/query/bitcoin/ordinals/brc20/use-brc-20.ts rename to src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts index eca1e985e3a..c2db9af9c99 100644 --- a/src/app/query/bitcoin/ordinals/brc20/use-brc-20.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts @@ -1,9 +1,17 @@ +import BigNumber from 'bignumber.js'; + +import { createMarketData, createMarketPair } from '@shared/models/market.model'; +import { createMoney } from '@shared/models/money.model'; + +import { unitToFractionalUnit } from '@app/common/money/unit-conversion'; +import { useGetBrc20TokensQuery } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; import { useConfigOrdinalsbot } from '@app/query/common/remote-config/remote-config.query'; import { useAppDispatch } from '@app/store'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; import { brc20TransferInitiated } from '@app/store/ordinals/ordinals.slice'; +import type { Brc20Token } from '../../bitcoin-client'; import { useAverageBitcoinFeeRates } from '../../fees/fee-estimates.hooks'; import { useOrdinalsbotClient } from '../../ordinalsbot-client'; import { createBrc20TransferInscription, encodeBrc20TransferInscription } from './brc-20.utils'; @@ -72,3 +80,30 @@ export function useBrc20Transfers(holderAddress: string) { }, }; } + +function makeBrc20Token(token: Brc20Token) { + return { + ...token, + balance: createMoney( + unitToFractionalUnit(token.decimals)(new BigNumber(token.overall_balance)), + token.ticker, + token.decimals + ), + marketData: token.min_listed_unit_price + ? createMarketData( + createMarketPair(token.ticker, 'USD'), + createMoney(new BigNumber(token.min_listed_unit_price), 'USD') + ) + : null, + }; +} + +export function useBrc20Tokens() { + const { data: allBrc20TokensResponse } = useGetBrc20TokensQuery(); + const brc20Tokens = allBrc20TokensResponse?.pages + .flatMap(page => page.brc20Tokens) + .filter(token => token.length > 0) + .flatMap(token => token); + + return brc20Tokens?.map(token => makeBrc20Token(token)) ?? []; +} diff --git a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts index 4df6782d3ca..a4a7ff2feb0 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts @@ -51,19 +51,22 @@ export function useGetBrc20TokensQuery() { } const brc20TokensPromises = addressesData.map(async address => { - const brc20Tokens = await client.BestinslotApi.getBrc20Balance(address); + const brc20Tokens = await client.BestinslotApi.getBrc20Balances(address); const tickerPromises = await Promise.all( brc20Tokens.data.map(token => { - return client.BestinslotApi.getBrc20TickerData(token.ticker); + return client.BestinslotApi.getBrc20TickerInfo(token.ticker); }) ); + // Initialize token with ticker data return brc20Tokens.data.map((token, index) => { return { ...token, - decimals: tickerPromises[index].data.decimals, + ...tickerPromises[index].data, + balance: null, holderAddress: address, + marketData: null, }; }); }); diff --git a/src/app/query/common/alex-sdk/latest-prices.query.ts b/src/app/query/common/alex-sdk/alex-sdk-latest-prices.query.ts similarity index 100% rename from src/app/query/common/alex-sdk/latest-prices.query.ts rename to src/app/query/common/alex-sdk/alex-sdk-latest-prices.query.ts diff --git a/src/app/query/common/alex-sdk/alex-sdk-swappable-currency.query.ts b/src/app/query/common/alex-sdk/alex-sdk-swappable-currency.query.ts new file mode 100644 index 00000000000..ba62c6001cb --- /dev/null +++ b/src/app/query/common/alex-sdk/alex-sdk-swappable-currency.query.ts @@ -0,0 +1,25 @@ +import { useQuery } from '@tanstack/react-query'; +import type { TokenInfo } from 'alex-sdk'; + +import { alex } from '@shared/utils/alex-sdk'; + +import type { AppUseQueryConfig } from '@app/query/query-config'; + +const queryOptions = { + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryDelay: 1000 * 60, + staleTime: 1000 * 60 * 10, +}; + +export function useAlexSdkSwappableCurrencyQuery( + options?: AppUseQueryConfig +) { + return useQuery({ + queryKey: ['alex-sdk-swappable-currencies'], + queryFn: async () => alex.fetchSwappableCurrency(), + ...queryOptions, + ...options, + }); +} diff --git a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts index 5add887bfbf..f343a0dd3bb 100644 --- a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts +++ b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts @@ -1,35 +1,114 @@ import { useCallback } from 'react'; -import { type Currency } from 'alex-sdk'; +import { Currency, type TokenInfo } from 'alex-sdk'; import BigNumber from 'bignumber.js'; import { logger } from '@shared/logger'; -import { createMoney } from '@shared/models/money.model'; +import { type MarketData, createMarketData, createMarketPair } from '@shared/models/market.model'; +import { type Money, createMoney } from '@shared/models/money.model'; import { isDefined } from '@shared/utils'; +import { sortAssetsByName } from '@app/common/asset-utils'; +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 { useTransferableStacksFungibleTokenAssetBalances } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; +import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { getAvatarFallback } from '@app/ui/components/avatar/avatar'; -import { useAlexSdkLatestPricesQuery } from './latest-prices.query'; -import { useAlexSdkSwappableCurrencyQuery } from './swappable-currency.query'; +import { useAlexSdkLatestPricesQuery } from './alex-sdk-latest-prices.query'; +import { useAlexSdkSwappableCurrencyQuery } from './alex-sdk-swappable-currency.query'; -export function useAlexSdKCurrencyPriceAsMoney() { +export interface SwapAsset { + address?: string; + balance: Money; + currency: Currency; + displayName?: string; + fallback: string; + icon: string; + name: string; + marketData: MarketData | null; + principal: string; +} + +export const defaultSwapFee = createMoney(1000000, 'STX'); + +export function useAlexCurrencyPriceAsMarketData() { const { data: supportedCurrencies = [] } = useAlexSdkSwappableCurrencyQuery(); const { data: prices } = useAlexSdkLatestPricesQuery(); return useCallback( - (principal: string) => { - if (!prices) { - logger.error('Latest prices could not be found'); - return null; - } + (principal: string, symbol?: string) => { const tokenInfo = supportedCurrencies .filter(isDefined) .find(token => pullContractIdFromIdentity(token.contractAddress) === principal); - const currency = tokenInfo?.id as Currency; + if (!symbol || !prices || !tokenInfo) { + logger.error('Could not create market data'); + return null; + } + const currency = tokenInfo.id as Currency; const price = convertAmountToFractionalUnit(new BigNumber(prices[currency] ?? 0), 2); - return createMoney(price, 'USD'); + return createMarketData(createMarketPair(symbol, 'USD'), createMoney(price, 'USD')); }, [prices, supportedCurrencies] ); } + +function useMakeSwapAsset() { + const account = useCurrentStacksAccount(); + const { data: prices } = useAlexSdkLatestPricesQuery(); + const priceAsMarketData = useAlexCurrencyPriceAsMarketData(); + const { availableBalance: availableStxBalance } = useStxBalance(); + const stacksFtAssetBalances = useTransferableStacksFungibleTokenAssetBalances( + account?.address ?? '' + ); + + return useCallback( + (tokenInfo?: TokenInfo): SwapAsset | undefined => { + if (!prices) return; + if (!tokenInfo) { + logger.error('No token data found to swap'); + return; + } + + const currency = tokenInfo.id as Currency; + const fungibleTokenBalance = stacksFtAssetBalances.find( + balance => tokenInfo.contractAddress === balance.asset.contractId + )?.balance; + const principal = pullContractIdFromIdentity(tokenInfo.contractAddress); + + const swapAsset = { + currency, + fallback: getAvatarFallback(tokenInfo.name), + icon: tokenInfo.icon, + name: tokenInfo.name, + principal: pullContractIdFromIdentity(tokenInfo.contractAddress), + }; + + if (currency === Currency.STX) { + return { + ...swapAsset, + balance: availableStxBalance, + displayName: 'Stacks', + marketData: priceAsMarketData(principal, availableStxBalance.symbol), + }; + } + + return { + ...swapAsset, + balance: fungibleTokenBalance ?? createMoney(0, tokenInfo.name, tokenInfo.decimals), + marketData: fungibleTokenBalance + ? priceAsMarketData(principal, fungibleTokenBalance.symbol) + : priceAsMarketData(principal, tokenInfo.name), + }; + }, + [availableStxBalance, priceAsMarketData, prices, stacksFtAssetBalances] + ); +} + +export function useAlexSwappableAssets() { + const makeSwapAsset = useMakeSwapAsset(); + return useAlexSdkSwappableCurrencyQuery({ + select: resp => sortAssetsByName(resp.map(makeSwapAsset).filter(isDefined)), + }); +} diff --git a/src/app/query/common/alex-sdk/swappable-currency.query.ts b/src/app/query/common/alex-sdk/swappable-currency.query.ts deleted file mode 100644 index 792b60b603e..00000000000 --- a/src/app/query/common/alex-sdk/swappable-currency.query.ts +++ /dev/null @@ -1,13 +0,0 @@ -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/stacks/balance/stacks-ft-balances.hooks.ts b/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts index 1dcd2a36d37..b5feb7cec5a 100644 --- a/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts +++ b/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts @@ -7,7 +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 { useAlexCurrencyPriceAsMarketData } 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'; @@ -36,7 +36,7 @@ function useStacksFungibleTokenAssetBalances(address: string) { export function useStacksFungibleTokenAssetBalancesWithMetadata(address: string) { const { data: initializedAssetBalances = [] } = useStacksFungibleTokenAssetBalances(address); - const priceAsMoney = useAlexSdKCurrencyPriceAsMoney(); + const priceAsMarketData = useAlexCurrencyPriceAsMarketData(); const ftAssetsMetadata = useGetFungibleTokenMetadataListQuery( initializedAssetBalances.map(assetBalance => @@ -52,8 +52,9 @@ export function useStacksFungibleTokenAssetBalancesWithMetadata(address: string) return addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( assetBalance, metadata, - priceAsMoney( - formatContractId(assetBalance.asset.contractAddress, assetBalance.asset.contractName) + priceAsMarketData( + formatContractId(assetBalance.asset.contractAddress, assetBalance.asset.contractName), + metadata.symbol ) ); }), 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 159d0af3409..6d88699c766 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,8 @@ import type { StacksCryptoCurrencyAssetBalance, StacksFungibleTokenAssetBalance, } from '@shared/models/crypto-asset-balance.model'; -import { type Money, createMoney } from '@shared/models/money.model'; +import { type MarketData } from '@shared/models/market.model'; +import { 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,7 +48,7 @@ export function createStacksFtCryptoAssetBalanceTypeWrapper( hasMemo: false, imageCanonicalUri: '', name: '', - price: null, + marketData: null, symbol: '', }, }; @@ -70,7 +71,7 @@ export function convertFtBalancesToStacksFungibleTokenAssetBalanceType( export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( assetBalance: StacksFungibleTokenAssetBalance, metadata: FtMetadataResponse, - price: Money | null + marketData: MarketData | null ) { return { ...assetBalance, @@ -86,7 +87,7 @@ export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( hasMemo: isTransferableStacksFungibleTokenAsset(assetBalance.asset), imageCanonicalUri: metadata.image_canonical_uri ?? '', name: metadata.name ?? '', - price, + marketData, symbol: metadata.symbol ?? '', }, }; diff --git a/src/shared/models/crypto-asset.model.ts b/src/shared/models/crypto-asset.model.ts index c91dc8d6f5b..78f75dc8adb 100644 --- a/src/shared/models/crypto-asset.model.ts +++ b/src/shared/models/crypto-asset.model.ts @@ -1,4 +1,4 @@ -import type { Money } from './money.model'; +import type { MarketData } from './market.model'; export interface BitcoinCryptoCurrencyAsset { decimals: number; @@ -24,7 +24,7 @@ export interface StacksFungibleTokenAsset { hasMemo: boolean; imageCanonicalUri: string; name: string; - price: Money | null; + marketData: MarketData | null; symbol: string; } From 5264a99bcb9f84057db7f186930fc8aeb42fad6e Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:06:48 +0100 Subject: [PATCH 03/11] fix: show hash formatting, ref #5261 --- src/app/features/message-signer/hash-drawer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/features/message-signer/hash-drawer.tsx b/src/app/features/message-signer/hash-drawer.tsx index 8e1c439ed8f..245d4f9aaac 100644 --- a/src/app/features/message-signer/hash-drawer.tsx +++ b/src/app/features/message-signer/hash-drawer.tsx @@ -32,6 +32,7 @@ export function HashDrawer(props: HashDrawerProps) { }} type="button" width="100%" + display="flex" > {showHash ? 'Hide hash' : 'Show hash'} From 8735380b07adc5c5c502abe385ff0eca85af4efd Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:37:32 +0100 Subject: [PATCH 04/11] fix: prevent form submit on tooltip click, ref #5258 --- src/app/ui/components/containers/footers/available-balance.tsx | 2 +- src/app/ui/components/tooltip/basic-tooltip.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/ui/components/containers/footers/available-balance.tsx b/src/app/ui/components/containers/footers/available-balance.tsx index 37d9041d638..8d06f60e85e 100644 --- a/src/app/ui/components/containers/footers/available-balance.tsx +++ b/src/app/ui/components/containers/footers/available-balance.tsx @@ -18,7 +18,7 @@ export function AvailableBalance({ Available balance - + diff --git a/src/app/ui/components/tooltip/basic-tooltip.tsx b/src/app/ui/components/tooltip/basic-tooltip.tsx index 56fcead792f..e10e2d61616 100644 --- a/src/app/ui/components/tooltip/basic-tooltip.tsx +++ b/src/app/ui/components/tooltip/basic-tooltip.tsx @@ -11,6 +11,7 @@ interface BasicTooltipProps { side?: RadixTooltip.TooltipContentProps['side']; asChild?: boolean; } + export function BasicTooltip({ children, label, disabled, side, asChild }: BasicTooltipProps) { const isDisabled = !label || disabled; return ( From 51cf2c000a01f5b82661c388260d482aaf4e9760 Mon Sep 17 00:00:00 2001 From: alter-eggo Date: Mon, 22 Apr 2024 13:22:26 +0400 Subject: [PATCH 05/11] feat: increase hiro rate limiter --- src/app/query/stacks/hiro-rate-limiter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/query/stacks/hiro-rate-limiter.ts b/src/app/query/stacks/hiro-rate-limiter.ts index 97326b46708..8ca587591b9 100644 --- a/src/app/query/stacks/hiro-rate-limiter.ts +++ b/src/app/query/stacks/hiro-rate-limiter.ts @@ -6,8 +6,8 @@ import { whenStacksChainId } from '@shared/crypto/stacks/stacks.utils'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; const hiroStacksMainnetApiLimiter = new PQueue({ - interval: 5000, - intervalCap: 10, + interval: 60000, + intervalCap: 100, timeout: 60000, }); From a6f774c6950b8378942d5b196197c1eb22ba5e23 Mon Sep 17 00:00:00 2001 From: alter-eggo Date: Mon, 22 Apr 2024 13:36:57 +0400 Subject: [PATCH 06/11] fix: rpc send transfer params network validation, closes #5284 --- src/shared/rpc/methods/send-transfer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/rpc/methods/send-transfer.ts b/src/shared/rpc/methods/send-transfer.ts index bdb98e79cb6..80eecf2ecc2 100644 --- a/src/shared/rpc/methods/send-transfer.ts +++ b/src/shared/rpc/methods/send-transfer.ts @@ -26,7 +26,7 @@ export const rpcSendTransferParamsSchemaLegacy = yup.object().shape({ export const rpcSendTransferParamsSchema = yup.object().shape({ account: accountSchema, - network: yup.string().required().oneOf(Object.values(WalletDefaultNetworkConfigurationIds)), + network: yup.string().oneOf(Object.values(WalletDefaultNetworkConfigurationIds)), recipients: yup .array() .required() From 4410185f05b16b2bf5ddd4c4daf1bd7c5e09cc97 Mon Sep 17 00:00:00 2001 From: alter-eggo Date: Mon, 22 Apr 2024 15:59:48 +0400 Subject: [PATCH 07/11] fix: rpc send transfer recipient default network --- src/background/messaging/rpc-methods/send-transfer.ts | 3 ++- src/shared/rpc/methods/send-transfer.ts | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/background/messaging/rpc-methods/send-transfer.ts b/src/background/messaging/rpc-methods/send-transfer.ts index 90c860bf32d..5c4b8ce5853 100644 --- a/src/background/messaging/rpc-methods/send-transfer.ts +++ b/src/background/messaging/rpc-methods/send-transfer.ts @@ -5,6 +5,7 @@ import { type RpcSendTransferParams, type RpcSendTransferParamsLegacy, convertRpcSendTransferLegacyParamsToNew, + defaultRpcSendTransferNetwork, getRpcSendTransferParamErrors, validateRpcSendTransferLegacyParams, validateRpcSendTransferParams, @@ -63,7 +64,7 @@ export async function rpcSendTransfer( const requestParams: RequestParams = [ ...recipients, ...amounts, - ['network', params.network ?? 'mainnet'], + ['network', params.network ?? defaultRpcSendTransferNetwork], ['requestId', message.id], ]; diff --git a/src/shared/rpc/methods/send-transfer.ts b/src/shared/rpc/methods/send-transfer.ts index 80eecf2ecc2..d1c48419bc8 100644 --- a/src/shared/rpc/methods/send-transfer.ts +++ b/src/shared/rpc/methods/send-transfer.ts @@ -17,6 +17,8 @@ import { validateRpcParams, } from './validation.utils'; +export const defaultRpcSendTransferNetwork = 'mainnet'; + export const rpcSendTransferParamsSchemaLegacy = yup.object().shape({ account: accountSchema, address: yup.string().required(), @@ -38,7 +40,9 @@ export const rpcSendTransferParamsSchema = yup.object().shape({ FormErrorMessages.IncorrectNetworkAddress, (value, context) => { const contextOptions = context.options as any; - const network = contextOptions.from[1].value.network as BitcoinNetworkModes; + const network = + (contextOptions.from[1].value.network as BitcoinNetworkModes) || + defaultRpcSendTransferNetwork; return btcAddressNetworkValidator(network).isValidSync(value); } ), From b0276ec76e89af51b113a4ff320715874f88e789 Mon Sep 17 00:00:00 2001 From: alter-eggo Date: Tue, 23 Apr 2024 12:19:26 +0400 Subject: [PATCH 08/11] feat: decrease num of addresses to fetch --- src/app/query/bitcoin/address/utxos-by-address.query.ts | 2 +- src/app/query/bitcoin/blockstream-rate-limiter.ts | 2 +- src/app/query/bitcoin/ordinals/inscriptions.query.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/query/bitcoin/address/utxos-by-address.query.ts b/src/app/query/bitcoin/address/utxos-by-address.query.ts index a47ce189354..3ac6ca3ca13 100644 --- a/src/app/query/bitcoin/address/utxos-by-address.query.ts +++ b/src/app/query/bitcoin/address/utxos-by-address.query.ts @@ -35,7 +35,7 @@ export function useGetUtxosByAddressQuery Date: Tue, 23 Apr 2024 20:36:08 +0400 Subject: [PATCH 09/11] fix: problem with endless loading of balances and names --- .../common/hooks/account/use-account-names.ts | 25 ++++++++++--------- .../hooks/balance/btc/use-btc-balance.ts | 4 ++- .../hooks/balance/use-total-balance.tsx | 11 +++++++- src/app/components/account-total-balance.tsx | 4 +-- .../stacks/stacks-crypto-assets.tsx | 1 + .../components/switch-account-list-item.tsx | 4 +-- .../choose-account/components/accounts.tsx | 3 ++- src/app/pages/home/home.tsx | 4 +-- .../account-list-item.tsx | 2 +- .../bitcoin/address/utxos-by-address.hooks.ts | 7 +----- .../bitcoin/balance/btc-balance.hooks.ts | 2 ++ .../btc-native-segwit-balance.hooks.ts | 4 ++- src/app/query/stacks/bns/bns.hooks.ts | 4 --- .../account/account.card.stories.tsx | 6 ++--- .../ui/components/account/account.card.tsx | 6 ++--- 15 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/app/common/hooks/account/use-account-names.ts b/src/app/common/hooks/account/use-account-names.ts index 71614800024..f556a58a3db 100644 --- a/src/app/common/hooks/account/use-account-names.ts +++ b/src/app/common/hooks/account/use-account-names.ts @@ -4,10 +4,8 @@ import { isUndefined } from '@shared/utils'; import { parseIfValidPunycode } from '@app/common/utils'; import { getAutogeneratedAccountDisplayName } from '@app/common/utils/get-account-display-name'; -import { - useCurrentAccountNames, - useGetAccountNamesByAddressQuery, -} from '@app/query/stacks/bns/bns.hooks'; +import { useCurrentAccountNames } from '@app/query/stacks/bns/bns.hooks'; +import { useGetBnsNamesOwnedByAddress } from '@app/query/stacks/bns/bns.query'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; export function useCurrentAccountDisplayName() { @@ -23,12 +21,15 @@ export function useCurrentAccountDisplayName() { } export function useAccountDisplayName({ address, index }: { index: number; address: string }) { - const { data: names = [], isLoading } = useGetAccountNamesByAddressQuery(address); - return useMemo(() => { - const name = names[0] || getAutogeneratedAccountDisplayName(index); - return { - name, - isLoading, - }; - }, [names, index, isLoading]); + const query = useGetBnsNamesOwnedByAddress(address, { + select: resp => { + const names = resp.names ?? []; + return names[0] || getAutogeneratedAccountDisplayName(index); + }, + }); + + return { + ...query, + data: query.data || getAutogeneratedAccountDisplayName(index), + }; } diff --git a/src/app/common/hooks/balance/btc/use-btc-balance.ts b/src/app/common/hooks/balance/btc/use-btc-balance.ts index 3012db712a8..2965ab84e16 100644 --- a/src/app/common/hooks/balance/btc/use-btc-balance.ts +++ b/src/app/common/hooks/balance/btc/use-btc-balance.ts @@ -11,6 +11,7 @@ export function useBtcAssetBalance(btcAddress: string) { const { btcBalance: btcAssetBalance, isLoading, + isFetching, isInitialLoading, } = useNativeSegwitBalance(btcAddress); @@ -28,8 +29,9 @@ export function useBtcAssetBalance(btcAddress: string) { baseCurrencyAmountInQuote(btcAssetBalance.balance, btcMarketData) ), isLoading, + isFetching, isInitialLoading, }), - [btcAddress, btcAssetBalance, btcMarketData, isLoading, isInitialLoading] + [btcAddress, btcAssetBalance, btcMarketData, isLoading, isInitialLoading, isFetching] ); } diff --git a/src/app/common/hooks/balance/use-total-balance.tsx b/src/app/common/hooks/balance/use-total-balance.tsx index 91b54228fc3..107dfa4e0f0 100644 --- a/src/app/common/hooks/balance/use-total-balance.tsx +++ b/src/app/common/hooks/balance/use-total-balance.tsx @@ -20,13 +20,19 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) const stxMarketData = useCryptoCurrencyMarketDataMeanAverage('STX'); // get stx balance - const { data: balances, isLoading, isInitialLoading } = useStacksAccountBalances(stxAddress); + const { + data: balances, + isLoading, + isInitialLoading, + isFetching: isFetchingStacksBalance, + } = useStacksAccountBalances(stxAddress); const stxBalance = balances ? balances.stx.balance : createMoney(0, 'STX'); // get btc balance const { btcAvailableAssetBalance, isLoading: isLoadingBtcBalance, + isFetching: isFetchingBtcBalance, isInitialLoading: isInititalLoadingBtcBalance, } = useBtcAssetBalance(btcAddress); @@ -44,6 +50,7 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) ), isLoading: isLoading || isLoadingBtcBalance, isInitialLoading: isInitialLoading || isInititalLoadingBtcBalance, + isFetching: isFetchingStacksBalance || isFetchingBtcBalance, }; }, [ btcAvailableAssetBalance.balance, @@ -54,5 +61,7 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) stxBalance, isLoading, isLoadingBtcBalance, + isFetchingStacksBalance, + isFetchingBtcBalance, ]); } diff --git a/src/app/components/account-total-balance.tsx b/src/app/components/account-total-balance.tsx index 1e7cfcf6dac..39138230f2b 100644 --- a/src/app/components/account-total-balance.tsx +++ b/src/app/components/account-total-balance.tsx @@ -13,7 +13,7 @@ interface AccountTotalBalanceProps { } export const AccountTotalBalance = memo(({ btcAddress, stxAddress }: AccountTotalBalanceProps) => { - const { totalUsdBalance, isLoading, isInitialLoading } = useTotalBalance({ + const { totalUsdBalance, isFetching, isInitialLoading } = useTotalBalance({ btcAddress, stxAddress, }); @@ -25,7 +25,7 @@ export const AccountTotalBalance = memo(({ btcAddress, stxAddress }: AccountTota {totalUsdBalance} diff --git a/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx b/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx index 77dab15147f..ffbb6691fe7 100644 --- a/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx +++ b/src/app/features/collectibles/components/stacks/stacks-crypto-assets.tsx @@ -14,6 +14,7 @@ interface StacksCryptoAssetsProps { } export function StacksCryptoAssets({ account }: StacksCryptoAssetsProps) { const { data: names = [] } = useCurrentAccountNames(); + const stacksNftsMetadataResp = useStacksNonFungibleTokensMetadata(account); const analytics = useAnalytics(); diff --git a/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx b/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx index 323882e01b3..c04ab6a1814 100644 --- a/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx +++ b/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx @@ -27,7 +27,7 @@ export const SwitchAccountListItem = memo( 'SWITCH_ACCOUNTS' + stxAddress || btcAddress ); const { handleSwitchAccount } = useSwitchAccount(handleClose); - const { name, isLoading: isLoadingBnsName } = useAccountDisplayName({ + const { data: name = '', isFetching: isFetchingBnsName } = useAccountDisplayName({ address: stxAddress, index, }); @@ -43,7 +43,7 @@ export const SwitchAccountListItem = memo( return ( } - accountName={{name}} + accountName={{name}} avatar={ { - const { name } = useAccountDisplayName(account); + const { data: name = '' } = useAccountDisplayName(account); + const btcAddress = useNativeSegwitAccountIndexAddressIndexZero(account.index); const accountSlug = useMemo(() => slugify(`Account ${account?.index + 1}`), [account?.index]); diff --git a/src/app/pages/home/home.tsx b/src/app/pages/home/home.tsx index 3c2c1225466..6c8f90ef9c1 100644 --- a/src/app/pages/home/home.tsx +++ b/src/app/pages/home/home.tsx @@ -28,7 +28,7 @@ export function Home() { const navigate = useNavigate(); const account = useCurrentStacksAccount(); - const { name, isLoading: isLoadingBnsName } = useAccountDisplayName({ + const { data: name = '', isFetching: isFetchingBnsName } = useAccountDisplayName({ address: account?.address || '', index: account?.index || 0, }); @@ -56,7 +56,7 @@ export function Home() { /> } toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} - isLoadingBnsName={isLoadingBnsName} + isFetchingBnsName={isFetchingBnsName} isLoadingBalance={isInitialLoading} > diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/account-list-item.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/account-list-item.tsx index 08da077101a..8176634ca92 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/account-list-item.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/account-list-item.tsx @@ -23,7 +23,7 @@ export const AccountListItem = memo(({ index, stacksAccount, onClose }: AccountL BitcoinSendFormValues | StacksSendFormValues >(); const stacksAddress = stacksAccount?.address || ''; - const { name } = useAccountDisplayName({ address: stacksAddress, index }); + const { data: name = '' } = useAccountDisplayName({ address: stacksAddress, index }); const bitcoinSigner = useNativeSegwitSigner(index); const bitcoinAddress = bitcoinSigner?.(0).address || ''; diff --git a/src/app/query/bitcoin/address/utxos-by-address.hooks.ts b/src/app/query/bitcoin/address/utxos-by-address.hooks.ts index 40567d97db6..05a15ecf1a5 100644 --- a/src/app/query/bitcoin/address/utxos-by-address.hooks.ts +++ b/src/app/query/bitcoin/address/utxos-by-address.hooks.ts @@ -117,21 +117,16 @@ function useFilterInscriptionsByAddress(address: string) { const { data: inscriptionsList, hasNextPage: hasMoreInscriptionsToLoad, - isLoading: isLoadingInscriptions, isInitialLoading: isInitialLoadingInscriptions, } = useInscriptionsByAddressQuery(address); const filterOutInscriptions = useCallback( (utxos: UtxoResponseItem[]) => { - // While infinite query checks if has more data to load, or Stamps - // are loading, assume nothing is spendable - if (hasMoreInscriptionsToLoad || isLoadingInscriptions) return []; - const inscriptions = inscriptionsList?.pages.flatMap(page => page.results) ?? []; return filterUtxosWithInscriptions(inscriptions, utxos); }, - [hasMoreInscriptionsToLoad, inscriptionsList?.pages, isLoadingInscriptions] + [inscriptionsList?.pages] ); return { diff --git a/src/app/query/bitcoin/balance/btc-balance.hooks.ts b/src/app/query/bitcoin/balance/btc-balance.hooks.ts index c5deba04ffb..d7b5f85a609 100644 --- a/src/app/query/bitcoin/balance/btc-balance.hooks.ts +++ b/src/app/query/bitcoin/balance/btc-balance.hooks.ts @@ -17,6 +17,7 @@ export function useGetBitcoinBalanceByAddress(address: string) { data: utxos, isInitialLoading, isLoading, + isFetching, } = useNativeSegwitUtxosByAddress({ address, filterInscriptionUtxos: true, @@ -33,5 +34,6 @@ export function useGetBitcoinBalanceByAddress(address: string) { balance, isInitialLoading, isLoading, + isFetching, }; } diff --git a/src/app/query/bitcoin/balance/btc-native-segwit-balance.hooks.ts b/src/app/query/bitcoin/balance/btc-native-segwit-balance.hooks.ts index acc34936cc9..594c21f8feb 100644 --- a/src/app/query/bitcoin/balance/btc-native-segwit-balance.hooks.ts +++ b/src/app/query/bitcoin/balance/btc-native-segwit-balance.hooks.ts @@ -7,7 +7,8 @@ import { useGetBitcoinBalanceByAddress } from './btc-balance.hooks'; // Balance is derived from a single query in address reuse mode export function useNativeSegwitBalance(address: string) { - const { balance, isInitialLoading, isLoading } = useGetBitcoinBalanceByAddress(address); + const { balance, isInitialLoading, isLoading, isFetching } = + useGetBitcoinBalanceByAddress(address); const wrappedBalance = useMemo( () => createBitcoinCryptoCurrencyAssetTypeWrapper(balance), @@ -18,6 +19,7 @@ export function useNativeSegwitBalance(address: string) { btcBalance: wrappedBalance, isInitialLoading, isLoading, + isFetching, }; } diff --git a/src/app/query/stacks/bns/bns.hooks.ts b/src/app/query/stacks/bns/bns.hooks.ts index bddf757f089..d0aea0e3fc6 100644 --- a/src/app/query/stacks/bns/bns.hooks.ts +++ b/src/app/query/stacks/bns/bns.hooks.ts @@ -13,7 +13,3 @@ export function useCurrentAccountNames() { }, }); } - -export function useGetAccountNamesByAddressQuery(address: string) { - return useGetBnsNamesOwnedByAddress(address, { select: resp => resp.names ?? [] }); -} diff --git a/src/app/ui/components/account/account.card.stories.tsx b/src/app/ui/components/account/account.card.stories.tsx index dac42307e68..6f5e7ef3c97 100644 --- a/src/app/ui/components/account/account.card.stories.tsx +++ b/src/app/ui/components/account/account.card.stories.tsx @@ -22,7 +22,7 @@ export function AccountCard() { switchAccount={<>} toggleSwitchAccount={() => null} isLoadingBalance={false} - isLoadingBnsName={false} + isFetchingBnsName={false} > } label="Send" /> @@ -42,7 +42,7 @@ export function AccountCardLoading() { switchAccount={<>} toggleSwitchAccount={() => null} isLoadingBalance - isLoadingBnsName={false} + isFetchingBnsName={false} > } label="Send" /> @@ -62,7 +62,7 @@ export function AccountCardBnsNameLoading() { switchAccount={<>} toggleSwitchAccount={() => null} isLoadingBalance={false} - isLoadingBnsName + isFetchingBnsName > } label="Send" /> diff --git a/src/app/ui/components/account/account.card.tsx b/src/app/ui/components/account/account.card.tsx index 32565fd937b..c3e090372f0 100644 --- a/src/app/ui/components/account/account.card.tsx +++ b/src/app/ui/components/account/account.card.tsx @@ -14,7 +14,7 @@ interface AccountCardProps { children: ReactNode; switchAccount: ReactNode; toggleSwitchAccount(): void; - isLoadingBnsName: boolean; + isFetchingBnsName: boolean; isLoadingBalance: boolean; } @@ -24,7 +24,7 @@ export function AccountCard({ switchAccount, toggleSwitchAccount, children, - isLoadingBnsName, + isFetchingBnsName, isLoadingBalance, }: AccountCardProps) { return ( @@ -44,7 +44,7 @@ export function AccountCard({ > From 5d618aab9bad8892976f132c869e296280e0cf2d Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:41:11 +0100 Subject: [PATCH 10/11] fix: allow virtuoso to resize dynamically, ref #5242 --- src/app/common/hooks/use-media-query.ts | 23 +++++++++ src/app/features/container/container.tsx | 12 +++-- .../switch-account-dialog.tsx | 50 ++++++++++--------- .../message-signer/message-preview-box.tsx | 4 +- .../pages/choose-account/choose-account.tsx | 17 +++++-- .../choose-account/components/accounts.tsx | 49 ++++++++---------- src/app/pages/home/home.tsx | 15 ++---- .../recipient-accounts-dialog.tsx | 46 +++++++++-------- .../account/account.card.stories.tsx | 3 -- .../ui/components/account/account.card.tsx | 3 -- .../components/containers/dialog/dialog.tsx | 36 ++++++++----- src/app/ui/components/virtuoso.tsx | 45 +++++++++++++++++ src/app/ui/shared/virtuoso.ts | 8 --- 13 files changed, 190 insertions(+), 121 deletions(-) create mode 100644 src/app/common/hooks/use-media-query.ts create mode 100644 src/app/ui/components/virtuoso.tsx delete mode 100644 src/app/ui/shared/virtuoso.ts diff --git a/src/app/common/hooks/use-media-query.ts b/src/app/common/hooks/use-media-query.ts new file mode 100644 index 00000000000..1d9249726cc --- /dev/null +++ b/src/app/common/hooks/use-media-query.ts @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +import { BreakpointToken, token } from 'leather-styles/tokens'; + +function useMediaQuery(query: string) { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + if (media.matches !== matches) { + setMatches(media.matches); + } + const listener = () => setMatches(media.matches); + window.addEventListener('resize', listener); + return () => window.removeEventListener('resize', listener); + }, [matches, query]); + + return matches; +} + +export function useViewportMinWidth(viewport: BreakpointToken) { + return useMediaQuery(`(min-width: ${token(`breakpoints.${viewport}`)})`); +} diff --git a/src/app/features/container/container.tsx b/src/app/features/container/container.tsx index 4379f003915..481904699fa 100644 --- a/src/app/features/container/container.tsx +++ b/src/app/features/container/container.tsx @@ -97,10 +97,12 @@ export function Container() { const isLogoClickable = variant !== 'home' && !isRpcRoute(pathname); return ( <> - setIsShowingSwitchAccount(false)} - /> + {isShowingSwitchAccount && ( + setIsShowingSwitchAccount(false)} + /> + )} - + ); diff --git a/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx index 2ac6b1e0478..1b0e7bdafdc 100644 --- a/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx +++ b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx @@ -8,22 +8,24 @@ import { useWalletType } from '@app/common/use-wallet-type'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { useHasLedgerKeys } from '@app/store/ledger/ledger.selectors'; import { Button } from '@app/ui/components/button/button'; -import { Dialog, DialogProps, getHeightOffset } from '@app/ui/components/containers/dialog/dialog'; +import { Dialog, DialogProps } from '@app/ui/components/containers/dialog/dialog'; import { Footer } from '@app/ui/components/containers/footers/footer'; import { Header } from '@app/ui/components/containers/headers/header'; -import { virtuosoHeight, virtuosoStyles } from '@app/ui/shared/virtuoso'; +import { VirtuosoWrapper } from '@app/ui/components/virtuoso'; import { AccountListUnavailable } from './components/account-list-unavailable'; import { SwitchAccountListItem } from './components/switch-account-list-item'; +export interface SwitchAccountOutletContext { + isShowingSwitchAccount: boolean; + setIsShowingSwitchAccount(isShowing: boolean): void; +} + export const SwitchAccountDialog = memo(({ isShowing, onClose }: DialogProps) => { const currentAccountIndex = useCurrentAccountIndex(); const createAccount = useCreateAccount(); const { whenWallet } = useWalletType(); - const isLedger = useHasLedgerKeys(); - const stacksAccounts = useStacksAccounts(); const bitcoinAccounts = useFilteredBitcoinAccounts(); const btcAddressesNum = bitcoinAccounts.length / 2; @@ -43,13 +45,13 @@ export const SwitchAccountDialog = memo(({ isShowing, onClose }: DialogProps) => if (!isShowing) return null; const accountNum = stacksAddressesNum || btcAddressesNum; - const maxAccountsShown = accountNum > 10 ? 10 : accountNum; return ( } isShowing={isShowing} onClose={onClose} + wrapChildren={false} footer={whenWallet({ software: (
@@ -61,24 +63,24 @@ export const SwitchAccountDialog = memo(({ isShowing, onClose }: DialogProps) => ledger: null, })} > - ( - - - - )} - /> + + ( + + + + )} + /> +
); }); diff --git a/src/app/features/message-signer/message-preview-box.tsx b/src/app/features/message-signer/message-preview-box.tsx index 32009cb5745..1d0c346a9a3 100644 --- a/src/app/features/message-signer/message-preview-box.tsx +++ b/src/app/features/message-signer/message-preview-box.tsx @@ -17,8 +17,8 @@ export function MessagePreviewBox({ message, hash }: MessageBoxProps) { py="space.05" overflowX="auto" > - {message.split(/\r?\n/).map(line => ( - + {message.split(/\r?\n/).map((line, index) => ( + {line} ))} diff --git a/src/app/pages/choose-account/choose-account.tsx b/src/app/pages/choose-account/choose-account.tsx index 41fa8bfe5ca..8552da7767c 100644 --- a/src/app/pages/choose-account/choose-account.tsx +++ b/src/app/pages/choose-account/choose-account.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect } from 'react'; +import { useEffect } from 'react'; import { Outlet } from 'react-router-dom'; import { Flex, Stack, styled } from 'leather-styles/jsx'; @@ -10,10 +10,13 @@ import { useAppDetails } from '@app/common/hooks/auth/use-app-details'; import { RequesterFlag } from '@app/components/requester-flag'; import { ChooseAccountsList } from '@app/pages/choose-account/components/accounts'; import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; +import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { LogomarkIcon } from '@app/ui/icons/logomark-icon'; -export const ChooseAccount = memo(() => { +export function ChooseAccount() { const { url } = useAppDetails(); + const accounts = useStacksAccounts(); + const hasConnectedStacksAccounts = accounts.length > 0; const cancelAuthentication = useCancelAuthRequest(); @@ -34,12 +37,16 @@ export const ChooseAccount = memo(() => { {url && } - Choose an account to connect + + {hasConnectedStacksAccounts + ? 'Choose an account to connect' + : 'No connected accounts found'} +
- + {hasConnectedStacksAccounts && } ); -}); +} diff --git a/src/app/pages/choose-account/components/accounts.tsx b/src/app/pages/choose-account/components/accounts.tsx index 1ee2b70dfcc..ab9f90b0fc7 100644 --- a/src/app/pages/choose-account/components/accounts.tsx +++ b/src/app/pages/choose-account/components/accounts.tsx @@ -20,8 +20,8 @@ import { useNativeSegwitAccountIndexAddressIndexZero } from '@app/store/accounts import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; import { AccountAvatar } from '@app/ui/components/account/account-avatar/account-avatar'; +import { VirtuosoWrapper } from '@app/ui/components/virtuoso'; import { PlusIcon } from '@app/ui/icons/plus-icon'; -import { virtuosoHeight, virtuosoStyles } from '@app/ui/shared/virtuoso'; interface AccountTitlePlaceholderProps { account: StacksAccount; @@ -44,7 +44,6 @@ const ChooseAccountItem = memo( const btcAddress = useNativeSegwitAccountIndexAddressIndexZero(account.index); const accountSlug = useMemo(() => slugify(`Account ${account?.index + 1}`), [account?.index]); - return ( } @@ -72,7 +71,7 @@ const ChooseAccountItem = memo( } ); -const AddAccountAction = memo(() => { +function AddAccountAction() { const [component, bind] = usePressable(true); const createAccount = useCreateAccount(); @@ -89,9 +88,9 @@ const AddAccountAction = memo(() => { {component} ); -}); +} -export const ChooseAccountsList = memo(() => { +export function ChooseAccountsList() { const finishSignIn = useFinishAuthRequest(); const { whenWallet } = useWalletType(); const accounts = useStacksAccounts(); @@ -111,31 +110,27 @@ export const ChooseAccountsList = memo(() => { }; if (!accounts) return null; - const accountNum = accounts.length; - - const maxAccountsShown = accountNum > 10 ? 10 : accountNum; return ( {whenWallet({ software: , ledger: <> })} - ( - - - - )} - /> + + ( + + + + )} + /> + ); -}); +} diff --git a/src/app/pages/home/home.tsx b/src/app/pages/home/home.tsx index 6c8f90ef9c1..9bf3b5b3ed1 100644 --- a/src/app/pages/home/home.tsx +++ b/src/app/pages/home/home.tsx @@ -1,5 +1,4 @@ -import { useState } from 'react'; -import { Route, useNavigate } from 'react-router-dom'; +import { Route, useNavigate, useOutletContext } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; @@ -9,7 +8,7 @@ import { useTotalBalance } from '@app/common/hooks/balance/use-total-balance'; import { useOnMount } from '@app/common/hooks/use-on-mount'; import { ActivityList } from '@app/features/activity-list/activity-list'; import { AssetsList } from '@app/features/asset-list/asset-list'; -import { SwitchAccountDialog } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog'; +import { SwitchAccountOutletContext } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog'; import { FeedbackButton } from '@app/features/feedback-button/feedback-button'; import { homePageModalRoutes } from '@app/routes/app-routes'; import { ModalBackgroundWrapper } from '@app/routes/components/modal-background-wrapper'; @@ -22,9 +21,9 @@ import { AccountActions } from './components/account-actions'; import { HomeTabs } from './components/home-tabs'; export function Home() { - const [isShowingSwitchAccount, setIsShowingSwitchAccount] = useState(false); const { decodedAuthRequest } = useOnboardingState(); - + const { isShowingSwitchAccount, setIsShowingSwitchAccount } = + useOutletContext(); const navigate = useNavigate(); const account = useCurrentStacksAccount(); @@ -49,12 +48,6 @@ export function Home() { setIsShowingSwitchAccount(false)} - /> - } toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} isFetchingBnsName={isFetchingBnsName} isLoadingBalance={isInitialLoading} diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx index 00b2f3e9bb1..9ec21d9831f 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx @@ -6,9 +6,9 @@ import { Box } from 'leather-styles/jsx'; import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { Dialog, getHeightOffset } from '@app/ui/components/containers/dialog/dialog'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; import { Header } from '@app/ui/components/containers/headers/header'; -import { virtuosoHeight, virtuosoStyles } from '@app/ui/shared/virtuoso'; +import { VirtuosoWrapper } from '@app/ui/components/virtuoso'; import { AccountListItem } from './account-list-item'; @@ -23,27 +23,31 @@ export function RecipientAccountsDialog() { if (stacksAddressesNum === 0 && btcAddressesNum === 0) return null; const accountNum = stacksAddressesNum || btcAddressesNum; - const maxAccountsShown = accountNum > 10 ? 10 : accountNum; return ( - } isShowing onClose={onGoBack}> - ( - - - - )} - totalCount={stacksAddressesNum || btcAddressesNum} - /> + } + isShowing + onClose={onGoBack} + wrapChildren={false} + > + + ( + + + + )} + totalCount={accountNum} + /> + ); } diff --git a/src/app/ui/components/account/account.card.stories.tsx b/src/app/ui/components/account/account.card.stories.tsx index 6f5e7ef3c97..30a6e1b70ab 100644 --- a/src/app/ui/components/account/account.card.stories.tsx +++ b/src/app/ui/components/account/account.card.stories.tsx @@ -19,7 +19,6 @@ export function AccountCard() { } toggleSwitchAccount={() => null} isLoadingBalance={false} isFetchingBnsName={false} @@ -39,7 +38,6 @@ export function AccountCardLoading() { } toggleSwitchAccount={() => null} isLoadingBalance isFetchingBnsName={false} @@ -59,7 +57,6 @@ export function AccountCardBnsNameLoading() { } toggleSwitchAccount={() => null} isLoadingBalance={false} isFetchingBnsName diff --git a/src/app/ui/components/account/account.card.tsx b/src/app/ui/components/account/account.card.tsx index c3e090372f0..aba2c75a980 100644 --- a/src/app/ui/components/account/account.card.tsx +++ b/src/app/ui/components/account/account.card.tsx @@ -12,7 +12,6 @@ interface AccountCardProps { name: string; balance: string; children: ReactNode; - switchAccount: ReactNode; toggleSwitchAccount(): void; isFetchingBnsName: boolean; isLoadingBalance: boolean; @@ -21,7 +20,6 @@ interface AccountCardProps { export function AccountCard({ name, balance, - switchAccount, toggleSwitchAccount, children, isFetchingBnsName, @@ -62,7 +60,6 @@ export function AccountCard({ {balance} - {switchAccount} {children} diff --git a/src/app/ui/components/containers/dialog/dialog.tsx b/src/app/ui/components/containers/dialog/dialog.tsx index 980b5baf8ba..be96b35cc7f 100644 --- a/src/app/ui/components/containers/dialog/dialog.tsx +++ b/src/app/ui/components/containers/dialog/dialog.tsx @@ -16,9 +16,10 @@ interface RadixDialogProps extends DialogProps { footer?: ReactNode; header?: ReactElement>; onGoBack?(): void; + wrapChildren?: boolean; } -export function getHeightOffset(header: ReactNode, footer: ReactNode) { +function getHeightOffset(header: ReactNode, footer: ReactNode) { const headerHeight = header ? pxStringToNumber(token('sizes.headerHeight')) : 0; const footerHeight = footer ? pxStringToNumber(token('sizes.footerHeight')) : 0; return headerHeight + footerHeight; @@ -30,7 +31,14 @@ function getContentMaxHeight(maxHeightOffset: number) { return `calc(${virtualHeight}vh - ${maxHeightOffset}px)`; } -export function Dialog({ children, footer, header, onClose, isShowing }: RadixDialogProps) { +export function Dialog({ + children, + footer, + header, + onClose, + isShowing, + wrapChildren = true, +}: RadixDialogProps) { if (!isShowing) return null; const maxHeightOffset = getHeightOffset(header, footer); @@ -68,16 +76,20 @@ export function Dialog({ children, footer, header, onClose, isShowing }: RadixDi > {header && cloneElement(header, { onClose })} - - {children} - + {wrapChildren ? ( + + {children} + + ) : ( + children + )} {footer} diff --git a/src/app/ui/components/virtuoso.tsx b/src/app/ui/components/virtuoso.tsx new file mode 100644 index 00000000000..b87aebbc45f --- /dev/null +++ b/src/app/ui/components/virtuoso.tsx @@ -0,0 +1,45 @@ +import { ReactNode, useState } from 'react'; + +import { Box } from 'leather-styles/jsx'; + +import { useViewportMinWidth } from '@app/common/hooks/use-media-query'; +import { useOnResizeListener } from '@app/common/hooks/use-on-resize-listener'; + +interface VirtuosoWrapper { + children: ReactNode; + hasFooter?: boolean; + isPopup?: boolean; +} + +function vhToPixels(vh: string) { + const numericHeight = +vh.replace('vh', ''); + return (numericHeight * window.innerHeight) / 100; +} + +export function VirtuosoWrapper({ children, hasFooter, isPopup }: VirtuosoWrapper) { + const [key, setKey] = useState(0); + const isAtLeastMd = useViewportMinWidth('md'); + const virtualHeight = isAtLeastMd ? '70vh' : '100vh'; + const headerHeight = isPopup ? 230 : 80; + const footerHeight = hasFooter ? 95 : 0; + const heightOffset = headerHeight + footerHeight; + const height = vhToPixels(virtualHeight) - heightOffset; + + const onResize = () => { + setKey(Date.now()); + }; + + useOnResizeListener(onResize); + return ( + + {children} + + ); +} diff --git a/src/app/ui/shared/virtuoso.ts b/src/app/ui/shared/virtuoso.ts deleted file mode 100644 index ce1dc191264..00000000000 --- a/src/app/ui/shared/virtuoso.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { token } from 'leather-styles/tokens'; - -// virtuosoHeight = calc(InteractiveItem height - negative margin) 72px - 24px = 58 -export const virtuosoHeight = 58; - -export const virtuosoStyles = { - paddingBottom: token('spacing.space.05'), -}; From b4d73f83692129927e39df3c9a0f13020fcbbb21 Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Wed, 24 Apr 2024 08:36:36 -0500 Subject: [PATCH 11/11] refactor: remove unneeded logger errors --- src/app/components/loaders/runes-loader.tsx | 4 +--- src/app/query/bitcoin/runes/runes.hooks.ts | 16 +++++++--------- src/app/query/common/alex-sdk/alex-sdk.hooks.ts | 5 +---- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/app/components/loaders/runes-loader.tsx b/src/app/components/loaders/runes-loader.tsx index 18e81e21d15..ee78d0287e8 100644 --- a/src/app/components/loaders/runes-loader.tsx +++ b/src/app/components/loaders/runes-loader.tsx @@ -1,5 +1,3 @@ -import { isDefined } from '@shared/utils'; - import type { RuneToken } from '@app/query/bitcoin/bitcoin-client'; import { useRuneTokens } from '@app/query/bitcoin/runes/runes.hooks'; @@ -9,5 +7,5 @@ interface RunesLoaderProps { } export function RunesLoader({ addresses, children }: RunesLoaderProps) { const runes = useRuneTokens(addresses); - return children(runes.filter(isDefined)); + return children(runes); } diff --git a/src/app/query/bitcoin/runes/runes.hooks.ts b/src/app/query/bitcoin/runes/runes.hooks.ts index 1ea8030e261..7e1cff6e0d8 100644 --- a/src/app/query/bitcoin/runes/runes.hooks.ts +++ b/src/app/query/bitcoin/runes/runes.hooks.ts @@ -1,4 +1,3 @@ -import { logger } from '@shared/logger'; import { createMoney } from '@shared/models/money.model'; import { isDefined } from '@shared/utils'; @@ -41,14 +40,13 @@ export function useRuneTokens(addresses: string[]) { .flatMap(query => query.data) .filter(isDefined); - return runesBalances.map(r => { - const tickerInfo = runesTickerInfo.find(t => t.rune_name === r.rune_name); - if (!tickerInfo) { - logger.error('No ticker info found for Rune'); - return; - } - return makeRuneToken(r, tickerInfo); - }); + return runesBalances + .map(r => { + const tickerInfo = runesTickerInfo.find(t => t.rune_name === r.rune_name); + if (!tickerInfo) return; + return makeRuneToken(r, tickerInfo); + }) + .filter(isDefined); } export function useRunesOutputsByAddress(address: string) { diff --git a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts index f343a0dd3bb..e7986efa9fc 100644 --- a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts +++ b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts @@ -42,10 +42,7 @@ export function useAlexCurrencyPriceAsMarketData() { const tokenInfo = supportedCurrencies .filter(isDefined) .find(token => pullContractIdFromIdentity(token.contractAddress) === principal); - if (!symbol || !prices || !tokenInfo) { - logger.error('Could not create market data'); - return null; - } + if (!symbol || !prices || !tokenInfo) return null; const currency = tokenInfo.id as Currency; const price = convertAmountToFractionalUnit(new BigNumber(prices[currency] ?? 0), 2); return createMarketData(createMarketPair(symbol, 'USD'), createMoney(price, 'USD'));