refactor: remove graphql flag and default to gql endpoints (#5151)

* remove graphql flag and old endpoints

* remove unused queries

* deprecate old sell order type

* better null checks

* merge conflict
This commit is contained in:
Charles Bachmeier 2022-11-09 18:15:40 -05:00 committed by GitHub
parent 37d2603406
commit dbf5c63ece
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 106 additions and 823 deletions

@ -1,6 +1,5 @@
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
@ -205,12 +204,6 @@ export default function FeatureFlagModal() {
</Header>
<FeatureFlagGroup name="Phase 1">
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
<FeatureFlagOption
variant={NftGraphQlVariant}
value={useNftGraphQlFlag()}
featureFlag={FeatureFlag.nftGraphQl}
label="NFT GraphQL Endpoints"
/>
</FeatureFlagGroup>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption

@ -3,5 +3,4 @@ export enum FeatureFlag {
nft = 'nfts',
traceJsonRpc = 'traceJsonRpc',
multiNetworkBalances = 'multiNetworkBalances',
nftGraphQl = 'nftGraphQl',
}

@ -1,7 +0,0 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useNftGraphQlFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.nftGraphQl)
}
export { BaseVariant as NftGraphQlVariant }

@ -4,7 +4,6 @@ import { TraceEvent } from 'analytics/TraceEvent'
import clsx from 'clsx'
import { loadingAnimation } from 'components/Loader/styled'
import { parseEther } from 'ethers/lib/utils'
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
import { NftAssetTraitInput, NftMarketplace } from 'graphql/data/nft/__generated__/AssetQuery.graphql'
import { useAssetsQuery } from 'graphql/data/nft/Asset'
import useDebounce from 'hooks/useDebounce'
@ -30,7 +29,6 @@ import {
} from 'nft/hooks'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { usePriceRange } from 'nft/hooks/usePriceRange'
import { AssetsFetcher } from 'nft/queries'
import { DropDownOption, GenieCollection, TokenType, UniformHeight, UniformHeights } from 'nft/types'
import { getRarityStatus } from 'nft/utils/asset'
import { pluralize } from 'nft/utils/roundAndPluralize'
@ -38,7 +36,6 @@ import { scrollToTop } from 'nft/utils/scrollToTop'
import { applyFiltersFromURL, syncLocalFiltersWithURL } from 'nft/utils/urlParams'
import { useEffect, useMemo, useRef, useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { useInfiniteQuery } from 'react-query'
import { useLocation } from 'react-router-dom'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
@ -223,7 +220,6 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const reset = useCollectionFilters((state) => state.reset)
const setMin = useCollectionFilters((state) => state.setMinPrice)
const setMax = useCollectionFilters((state) => state.setMaxPrice)
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const toggleBag = useBag((state) => state.toggleBag)
const bagExpanded = useBag((state) => state.bagExpanded)
@ -235,75 +231,12 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const [sweepIsOpen, setSweepOpen] = useState(false)
const {
data: collectionAssets,
isSuccess: AssetsFetchSuccess,
isLoading,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery(
[
'collectionNfts',
{
traits,
contractAddress,
markets,
notForSale: !buyNow,
sortBy,
debouncedMinPrice,
debouncedMaxPrice,
searchText: debouncedSearchByNameText,
},
],
async ({ pageParam = 0 }) => {
let sort = undefined
switch (sortBy) {
case SortBy.HighToLow: {
sort = { currentEthPrice: 'desc' }
break
}
case SortBy.RareToCommon: {
sort = { 'rarity.providers.0.rank': 1 }
break
}
case SortBy.CommonToRare: {
sort = { 'rarity.providers.0.rank': -1 }
break
}
default:
}
return await AssetsFetcher({
contractAddress,
sort,
markets,
notForSale: !buyNow,
searchText: debouncedSearchByNameText,
pageParam,
traits,
price: {
low: debouncedMinPrice,
high: debouncedMaxPrice,
symbol: 'ETH',
},
})
},
{
getNextPageParam: (lastPage, pages) => {
return lastPage?.flat().length === DEFAULT_ASSET_QUERY_AMOUNT ? pages.length : null
},
refetchOnReconnect: false,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchInterval: 5000,
}
)
const {
assets: nftQueryAssets,
assets: collectionNfts,
loadNext,
hasNext,
isLoadingNext,
} = useAssetsQuery(
isNftGraphQl ? contractAddress : '',
contractAddress,
SortByQueries[sortBy].field,
SortByQueries[sortBy].asc,
{
@ -328,23 +261,9 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const oldStateRef = useRef<CollectionFilters | null>(null)
const isMobile = useIsMobile()
const collectionNfts = useMemo(() => {
if (
(isNftGraphQl && !nftQueryAssets && !isLoadingNext) ||
(!isNftGraphQl && !collectionAssets) ||
!AssetsFetchSuccess
)
return undefined
return isNftGraphQl ? nftQueryAssets : collectionAssets?.pages.flat()
}, [AssetsFetchSuccess, collectionAssets, isLoadingNext, isNftGraphQl, nftQueryAssets])
const wrappedLoadingState = isNftGraphQl ? isLoadingNext : isLoading
const wrappedHasNext = isNftGraphQl ? hasNext : hasNextPage ?? false
useEffect(() => {
setIsCollectionNftsLoading(wrappedLoadingState)
}, [wrappedLoadingState, setIsCollectionNftsLoading])
setIsCollectionNftsLoading(isLoadingNext)
}, [isLoadingNext, setIsCollectionNftsLoading])
const hasRarity = getRarityStatus(rarityStatusCache, collectionStats?.address, collectionNfts)
@ -508,28 +427,24 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
<CollectionSearch />
</ActionsSubContainer>
{!hasErc1155s ? (
isLoading ? (
<LoadingButton />
) : (
<SweepButton
toggled={sweepIsOpen}
disabled={!buyNow}
className={buttonTextMedium}
onClick={() => {
if (!buyNow || hasErc1155s) return
if (!sweepIsOpen) {
scrollToTop()
if (!bagExpanded && !isMobile) toggleBag()
}
setSweepOpen(!sweepIsOpen)
}}
>
<SweepIcon viewBox="0 0 24 24" width="20px" height="20px" />
<SweepText fontWeight={600} color="currentColor" lineHeight="20px">
Sweep
</SweepText>
</SweepButton>
)
<SweepButton
toggled={sweepIsOpen}
disabled={!buyNow}
className={buttonTextMedium}
onClick={() => {
if (!buyNow || hasErc1155s) return
if (!sweepIsOpen) {
scrollToTop()
if (!bagExpanded && !isMobile) toggleBag()
}
setSweepOpen(!sweepIsOpen)
}}
>
<SweepIcon viewBox="0 0 24 24" width="20px" height="20px" />
<SweepText fontWeight={600} color="currentColor" lineHeight="20px">
Sweep
</SweepText>
</SweepButton>
) : null}
</ActionsContainer>
<Sweep
@ -593,12 +508,12 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
</Box>
</AnimatedBox>
<InfiniteScroll
next={() => (isNftGraphQl ? loadNext(DEFAULT_ASSET_QUERY_AMOUNT) : fetchNextPage())}
hasMore={wrappedHasNext}
loader={wrappedHasNext && hasNfts ? loadingAssets : null}
next={() => loadNext(DEFAULT_ASSET_QUERY_AMOUNT)}
hasMore={hasNext}
loader={hasNext && hasNfts ? loadingAssets : null}
dataLength={collectionNfts?.length ?? 0}
style={{ overflow: 'unset' }}
className={hasNfts || wrappedLoadingState ? styles.assetList : undefined}
className={hasNfts || isLoadingNext ? styles.assetList : undefined}
>
{hasNfts ? (
Nfts
@ -617,10 +532,8 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
</Box>
</EmptyCollectionWrapper>
</Center>
) : isNftGraphQl ? (
<CollectionNftsLoading />
) : (
loadingAssets
<CollectionNftsLoading />
)}
</InfiniteScroll>
</>

@ -1,6 +1,5 @@
import clsx from 'clsx'
import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart'
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
import { Box, BoxProps } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { Marquee } from 'nft/components/layout/Marquee'
@ -266,31 +265,21 @@ const statsLoadingSkeleton = (isMobile: boolean) =>
)
const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMobile?: boolean } & BoxProps) => {
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const uniqueOwnersPercentage =
stats.stats && stats.stats.total_supply
? roundWholePercentage(((stats.stats.num_owners ?? 0) / stats.stats.total_supply) * 100)
: 0
const uniqueOwnersPercentage = stats?.stats?.total_supply
? roundWholePercentage(((stats.stats.num_owners ?? 0) / stats.stats.total_supply) * 100)
: 0
const totalSupplyStr = stats.stats ? quantityFormatter(stats.stats.total_supply ?? 0) : 0
const listedPercentageStr =
stats.stats && stats.stats.total_supply
? roundWholePercentage(((stats.stats.total_listings ?? 0) / stats.stats.total_supply) * 100)
: 0
const listedPercentageStr = stats?.stats?.total_supply
? roundWholePercentage(((stats.stats.total_listings ?? 0) / stats.stats.total_supply) * 100)
: 0
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
// round daily volume & floorPrice to 3 decimals or less
const totalVolumeStr = volumeFormatter(stats.stats?.total_volume ?? 0)
const floorPriceStr = floorFormatter(stats.stats?.floor_price ?? 0)
// graphQL formatted %age values out of 100, whereas v3 endpoint did a decimal between 0 & 1
// TODO: remove feature flag gated logic when graphql migration is complete
const floorChangeStr =
stats.stats && stats.stats.one_day_floor_change
? Math.round(Math.abs(stats.stats.one_day_floor_change) * (isNftGraphQl ? 1 : 100))
: 0
const arrow =
stats.stats && stats.stats.one_day_floor_change !== undefined
? getDeltaArrow(stats.stats.one_day_floor_change)
: null
const floorChangeStr = Math.round(Math.abs(stats?.stats?.one_day_floor_change ?? 0))
const arrow = stats?.stats?.one_day_floor_change ? getDeltaArrow(stats.stats.one_day_floor_change) : undefined
return (
<Row gap={{ sm: '36', md: '60' }} {...props}>

@ -1,15 +1,13 @@
import { sendAnalyticsEvent } from 'analytics'
import { EventName, FilterTypes } from 'analytics/constants'
import clsx from 'clsx'
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
import { Box } from 'nft/components/Box'
import * as styles from 'nft/components/collection/Filters.css'
import { Column, Row } from 'nft/components/Flex'
import { ChevronUpIcon } from 'nft/components/icons'
import { subheadSmall } from 'nft/css/common.css'
import { useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { useTraitsOpen } from 'nft/hooks/useTraitsOpen'
import { TraitPosition } from 'nft/hooks/useTraitsOpen'
import { TraitPosition, useTraitsOpen } from 'nft/hooks/useTraitsOpen'
import { FormEvent, useEffect, useMemo, useReducer, useState } from 'react'
import { Checkbox } from '../layout/Checkbox'
@ -87,8 +85,6 @@ const MarketplaceItem = ({
)
}
const GRAPHQL_MARKETS = ['cryptopunks', 'sudoswap']
export const MarketplaceSelect = () => {
const {
addMarket,
@ -104,22 +100,19 @@ export const MarketplaceSelect = () => {
const [isOpen, setOpen] = useState(!!selectedMarkets.length)
const setTraitsOpen = useTraitsOpen((state) => state.setTraitsOpen)
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const MarketplaceItems = useMemo(
() =>
Object.entries(MARKETPLACE_ITEMS)
.filter(([value]) => isNftGraphQl || !GRAPHQL_MARKETS.includes(value))
.map(([value, title]) => (
<MarketplaceItem
key={value}
title={title}
value={value}
count={marketCount?.[value] || 0}
{...{ addMarket, removeMarket, isMarketSelected: selectedMarkets.includes(value) }}
/>
)),
[addMarket, isNftGraphQl, marketCount, removeMarket, selectedMarkets]
Object.entries(MARKETPLACE_ITEMS).map(([value, title]) => (
<MarketplaceItem
key={value}
title={title}
value={value}
count={marketCount?.[value] || 0}
{...{ addMarket, removeMarket, isMarketSelected: selectedMarkets.includes(value) }}
/>
)),
[addMarket, marketCount, removeMarket, selectedMarkets]
)
return (

@ -7,13 +7,7 @@ import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { Center } from 'nft/components/Flex'
import { VerifiedIcon } from 'nft/components/icons'
import { ActivityFetcher } from 'nft/queries/genie/ActivityFetcher'
import {
ActivityEventResponse,
ActivityEventType,
CollectionInfoForAsset,
GenieAsset,
GenieCollection,
} from 'nft/types'
import { ActivityEventResponse, ActivityEventType, CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { shortenAddress } from 'nft/utils/address'
import { formatEthPrice } from 'nft/utils/currency'
import { isAudio } from 'nft/utils/isAudio'
@ -281,10 +275,9 @@ enum MediaType {
interface AssetDetailsProps {
asset: GenieAsset
collection: CollectionInfoForAsset
collectionStats: GenieCollection | undefined
}
export const AssetDetails = ({ asset, collection, collectionStats }: AssetDetailsProps) => {
export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
const [dominantColor] = useState<[number, number, number]>([0, 0, 0])
const { rarityProvider } = useMemo(
@ -405,12 +398,6 @@ export const AssetDetails = ({ asset, collection, collectionStats }: AssetDetail
[isSuccess, eventsData]
)
// TODO: remove after switching to graphql
const externalUrl = collection.externalUrl ?? collectionStats?.externalUrl
const twitterUrl = collection.twitterUrl ?? collectionStats?.twitterUrl
const discordUrl = collection.discordUrl ?? collectionStats?.discordUrl
const isVerified = collection.isVerified ?? collectionStats?.isVerified
return (
<Column>
<MediaContainer>
@ -429,7 +416,7 @@ export const AssetDetails = ({ asset, collection, collectionStats }: AssetDetail
</MediaContainer>
<DefaultLink to={`/nfts/collection/${asset.address}`}>
<CollectionHeader>
{collection.collectionName} {isVerified && <VerifiedIcon />}
{collection.collectionName} {collection.isVerified && <VerifiedIcon />}
</CollectionHeader>
</DefaultLink>
@ -449,9 +436,7 @@ export const AssetDetails = ({ asset, collection, collectionStats }: AssetDetail
<img src={rarityProviderLogo} alt="cardLogo" width={16} />
</HoverImageContainer>
<ContainerText>
{collectionStats?.rarityVerified
? `Verified by ${collectionStats?.name}`
: `Ranking by ${rarity.provider === 'Genie' ? fallbackProvider : rarity.provider}`}
{`Ranking by ${rarity.provider === 'Genie' ? fallbackProvider : rarity.provider}`}
</ContainerText>
</HoverContainer>
}
@ -514,9 +499,9 @@ export const AssetDetails = ({ asset, collection, collectionStats }: AssetDetail
<DescriptionText>{collection.collectionDescription}</DescriptionText>
<SocialsContainer>
{externalUrl && <Resource name="Website" link={`${externalUrl}`} />}
{twitterUrl && <Resource name="Twitter" link={`https://twitter.com/${twitterUrl}`} />}
{discordUrl && <Resource name="Discord" link={discordUrl} />}
{collection.externalUrl && <Resource name="Website" link={`${collection.externalUrl}`} />}
{collection.twitterUrl && <Resource name="Twitter" link={`https://twitter.com/${collection.twitterUrl}`} />}
{collection.discordUrl && <Resource name="Discord" link={collection.discordUrl} />}
</SocialsContainer>
</>
</InfoContainer>

@ -2,7 +2,7 @@ import { useWeb3React } from '@web3-react/core'
import useCopyClipboard from 'hooks/useCopyClipboard'
import { CancelListingIcon, MinusIcon, PlusIcon } from 'nft/components/icons'
import { useBag } from 'nft/hooks'
import { CollectionInfoForAsset, Deprecated_SellOrder, GenieAsset, SellOrder, TokenType } from 'nft/types'
import { CollectionInfoForAsset, GenieAsset, TokenType } from 'nft/types'
import { ethNumberStandardFormatter, formatEthPrice, getMarketplaceIcon, timeLeft, useUsdPrice } from 'nft/utils'
import { shortenAddress } from 'nft/utils/address'
import { useMemo } from 'react'
@ -203,9 +203,7 @@ const OwnerInformationContainer = styled.div`
export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => {
const listing = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const cheapestOrder = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const expirationDate = cheapestOrder
? new Date((cheapestOrder as Deprecated_SellOrder).orderClosingDate ?? (cheapestOrder as SellOrder).endAt)
: undefined
const expirationDate = cheapestOrder ? new Date(cheapestOrder.endAt) : undefined
const USDPrice = useUsdPrice(asset)
const navigate = useNavigate()
@ -288,9 +286,7 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
const { account } = useWeb3React()
const cheapestOrder = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const expirationDate = cheapestOrder
? new Date((cheapestOrder as Deprecated_SellOrder).orderClosingDate ?? (cheapestOrder as SellOrder).endAt)
: undefined
const expirationDate = cheapestOrder ? new Date(cheapestOrder.endAt) : undefined
const itemsInBag = useBag((s) => s.itemsInBag)
const addAssetsToBag = useBag((s) => s.addAssetsToBag)

@ -1,4 +1,3 @@
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
import { useNftBalanceQuery } from 'graphql/data/nft/NftBalance'
import { AnimatedBox, Box } from 'nft/components/Box'
import { assetList } from 'nft/components/collection/CollectionNfts.css'
@ -18,11 +17,11 @@ import {
useWalletCollections,
} from 'nft/hooks'
import { ScreenBreakpointsPaddings } from 'nft/pages/collection/index.css'
import { fetchWalletAssets, OSCollectionsFetcher } from 'nft/queries'
import { OSCollectionsFetcher } from 'nft/queries'
import { ProfilePageStateType, WalletAsset, WalletCollection } from 'nft/types'
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { useInfiniteQuery, useQuery } from 'react-query'
import { useQuery } from 'react-query'
import { useSpring } from 'react-spring'
import styled from 'styled-components/macro'
import shallow from 'zustand/shallow'
@ -30,7 +29,6 @@ import shallow from 'zustand/shallow'
import { EmptyWalletContent } from './EmptyWalletContent'
import { ProfileAccountDetails } from './ProfileAccountDetails'
import * as styles from './ProfilePage.css'
import { ProfileBodyLoadingSkeleton } from './ProfilePageLoadingSkeleton'
import { WalletAssetDisplay } from './WalletAssetDisplay'
const SellModeButton = styled.button<{ active: boolean }>`
@ -65,12 +63,8 @@ export const ProfilePage = () => {
const collectionFilters = useWalletCollections((state) => state.collectionFilters)
const setCollectionFilters = useWalletCollections((state) => state.setCollectionFilters)
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
const walletAssets = useWalletCollections((state) => state.walletAssets)
const setWalletAssets = useWalletCollections((state) => state.setWalletAssets)
const setDisplayAssets = useWalletCollections((state) => state.setDisplayAssets)
const walletCollections = useWalletCollections((state) => state.walletCollections)
const setWalletCollections = useWalletCollections((state) => state.setWalletCollections)
const listFilter = useWalletCollections((state) => state.listFilter)
const { isSellMode, resetSellAssets, setIsSellMode } = useSellAsset(
({ isSellMode, reset, setIsSellMode }) => ({
isSellMode,
@ -85,7 +79,6 @@ export const ProfilePage = () => {
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
const isMobile = useIsMobile()
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const handleSellModeClick = useCallback(() => {
resetSellAssets()
@ -93,7 +86,7 @@ export const ProfilePage = () => {
setBagExpanded({ bagExpanded: !isSellMode })
}, [isSellMode, resetSellAssets, setBagExpanded, setIsSellMode])
const { data: ownerCollections, isLoading: collectionsAreLoading } = useQuery(
const { data: ownerCollections } = useQuery(
['ownerCollections', address],
() => OSCollectionsFetcher({ params: { asset_owner: address, offset: '0', limit: '300' } }),
{
@ -102,49 +95,10 @@ export const ProfilePage = () => {
)
const {
data: ownerAssetsData,
fetchNextPage,
hasNextPage,
isSuccess,
isLoading: assetsAreLoading,
} = useInfiniteQuery(
['ownerAssets', address, collectionFilters],
async ({ pageParam = 0 }) => {
return await fetchWalletAssets({
ownerAddress: address ?? '',
collectionAddresses: collectionFilters,
pageParam,
})
},
{
getNextPageParam: (lastPage, pages) => {
return lastPage?.flat().length === DEFAULT_WALLET_ASSET_QUERY_AMOUNT ? pages.length : null
},
refetchOnWindowFocus: false,
refetchOnMount: false,
}
)
const anyQueryIsLoading = collectionsAreLoading || assetsAreLoading
const {
walletAssets: gqlWalletAssets,
walletAssets: ownerAssets,
loadNext,
hasNext,
} = useNftBalanceQuery(isNftGraphQl ? address : '', collectionFilters, DEFAULT_WALLET_ASSET_QUERY_AMOUNT)
const ownerAssets = useMemo(
() => (isNftGraphQl ? gqlWalletAssets : isSuccess ? ownerAssetsData?.pages.flat() : []),
[isNftGraphQl, gqlWalletAssets, isSuccess, ownerAssetsData]
)
useEffect(() => {
!isNftGraphQl && setWalletAssets(ownerAssets?.flat() ?? [])
}, [ownerAssets, setWalletAssets, isNftGraphQl])
useEffect(() => {
!isNftGraphQl && setDisplayAssets(walletAssets, listFilter)
}, [walletAssets, listFilter, setDisplayAssets, isNftGraphQl])
} = useNftBalanceQuery(address, collectionFilters, DEFAULT_WALLET_ASSET_QUERY_AMOUNT)
useEffect(() => {
ownerCollections && setWalletCollections(ownerCollections)
@ -156,9 +110,7 @@ export const ProfilePage = () => {
return (
<ProfilePageColumn width="full" paddingTop={{ sm: `${PADDING}`, md: '40' }}>
{anyQueryIsLoading && !isNftGraphQl ? (
<ProfileBodyLoadingSkeleton />
) : ownerAssets?.length === 0 ? (
{ownerAssets?.length === 0 ? (
<EmptyWalletContent />
) : (
<Row alignItems="flex-start" position="relative">
@ -200,8 +152,8 @@ export const ProfilePage = () => {
/>
</Row>
<InfiniteScroll
next={() => (isNftGraphQl ? loadNext(DEFAULT_WALLET_ASSET_QUERY_AMOUNT) : fetchNextPage())}
hasMore={isNftGraphQl ? hasNext : hasNextPage ?? false}
next={() => loadNext(DEFAULT_WALLET_ASSET_QUERY_AMOUNT)}
hasMore={hasNext}
loader={
<Center>
<LoadingSparkle />
@ -270,19 +222,12 @@ export const ProfilePage = () => {
const SelectAllButton = ({ ownerAssets }: { ownerAssets: WalletAsset[] }) => {
const [isAllSelected, setIsAllSelected] = useState(false)
const displayAssets = useWalletCollections((state) => state.displayAssets)
const selectSellAsset = useSellAsset((state) => state.selectSellAsset)
const resetSellAssets = useSellAsset((state) => state.reset)
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const allAssets = useMemo(
() => (isNftGraphQl ? ownerAssets : displayAssets),
[isNftGraphQl, ownerAssets, displayAssets]
)
useEffect(() => {
if (isAllSelected) {
allAssets.forEach((asset) => selectSellAsset(asset))
ownerAssets.forEach((asset) => selectSellAsset(asset))
} else {
resetSellAssets()
}

@ -283,16 +283,6 @@ const borderWidth = ['0px', '0.5px', '1px', '1.5px', '2px', '3px', '4px']
const borderStyle = ['none', 'solid'] as const
// TODO: remove when code is done being ported over
// I'm leaving this here as a reference of the old breakpoints while we port over the new code
// tabletSm: 656,
// tablet: 708,
// tabletL: 784,
// tabletXl: 830,
// desktop: 948,
// desktopL: 1030,
// desktopXl: 1260,
export const breakpoints = {
sm: 640,
md: 768,

@ -1,13 +1,9 @@
import { PageName } from 'analytics/constants'
import { Trace } from 'analytics/Trace'
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
import { useDetailsQuery } from 'graphql/data/nft/Details'
import { AssetDetails } from 'nft/components/details/AssetDetails'
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { fetchSingleAsset } from 'nft/queries'
import { CollectionStatsFetcher } from 'nft/queries'
import { useMemo } from 'react'
import { useQuery } from 'react-query'
import { useParams } from 'react-router-dom'
import styled from 'styled-components/macro'
@ -41,28 +37,9 @@ const AssetPriceDetailsContainer = styled.div`
const Asset = () => {
const { tokenId = '', contractAddress = '' } = useParams()
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const data = useDetailsQuery(contractAddress, tokenId)
const { data } = useQuery(
['assetDetail', contractAddress, tokenId],
() => fetchSingleAsset({ contractAddress, tokenId }),
{
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
}
)
const gqlData = useDetailsQuery(contractAddress, tokenId)
const asset = useMemo(() => (isNftGraphQl ? gqlData && gqlData[0] : data && data[0]), [data, gqlData, isNftGraphQl])
const collection = useMemo(
() => (isNftGraphQl ? gqlData && gqlData[1] : data && data[1]),
[data, gqlData, isNftGraphQl]
)
const { data: collectionStats } = useQuery(['collectionStats', contractAddress], () =>
CollectionStatsFetcher(contractAddress)
)
const [asset, collection] = useMemo(() => data ?? [], [data])
return (
<>
@ -73,7 +50,7 @@ const Asset = () => {
>
{asset && collection ? (
<AssetContainer>
<AssetDetails collection={collection} asset={asset} collectionStats={collectionStats} />
<AssetDetails collection={collection} asset={asset} />
<AssetPriceDetailsContainer>
<AssetPriceDetails collection={collection} asset={asset} />
</AssetPriceDetailsContainer>

@ -1,19 +1,16 @@
import { useWeb3React } from '@web3-react/core'
import { PageName } from 'analytics/constants'
import { Trace } from 'analytics/Trace'
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
import { useCollectionQuery } from 'graphql/data/nft/Collection'
import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag'
import { AnimatedBox, Box } from 'nft/components/Box'
import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection'
import { CollectionNftsAndMenuLoading } from 'nft/components/collection/CollectionNfts'
import { Column, Row } from 'nft/components/Flex'
import { useBag, useCollectionFilters, useFiltersExpanded, useIsCollectionLoading, useIsMobile } from 'nft/hooks'
import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks'
import * as styles from 'nft/pages/collection/index.css'
import { CollectionStatsFetcher } from 'nft/queries'
import { GenieCollection } from 'nft/types'
import { Suspense, useEffect, useMemo } from 'react'
import { useQuery } from 'react-query'
import { Suspense, useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { useSpring } from 'react-spring'
import styled from 'styled-components/macro'
@ -35,7 +32,6 @@ const CollectionDisplaySection = styled(Row)`
const Collection = () => {
const { contractAddress } = useParams()
const setIsCollectionStatsLoading = useIsCollectionLoading((state) => state.setIsCollectionStatsLoading)
const isMobile = useIsMobile()
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
@ -44,23 +40,9 @@ const Collection = () => {
const isActivityToggled = pathname.includes('/activity')
const setMarketCount = useCollectionFilters((state) => state.setMarketCount)
const isBagExpanded = useBag((state) => state.bagExpanded)
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const { chainId } = useWeb3React()
const { data: queryCollection, isLoading } = useQuery(['collectionStats', contractAddress], () =>
CollectionStatsFetcher(contractAddress as string)
)
const gqlCollection = useCollectionQuery(contractAddress as string)
const collectionStats = useMemo(
() => (isNftGraphQl ? gqlCollection : queryCollection),
[isNftGraphQl, gqlCollection, queryCollection]
)
useEffect(() => {
setIsCollectionStatsLoading(isLoading)
}, [isLoading, setIsCollectionStatsLoading])
const collectionStats = useCollectionQuery(contractAddress as string)
const { gridX, gridWidthOffset } = useSpring({
gridX: isFiltersExpanded ? FILTER_WIDTH : 0,
@ -100,26 +82,22 @@ const Collection = () => {
{' '}
<Box width="full" height="276">
<Box width="full" height="276">
{isLoading ? (
<CollectionBannerLoading />
) : (
<Box
as={collectionStats?.bannerImageUrl ? 'img' : 'div'}
height="full"
width="full"
src={
collectionStats?.bannerImageUrl
? `${collectionStats.bannerImageUrl}?w=${window.innerWidth}`
: undefined
}
className={isLoading ? styles.loadingBanner : styles.bannerImage}
background="none"
/>
)}
<Box
as={collectionStats?.bannerImageUrl ? 'img' : 'div'}
height="full"
width="full"
src={
collectionStats?.bannerImageUrl
? `${collectionStats.bannerImageUrl}?w=${window.innerWidth}`
: undefined
}
className={styles.bannerImage}
background="none"
/>
</Box>
</Box>
<CollectionDescriptionSection>
{(isLoading || collectionStats !== undefined) && (
{collectionStats && (
<CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} />
)}
<div id="nft-anchor" />
@ -153,7 +131,7 @@ const Collection = () => {
/>
)
: contractAddress &&
(isLoading || collectionStats !== undefined) && (
collectionStats && (
<Suspense fallback={<CollectionNftsAndMenuLoading />}>
<CollectionNfts
collectionStats={collectionStats || ({} as GenieCollection)}
@ -167,7 +145,7 @@ const Collection = () => {
</>
) : (
// TODO: Put no collection asset page here
!isLoading && <div className={styles.noCollectionAssets}>No collection assets exist at this address</div>
<div className={styles.noCollectionAssets}>No collection assets exist at this address</div>
)}
</Column>
</Trace>

@ -1,126 +0,0 @@
import { parseEther } from '@ethersproject/units'
import { Trait } from '../../hooks/useCollectionFilters'
import { AssetPayload, CollectionSort, GenieAsset } from '../../types'
export const formatTraits = (traits: Trait[]) => {
const traitObj: Record<string, string[]> = {}
const nonMetaTraits = traits.filter((el) => el.trait_type !== 'Number of traits')
for (const trait of nonMetaTraits) {
if (!traitObj[trait.trait_type]) traitObj[trait.trait_type] = [trait.trait_value]
else traitObj[trait.trait_type].push(trait.trait_value)
}
return traitObj
}
const formatPrice = (x: number | string) => parseEther(x.toString()).toString()
export const AssetsFetcher = async ({
contractAddress,
tokenId,
sort,
markets,
price,
rarityRange,
traits,
searchText,
notForSale,
pageParam,
}: {
contractAddress: string
tokenId?: string
offset?: number
sort?: CollectionSort
markets?: string[]
price?: { high?: number | string; low?: number | string; symbol: string }
rarityRange?: Record<string, unknown>
traits?: Trait[]
searchText?: string
notForSale?: boolean
pageParam: number
}): Promise<GenieAsset[] | undefined> => {
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/assets`
const payload: AssetPayload = {
filters: {
address: contractAddress.toLowerCase(),
traits: {},
searchText,
notForSale,
tokenId,
...rarityRange,
},
fields: {
address: 1,
name: 1,
id: 1,
imageUrl: 1,
currentPrice: 1,
currentUsdPrice: 1,
paymentToken: 1,
animationUrl: 1,
notForSale: 1,
rarity: 1,
tokenId: 1,
},
limit: 25,
offset: pageParam * 25,
}
if (sort) {
payload.sort = sort
}
if (markets) {
payload.markets = markets
}
const numberOfTraits = traits?.filter((trait) => trait.trait_type === 'Number of traits')
if (numberOfTraits) {
payload.filters.numTraits = numberOfTraits.map((el) => ({ traitCount: el.trait_value }))
}
if (traits) {
payload.filters.traits = formatTraits(traits)
}
const low = price?.low ? parseFloat(formatPrice(price.low)) : undefined
const high = price?.high ? parseFloat(formatPrice(price.high)) : undefined
// Only consider sending eth price filters when searching
// across listed assets
if (!notForSale) {
if (low || high) {
payload.filters.currentEthPrice = {}
}
if (low && payload.filters.currentEthPrice) {
payload.filters.currentEthPrice.$gte = low
}
if (high && payload.filters.currentEthPrice) {
payload.filters.currentEthPrice.$lte = high
}
}
try {
const r = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
const data = await r.json()
// Unfortunately have to include totalCount into each element. The fetcher
// for swr infinite must return an array.
for (const x of data.data) {
x.totalCount = data.totalCount
x.numTraitsByAmount = data.numTraitsByAmount
}
// Uncomment the lines belo if you want to simulate a delay
// await (async () => await new Promise((resolve) => setTimeout(resolve, 50000)))();
return data.data
} catch (e) {
console.log(e)
return
}
}

@ -1,69 +0,0 @@
import { isAddress } from '@ethersproject/address'
import { groupBy } from 'nft/utils/groupBy'
import { GenieCollection } from '../../types'
export const CollectionStatsFetcher = async (addressOrName: string, recursive = false): Promise<GenieCollection> => {
const isName = !isAddress(addressOrName.toLowerCase())
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/collections`
if (!isName && !recursive) {
try {
return await CollectionStatsFetcher(addressOrName.toLowerCase(), true)
} catch {
// Handle Error
}
}
const filters = isName
? {
$or: [{ name: { $regex: addressOrName, $options: 'i' } }],
}
: { address: addressOrName }
const payload = {
filters,
limit: isName ? 6 : 1,
fields: isName
? {
name: 1,
imageUrl: 1,
address: 1,
stats: 1,
floorPrice: 1,
}
: {
traits: 1,
stats: 1,
'indexingStats.openSea': 1,
imageUrl: 1,
bannerImageUrl: 1,
twitter: 1,
externalUrl: 1,
instagram: 1,
discordUrl: 1,
marketplaceCount: 1,
floorPrice: 1,
},
offset: 0,
}
const r = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
const data = await r.json()
const collections = data?.data.map((collection: Record<string, unknown>) => {
// @ts-ignore
collection.stats.floor_price = collection.floorPrice
return {
...collection,
traits: collection.traits && groupBy(collection.traits as unknown[], 'trait_type'),
}
})
return collections[0]
}

@ -1,31 +0,0 @@
import { GenieCollection } from '../../types'
export const fetchMultipleCollectionStats = async ({
addresses,
}: {
addresses: string[]
}): Promise<GenieCollection[]> => {
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/searchCollections`
const filters = {
address: { $in: addresses },
}
const payload = {
filters,
fields: {
stats: 1,
imageUrl: 1,
address: 1,
name: 1,
},
}
const r = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
const data = await r.json()
return data.data
}

@ -1,23 +0,0 @@
import { CollectionInfoForAsset, GenieAsset } from '../../types'
interface ReponseTrait {
trait_type: string
value: string
}
export const fetchSingleAsset = async ({
contractAddress,
tokenId,
}: {
contractAddress: string
tokenId?: string
}): Promise<[GenieAsset, CollectionInfoForAsset]> => {
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/assetDetails?address=${contractAddress}&tokenId=${tokenId}`
const r = await fetch(url)
const data = await r.json()
const asset = data.asset[0]
asset.traits = asset.traits.map((trait: ReponseTrait) => ({ trait_type: trait.trait_type, trait_value: trait.value }))
return [asset, data.collection]
}

@ -1,79 +0,0 @@
import { parseEther } from '@ethersproject/units'
import { Trait } from 'nft/hooks/useCollectionFilters'
import { AssetPayload, GenieAsset } from 'nft/types'
import { formatTraits } from './AssetsFetcher'
const formatPrice = (x: number | string) => parseEther(x.toString()).toString()
export const fetchSweep = async ({
contractAddress,
markets,
price,
rarityRange,
traits,
}: {
contractAddress: string
markets?: string[]
price?: { high?: number | string; low?: number | string; symbol: string }
rarityRange?: Record<string, unknown>
traits?: Trait[]
}): Promise<GenieAsset[]> => {
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/assets`
const payload: AssetPayload = {
filters: {
address: contractAddress.toLowerCase(),
traits: {},
...rarityRange,
},
fields: {
address: 1,
name: 1,
id: 1,
imageUrl: 1,
currentPrice: 1,
currentUsdPrice: 1,
paymentToken: 1,
animationUrl: 1,
notForSale: 1,
rarity: 1,
tokenId: 1,
},
limit: 50,
offset: 0,
}
if (markets) {
payload.markets = markets
}
if (traits) {
payload.filters.traits = formatTraits(traits)
}
const low = price?.low ? parseFloat(formatPrice(price.low)) : undefined
const high = price?.high ? parseFloat(formatPrice(price.high)) : undefined
if (low || high) {
payload.filters.currentEthPrice = {}
}
if (low && payload.filters.currentEthPrice) {
payload.filters.currentEthPrice.$gte = low
}
if (high && payload.filters.currentEthPrice) {
payload.filters.currentEthPrice.$lte = high
}
const r = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
const data = await r.json()
return data.data
}

@ -1,19 +0,0 @@
import { TransactionsResponse } from '../../types'
export const fetchTransactions = async (payload: { sweep?: boolean }): Promise<TransactionsResponse[]> => {
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/transactions`
const r = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
const data = (await r.json()) as TransactionsResponse[]
return data.filter(
(x) => x.bannerImage && (payload.sweep ? x.nftCount >= 3 && Math.floor(x.ethValue / 10 ** 18) >= 1 : true)
)
}

@ -1,64 +0,0 @@
import { BigNumber } from '@ethersproject/bignumber'
import { formatEther } from '@ethersproject/units'
import { WalletAsset } from '../../types'
const getEthPrice = (price: any) => {
if (price.toString().includes('e')) {
return BigNumber.from(10).pow(price.toString().split('e+')[1]).toString()
}
return Math.round(price).toString()
}
export const fetchWalletAssets = async ({
ownerAddress,
collectionAddresses,
pageParam,
}: {
ownerAddress: string
collectionAddresses?: string[]
pageParam: number
}): Promise<WalletAsset[]> => {
const collectionAddressesString = collectionAddresses
? collectionAddresses.reduce((str, collectionAddress) => str + `&assetContractAddresses=${collectionAddress}`, '')
: ''
const url = `${
process.env.REACT_APP_GENIE_V3_API_URL
}/walletAssets?address=${ownerAddress}${collectionAddressesString}&limit=25&offset=${pageParam * 25}`
const r = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
const data = await r.json()
return data.data.assets.map((asset: any) => {
return {
...asset,
collectionIsVerified: asset.asset_contract.isVerified,
lastPrice: asset.last_sale.total_price && formatEther(asset.last_sale.total_price),
floorPrice: asset.collection?.floorPrice,
creatorPercentage: parseFloat(asset.asset_contract.dev_seller_fee_basis_points) / 10000,
date_acquired: asset.last_sale ? asset.last_sale.event_timestamp : asset.asset_contract.created_date,
listing_date: asset.sellOrders.length
? Math.max
.apply(
null,
asset.sellOrders.map(function (order: any) {
return new Date(order.orderCreatedDate)
})
)
.toString()
: null,
floor_sell_order_price: asset?.sellOrders?.length
? Math.min(
...asset.sellOrders.map((order: any) => {
return parseFloat(formatEther(getEthPrice(order.ethPrice)))
})
)
: null,
}
})
}

@ -1,15 +1,7 @@
export * from './ActivityFetcher'
export * from './AssetsFetcher'
export * from './CollectionPreviewFetcher'
export * from './CollectionStatsFetcher'
export * from './logListing'
export * from './LooksRareRewardsFetcher'
export * from './MultipleCollectionStatsFetcher'
export * from './RouteFetcher'
export * from './SearchCollectionsFetcher'
export * from './SingleAssetFetcher'
export * from './SweepFetcher'
export * from './TransactionsFetcher'
export * from './TrendingCollectionsFetcher'
export * from './triggerPriceUpdatesForCollection'
export * from './WalletAssetsFetcher'

@ -1,13 +0,0 @@
export const triggerPriceUpdatesForCollection = async (address: string) => {
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/collections/refresh`
const r = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
address,
}),
})
return r.json()
}

@ -1,4 +1,4 @@
import { Deprecated_SellOrder, SellOrder } from '../sell'
import { SellOrder } from '../sell'
export interface OpenSeaCollection {
name: string
@ -88,7 +88,7 @@ export interface GenieAsset {
name?: string
priceInfo: PriceInfo
susFlag?: boolean
sellorders?: Deprecated_SellOrder[] | SellOrder[] // TODO remove Deprecated_SellOrder when full migration to GraphQL is complete
sellorders?: SellOrder[]
smallImageUrl?: string
tokenId: string
tokenType: TokenType

@ -12,24 +12,6 @@ export interface ListingWarning {
message: string
}
export interface Deprecated_SellOrder {
assetId: string
ethPrice: number
basePrice: number
baseCurrency: string
baseCurrencyDecimal: number
orderCreatedDate: string
orderClosingDate: string
quantity: number
timestamp: string
marketplace: string
marketplaceUrl: string
orderHash: string
ammFeePercent?: number
ethReserves?: number
tokenReserves?: number
}
export interface SellOrder {
address: string
createdAt: number
@ -78,7 +60,7 @@ export interface WalletAsset {
creatorPercentage: number
listing_date: string
date_acquired: string
sellOrders: Deprecated_SellOrder[] | SellOrder[] // TODO remove Deprecated_SellOrder when full migration to GraphQL is complete
sellOrders: SellOrder[]
floor_sell_order_price: number
// Used for creating new listings
expirationTime?: number

@ -1,13 +1,5 @@
import { BigNumber } from '@ethersproject/bignumber'
import {
BagItem,
BagItemStatus,
Deprecated_SellOrder,
GenieAsset,
Markets,
SellOrder,
UpdatedGenieAsset,
} from 'nft/types'
import { BagItem, BagItemStatus, GenieAsset, Markets, UpdatedGenieAsset } from 'nft/types'
// TODO: a lot of the below typecasting logic can be simplified when GraphQL migration is complete
export const calcPoolPrice = (asset: GenieAsset, position = 0) => {
@ -15,11 +7,7 @@ export const calcPoolPrice = (asset: GenieAsset, position = 0) => {
let marginalBuy: BigNumber = BigNumber.from(0)
if (!asset.sellorders) return ''
const nft =
(asset.sellorders[0] as Deprecated_SellOrder).ammFeePercent === undefined
? (asset.sellorders[0] as SellOrder).protocolParameters
: (asset.sellorders[0] as Deprecated_SellOrder)
const nft = asset.sellorders[0].protocolParameters
const decimals = BigNumber.from(1).mul(10).pow(18)
const ammFee = nft?.ammFeePercent ? (100 + (nft.ammFeePercent as number)) * 100 : 110 * 100
@ -43,29 +31,23 @@ export const calcPoolPrice = (asset: GenieAsset, position = 0) => {
const ethReserves = BigNumber.from(
(
(nft?.ethReserves as number) ??
(
nft as Record<
string,
{
ethReserves: number
}
>
)?.poolMetadata?.ethReserves
)?.toLocaleString('fullwide', { useGrouping: false }) ?? 1
nft as Record<
string,
{
ethReserves: number
}
>
)?.poolMetadata?.ethReserves?.toLocaleString('fullwide', { useGrouping: false }) ?? 1
)
const tokenReserves = BigNumber.from(
(
(nft?.tokenReserves as number) ??
(
nft as Record<
string,
{
tokenReserves: number
}
>
)?.poolMetadata?.tokenReserves
)?.toLocaleString('fullwide', { useGrouping: false }) ?? 1
nft as Record<
string,
{
tokenReserves: number
}
>
)?.poolMetadata?.tokenReserves?.toLocaleString('fullwide', { useGrouping: false }) ?? 1
)
const numerator = ethReserves.mul(amountToBuy).mul(1000)
const denominator = tokenReserves.sub(amountToBuy).mul(997)