Commit 9fd6ab01 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

refactor: Asset Details GQL Query (#5037)

* add details query

* add new query file

* connect details gql query to page

* remove comment

* type refactoring

* working pooled asset logic

* useLazyload

* remove any cast

* Deprecated_SellOrder

* extranneous cast

* return markets
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 48aa1140
...@@ -19,7 +19,7 @@ const nftHeaders = { ...@@ -19,7 +19,7 @@ const nftHeaders = {
// The issue below prevented using a custom var in metadata to gate which queries are for the nft endpoint vs base endpoint // The issue below prevented using a custom var in metadata to gate which queries are for the nft endpoint vs base endpoint
// This is a temporary solution before the two endpoints merge // This is a temporary solution before the two endpoints merge
// https://github.com/relay-tools/relay-hooks/issues/215 // https://github.com/relay-tools/relay-hooks/issues/215
const NFT_QUERIES = ['AssetQuery', 'AssetPaginationQuery', 'CollectionQuery'] const NFT_QUERIES = ['AssetQuery', 'AssetPaginationQuery', 'CollectionQuery', 'DetailsQuery']
const fetchQuery = (params: RequestParameters, variables: Variables): Promise<GraphQLResponse> => { const fetchQuery = (params: RequestParameters, variables: Variables): Promise<GraphQLResponse> => {
const isNFT = NFT_QUERIES.includes(params.name) const isNFT = NFT_QUERIES.includes(params.name)
......
import graphql from 'babel-plugin-relay/macro' import graphql from 'babel-plugin-relay/macro'
import { parseEther } from 'ethers/lib/utils' import { parseEther } from 'ethers/lib/utils'
import { GenieAsset, Rarity } from 'nft/types' import { GenieAsset, Rarity, SellOrder } from 'nft/types'
import { useLazyLoadQuery, usePaginationFragment } from 'react-relay' import { useLazyLoadQuery, usePaginationFragment } from 'react-relay'
import { AssetPaginationQuery } from './__generated__/AssetPaginationQuery.graphql' import { AssetPaginationQuery } from './__generated__/AssetPaginationQuery.graphql'
...@@ -75,6 +75,7 @@ const assetPaginationQuery = graphql` ...@@ -75,6 +75,7 @@ const assetPaginationQuery = graphql`
taker taker
tokenId tokenId
type type
protocolParameters
} }
cursor cursor
} }
...@@ -155,7 +156,14 @@ export function useAssetsQuery( ...@@ -155,7 +156,14 @@ export function useAssetsQuery(
} }
: undefined, : undefined,
susFlag: asset.suspiciousFlag, susFlag: asset.suspiciousFlag,
sellorders: asset.listings?.edges, sellorders: asset.listings?.edges.map((listingNode: { node: SellOrder }) => {
return {
...listingNode.node,
protocolParameters: listingNode.node.protocolParameters
? JSON.parse(listingNode.node.protocolParameters.toString())
: undefined,
}
}),
smallImageUrl: asset.smallImage?.url, smallImageUrl: asset.smallImage?.url,
tokenId: asset.tokenId, tokenId: asset.tokenId,
tokenType: asset.collection.nftContracts[0]?.standard, tokenType: asset.collection.nftContracts[0]?.standard,
......
import { parseEther } from '@ethersproject/units'
import graphql from 'babel-plugin-relay/macro'
import { Trait } from 'nft/hooks'
import { CollectionInfoForAsset, GenieAsset, SellOrder, TokenType } from 'nft/types'
import { useLazyLoadQuery } from 'react-relay'
import { DetailsQuery } from './__generated__/DetailsQuery.graphql'
const detailsQuery = graphql`
query DetailsQuery($address: String!, $tokenId: String!) {
nftAssets(address: $address, filter: { listed: false, tokenIds: [$tokenId] }) {
edges {
node {
id
name
ownerAddress
image {
url
}
smallImage {
url
}
originalImage {
url
}
tokenId
description
animationUrl
suspiciousFlag
creator {
address
profileImage {
url
}
isVerified
}
collection {
name
isVerified
numAssets
image {
url
}
nftContracts {
address
standard
}
description
}
listings(first: 1) {
edges {
node {
address
createdAt
endAt
id
maker
marketplace
marketplaceUrl
orderHash
price {
currency
value
}
quantity
startAt
status
taker
tokenId
type
protocolParameters
}
cursor
}
}
rarities {
provider
rank
score
}
metadataUrl
traits {
name
value
}
}
}
}
}
`
export function useDetailsQuery(address: string, tokenId: string): [GenieAsset, CollectionInfoForAsset] | undefined {
const queryData = useLazyLoadQuery<DetailsQuery>(detailsQuery, {
address,
tokenId,
})
const asset = queryData.nftAssets?.edges[0]?.node
const collection = asset?.collection
const ethPrice = parseEther(asset?.listings?.edges[0].node.price.value?.toString() ?? '0').toString()
return [
{
id: asset?.id,
address,
notForSale: asset?.listings === null,
collectionName: asset?.collection?.name ?? undefined,
collectionSymbol: asset?.collection?.image?.url,
imageUrl: asset?.image?.url,
animationUrl: asset?.animationUrl ?? undefined,
marketplace: asset?.listings?.edges[0]?.node.marketplace.toLowerCase() as any,
name: asset?.name ?? undefined,
priceInfo: {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
},
susFlag: asset?.suspiciousFlag ?? undefined,
sellorders: asset?.listings?.edges.map((listingNode) => {
return {
...listingNode.node,
protocolParameters: listingNode.node.protocolParameters
? JSON.parse(listingNode.node.protocolParameters.toString())
: undefined,
} as SellOrder
}),
smallImageUrl: asset?.smallImage?.url,
tokenId,
tokenType: (asset?.collection?.nftContracts && asset?.collection.nftContracts[0]?.standard) as TokenType,
collectionIsVerified: asset?.collection?.isVerified ?? undefined,
rarity: {
primaryProvider: 'Rarity Sniper', // TODO update when backend adds more providers
providers: asset?.rarities
? asset?.rarities?.map((rarity) => {
return {
rank: rarity.rank ?? undefined,
score: rarity.score ?? undefined,
provider: 'Rarity Sniper',
}
})
: undefined,
},
owner: asset?.ownerAddress ?? undefined,
creator: {
profile_img_url: asset?.creator?.profileImage?.url,
address: asset?.creator?.address,
},
metadataUrl: asset?.metadataUrl ?? undefined,
traits: asset?.traits?.map((trait) => {
return { trait_type: trait.name ?? undefined, trait_value: trait.value ?? undefined } as Trait
}),
},
{
collectionDescription: collection?.description,
collectionImageUrl: collection?.image?.url,
collectionName: collection?.name ?? undefined,
isVerified: collection?.isVerified ?? undefined,
totalSupply: collection?.numAssets ?? undefined,
},
]
}
...@@ -577,7 +577,7 @@ const DetailsLink = () => { ...@@ -577,7 +577,7 @@ const DetailsLink = () => {
/* -------- RANKING CARD -------- */ /* -------- RANKING CARD -------- */
interface RankingProps { interface RankingProps {
rarity: Rarity rarity: Rarity
provider: { url?: string; rank: number } provider: { url?: string; rank?: number }
rarityVerified: boolean rarityVerified: boolean
rarityLogo?: string rarityLogo?: string
} }
...@@ -586,33 +586,37 @@ const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps) ...@@ -586,33 +586,37 @@ const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps)
const { asset } = useCardContext() const { asset } = useCardContext()
return ( return (
<RankingContainer> <>
<MouseoverTooltip {provider.rank && (
text={ <RankingContainer>
<Row> <MouseoverTooltip
<Box display="flex" marginRight="4"> text={
<img src={rarityLogo} alt="cardLogo" width={16} /> <Row>
</Box> <Box display="flex" marginRight="4">
<Box width="full" className={bodySmall}> <img src={rarityLogo} alt="cardLogo" width={16} />
{rarityVerified </Box>
? `Verified by ${asset.collectionName}` <Box width="full" className={bodySmall}>
: `Ranking by ${rarity.primaryProvider === 'Genie' ? fallbackProvider : rarity.primaryProvider}`} {rarityVerified
? `Verified by ${asset.collectionName}`
: `Ranking by ${rarity.primaryProvider === 'Genie' ? fallbackProvider : rarity.primaryProvider}`}
</Box>
</Row>
}
placement="top"
>
<Box className={styles.rarityInfo}>
<Box paddingTop="2" paddingBottom="2" display="flex">
{putCommas(provider.rank)}
</Box>
<Box display="flex" height="16">
{rarityVerified ? <RarityVerifiedIcon /> : null}
</Box>
</Box> </Box>
</Row> </MouseoverTooltip>
} </RankingContainer>
placement="top" )}
> </>
<Box className={styles.rarityInfo}>
<Box paddingTop="2" paddingBottom="2" display="flex">
{putCommas(provider.rank)}
</Box>
<Box display="flex" height="16">
{rarityVerified ? <RarityVerifiedIcon /> : null}
</Box>
</Box>
</MouseoverTooltip>
</RankingContainer>
) )
} }
const SUSPICIOUS_TEXT = 'Blocked on OpenSea' const SUSPICIOUS_TEXT = 'Blocked on OpenSea'
......
...@@ -56,11 +56,13 @@ export const CollectionAsset = ({ ...@@ -56,11 +56,13 @@ export const CollectionAsset = ({
let notForSale = true let notForSale = true
let assetMediaType = AssetMediaType.Image let assetMediaType = AssetMediaType.Image
notForSale = asset.notForSale || BigNumber.from(asset.priceInfo.ETHPrice ? asset.priceInfo.ETHPrice : 0).lt(0) notForSale = asset.notForSale || BigNumber.from(asset.priceInfo ? asset.priceInfo.ETHPrice : 0).lt(0)
if (isAudio(asset.animationUrl)) { if (asset.animationUrl) {
assetMediaType = AssetMediaType.Audio if (isAudio(asset.animationUrl)) {
} else if (isVideo(asset.animationUrl)) { assetMediaType = AssetMediaType.Audio
assetMediaType = AssetMediaType.Video } else if (isVideo(asset.animationUrl)) {
assetMediaType = AssetMediaType.Video
}
} }
return { return {
...@@ -71,7 +73,9 @@ export const CollectionAsset = ({ ...@@ -71,7 +73,9 @@ export const CollectionAsset = ({
const { provider, rarityLogo } = useMemo(() => { const { provider, rarityLogo } = useMemo(() => {
return { return {
provider: asset.rarity?.providers.find(({ provider: _provider }) => _provider === asset.rarity?.primaryProvider), provider: asset?.rarity?.providers?.find(
({ provider: _provider }) => _provider === asset.rarity?.primaryProvider
),
rarityLogo: rarityProviderLogo[asset.rarity?.primaryProvider ?? 0] ?? '', rarityLogo: rarityProviderLogo[asset.rarity?.primaryProvider ?? 0] ?? '',
} }
}, [asset]) }, [asset])
...@@ -98,7 +102,7 @@ export const CollectionAsset = ({ ...@@ -98,7 +102,7 @@ export const CollectionAsset = ({
> >
<Card.ImageContainer> <Card.ImageContainer>
{asset.tokenType === 'ERC1155' && quantity > 0 && <Card.Erc1155Controls quantity={quantity.toString()} />} {asset.tokenType === 'ERC1155' && quantity > 0 && <Card.Erc1155Controls quantity={quantity.toString()} />}
{asset.rarity && provider && provider.rank && ( {asset.rarity && provider && (
<Card.Ranking <Card.Ranking
rarity={asset.rarity} rarity={asset.rarity}
provider={provider} provider={provider}
......
...@@ -16,7 +16,7 @@ import { badge, bodySmall, caption, headlineMedium, subhead } from 'nft/css/comm ...@@ -16,7 +16,7 @@ import { badge, bodySmall, caption, headlineMedium, subhead } from 'nft/css/comm
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useBag } from 'nft/hooks' import { useBag } from 'nft/hooks'
import { useTimeout } from 'nft/hooks/useTimeout' import { useTimeout } from 'nft/hooks/useTimeout'
import { CollectionInfoForAsset, GenieAsset, SellOrder } from 'nft/types' import { CollectionInfoForAsset, Deprecated_SellOrder, GenieAsset, SellOrder } from 'nft/types'
import { useUsdPrice } from 'nft/utils' import { useUsdPrice } from 'nft/utils'
import { shortenAddress } from 'nft/utils/address' import { shortenAddress } from 'nft/utils/address'
import { formatEthPrice } from 'nft/utils/currency' import { formatEthPrice } from 'nft/utils/currency'
...@@ -60,9 +60,9 @@ const AudioPlayer = ({ ...@@ -60,9 +60,9 @@ const AudioPlayer = ({
const formatter = Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' }) const formatter = Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' })
const CountdownTimer = ({ sellOrder }: { sellOrder: SellOrder }) => { const CountdownTimer = ({ sellOrder }: { sellOrder: Deprecated_SellOrder | SellOrder }) => {
const { date, expires } = useMemo(() => { const { date, expires } = useMemo(() => {
const date = new Date(sellOrder.orderClosingDate) const date = new Date((sellOrder as Deprecated_SellOrder).orderClosingDate ?? (sellOrder as SellOrder).endAt)
return { return {
date, date,
expires: formatter.format(date), expires: formatter.format(date),
...@@ -148,7 +148,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => { ...@@ -148,7 +148,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
() => () =>
asset.rarity asset.rarity
? { ? {
rarityProvider: asset.rarity.providers.find( rarityProvider: asset?.rarity?.providers?.find(
({ provider: _provider }) => _provider === asset.rarity?.primaryProvider ({ provider: _provider }) => _provider === asset.rarity?.primaryProvider
), ),
rarityLogo: rarityProviderLogo[asset.rarity.primaryProvider] || '', rarityLogo: rarityProviderLogo[asset.rarity.primaryProvider] || '',
...@@ -158,16 +158,16 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => { ...@@ -158,16 +158,16 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
) )
const assetMediaType = useMemo(() => { const assetMediaType = useMemo(() => {
if (isAudio(asset.animationUrl)) { if (isAudio(asset.animationUrl ?? '')) {
return MediaType.Audio return MediaType.Audio
} else if (isVideo(asset.animationUrl)) { } else if (isVideo(asset.animationUrl ?? '')) {
return MediaType.Video return MediaType.Video
} }
return MediaType.Image return MediaType.Image
}, [asset]) }, [asset])
useEffect(() => { useEffect(() => {
if (asset.creator) setCreatorAddress(asset.creator.address) if (asset.creator) setCreatorAddress(asset.creator.address ?? '')
if (asset.owner) setOwnerAddress(asset.owner) if (asset.owner) setOwnerAddress(asset.owner)
}, [asset]) }, [asset])
...@@ -355,7 +355,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => { ...@@ -355,7 +355,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
</Row> </Row>
</Column> </Column>
{asset.priceInfo && !isOwned ? ( {asset.priceInfo && asset.sellorders && !isOwned ? (
<Row <Row
marginTop="8" marginTop="8"
marginBottom="40" marginBottom="40"
...@@ -372,7 +372,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => { ...@@ -372,7 +372,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
<a href={asset.sellorders[0].marketplaceUrl} rel="noreferrer" target="_blank"> <a href={asset.sellorders[0].marketplaceUrl} rel="noreferrer" target="_blank">
<img <img
className={styles.marketplace} className={styles.marketplace}
src={`/nft/svgs/marketplaces/${asset.sellorders[0].marketplace}.svg`} src={`/nft/svgs/marketplaces/${asset.sellorders[0].marketplace.toLowerCase()}.svg`}
height={16} height={16}
width={16} width={16}
alt="Markeplace" alt="Markeplace"
...@@ -387,7 +387,10 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => { ...@@ -387,7 +387,10 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
</Box> </Box>
)} )}
</Row> </Row>
{asset.sellorders?.[0].orderClosingDate ? <CountdownTimer sellOrder={asset.sellorders[0]} /> : null} {(asset.sellorders?.[0] as Deprecated_SellOrder).orderClosingDate ||
(asset.sellorders?.[0] as SellOrder).endAt ? (
<CountdownTimer sellOrder={asset.sellorders[0]} />
) : null}
</Column> </Column>
<Box <Box
as="button" as="button"
......
...@@ -4,7 +4,7 @@ import { EventName, PageName } from 'analytics/constants' ...@@ -4,7 +4,7 @@ import { EventName, PageName } from 'analytics/constants'
import { useTrace } from 'analytics/Trace' import { useTrace } from 'analytics/Trace'
import { CancelListingIcon, MinusIcon, PlusIcon } from 'nft/components/icons' import { CancelListingIcon, MinusIcon, PlusIcon } from 'nft/components/icons'
import { useBag } from 'nft/hooks' import { useBag } from 'nft/hooks'
import { CollectionInfoForAsset, GenieAsset, TokenType } from 'nft/types' import { CollectionInfoForAsset, Deprecated_SellOrder, GenieAsset, SellOrder, TokenType } from 'nft/types'
import { ethNumberStandardFormatter, formatEthPrice, getMarketplaceIcon, timeLeft, useUsdPrice } from 'nft/utils' import { ethNumberStandardFormatter, formatEthPrice, getMarketplaceIcon, timeLeft, useUsdPrice } from 'nft/utils'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
...@@ -113,7 +113,9 @@ const DiscoveryContainer = styled.div` ...@@ -113,7 +113,9 @@ const DiscoveryContainer = styled.div`
export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => { export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => {
const listing = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined const listing = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const expirationDate = listing ? new Date(listing.orderClosingDate) : undefined const expirationDate = listing
? new Date((listing as Deprecated_SellOrder).orderClosingDate ?? (listing as SellOrder).endAt)
: undefined
const USDPrice = useUsdPrice(asset) const USDPrice = useUsdPrice(asset)
const navigate = useNavigate() const navigate = useNavigate()
...@@ -191,7 +193,9 @@ export const NotForSale = ({ collection }: { collection: CollectionInfoForAsset ...@@ -191,7 +193,9 @@ export const NotForSale = ({ collection }: { collection: CollectionInfoForAsset
export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) => { export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) => {
const { account } = useWeb3React() const { account } = useWeb3React()
const cheapestOrder = 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.orderClosingDate) : undefined const expirationDate = cheapestOrder
? new Date((cheapestOrder as Deprecated_SellOrder).orderClosingDate ?? (cheapestOrder as SellOrder).endAt)
: undefined
const itemsInBag = useBag((s) => s.itemsInBag) const itemsInBag = useBag((s) => s.itemsInBag)
const addAssetsToBag = useBag((s) => s.addAssetsToBag) const addAssetsToBag = useBag((s) => s.addAssetsToBag)
const removeAssetsFromBag = useBag((s) => s.removeAssetsFromBag) const removeAssetsFromBag = useBag((s) => s.removeAssetsFromBag)
......
...@@ -12,8 +12,8 @@ export const CollectionProfile = ({ ...@@ -12,8 +12,8 @@ export const CollectionProfile = ({
}: { }: {
isVerified?: boolean isVerified?: boolean
label: string label: string
name: string name?: string
avatarUrl: string avatarUrl?: string
} & BoxProps) => { } & BoxProps) => {
return ( return (
<Row {...props}> <Row {...props}>
......
...@@ -23,9 +23,9 @@ export const Details = ({ ...@@ -23,9 +23,9 @@ export const Details = ({
}: { }: {
contractAddress: string contractAddress: string
tokenId: string tokenId: string
metadataUrl: string metadataUrl?: string
tokenType: string tokenType: string
totalSupply: number totalSupply?: number
blockchain: string blockchain: string
}) => ( }) => (
<Row gap={{ md: '32', sm: '16' }} width="full" justifyContent="space-between" alignItems="flex-start" flexWrap="wrap"> <Row gap={{ md: '32', sm: '16' }} width="full" justifyContent="space-between" alignItems="flex-start" flexWrap="wrap">
......
import { PageName } from 'analytics/constants' import { PageName } from 'analytics/constants'
import { Trace } from 'analytics/Trace' 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 { AssetDetails } from 'nft/components/details/AssetDetails'
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails' import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { fetchSingleAsset } from 'nft/queries' import { fetchSingleAsset } from 'nft/queries'
...@@ -16,6 +18,8 @@ const AssetContainer = styled.div` ...@@ -16,6 +18,8 @@ const AssetContainer = styled.div`
const Asset = () => { const Asset = () => {
const { tokenId = '', contractAddress = '' } = useParams() const { tokenId = '', contractAddress = '' } = useParams()
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
const { data } = useQuery( const { data } = useQuery(
['assetDetail', contractAddress, tokenId], ['assetDetail', contractAddress, tokenId],
() => fetchSingleAsset({ contractAddress, tokenId }), () => fetchSingleAsset({ contractAddress, tokenId }),
...@@ -25,9 +29,13 @@ const Asset = () => { ...@@ -25,9 +29,13 @@ const Asset = () => {
refetchOnReconnect: false, refetchOnReconnect: false,
} }
) )
const gqlData = useDetailsQuery(contractAddress, tokenId)
const asset = useMemo(() => (data ? data[0] : undefined), [data]) const asset = useMemo(() => (isNftGraphQl ? gqlData && gqlData[0] : data && data[0]), [data, gqlData, isNftGraphQl])
const collection = useMemo(() => (data ? data[1] : undefined), [data]) const collection = useMemo(
() => (isNftGraphQl ? gqlData && gqlData[1] : data && data[1]),
[data, gqlData, isNftGraphQl]
)
return ( return (
<> <>
......
...@@ -52,7 +52,7 @@ const buildRouteItem = (item: GenieAsset): RouteItem => { ...@@ -52,7 +52,7 @@ const buildRouteItem = (item: GenieAsset): RouteItem => {
return { return {
id: item.id, id: item.id,
symbol: item.priceInfo.baseAsset, symbol: item.priceInfo.baseAsset,
name: item.name, name: item.name ?? '',
decimals: parseFloat(item.priceInfo.baseDecimals), decimals: parseFloat(item.priceInfo.baseDecimals),
address: item.address, address: item.address,
tokenType: item.tokenType, tokenType: item.tokenType,
......
...@@ -21,12 +21,11 @@ export interface AssetPayload { ...@@ -21,12 +21,11 @@ export interface AssetPayload {
} }
export interface CollectionInfoForAsset { export interface CollectionInfoForAsset {
collectionSymbol: string collectionDescription?: string | null
collectionDescription: string | null collectionImageUrl?: string
collectionImageUrl: string collectionName?: string
collectionName: string isVerified?: boolean
isVerified: boolean totalSupply?: number
totalSupply: number
} }
export type CollectionSort = Record< export type CollectionSort = Record<
......
import { Trait } from 'nft/hooks/useCollectionFilters' import { Trait } from 'nft/hooks/useCollectionFilters'
import { SellOrder } from '../sell' import { Deprecated_SellOrder, SellOrder } from '../sell'
export interface OpenSeaCollection { export interface OpenSeaCollection {
name: string name: string
...@@ -45,9 +45,9 @@ export interface OpenSeaAsset { ...@@ -45,9 +45,9 @@ export interface OpenSeaAsset {
interface OpenSeaUser { interface OpenSeaUser {
user?: null user?: null
profile_img_url: string profile_img_url?: string
address: string address?: string
config: string config?: string
} }
export enum TokenType { export enum TokenType {
...@@ -60,7 +60,7 @@ export enum TokenType { ...@@ -60,7 +60,7 @@ export enum TokenType {
export interface PriceInfo { export interface PriceInfo {
ETHPrice: string ETHPrice: string
USDPrice: string USDPrice?: string
baseAsset: string baseAsset: string
baseDecimals: string baseDecimals: string
basePrice: string basePrice: string
...@@ -74,31 +74,31 @@ export interface AssetSellOrder { ...@@ -74,31 +74,31 @@ export interface AssetSellOrder {
export interface Rarity { export interface Rarity {
primaryProvider: string primaryProvider: string
providers: { provider: string; rank: number; url?: string; score: number }[] providers?: { provider: string; rank?: number; url?: string; score?: number }[]
} }
export interface GenieAsset { export interface GenieAsset {
id?: string // This would be a random id created and assigned by front end id?: string // This would be a random id created and assigned by front end
address: string address: string
notForSale: boolean notForSale: boolean
collectionName: string collectionName?: string
collectionSymbol: string collectionSymbol?: string
imageUrl: string imageUrl?: string
animationUrl: string animationUrl?: string
marketplace: Markets marketplace?: Markets
name: string name?: string
priceInfo: PriceInfo priceInfo: PriceInfo
susFlag: boolean susFlag?: boolean
sellorders: SellOrder[] sellorders?: Deprecated_SellOrder[] | SellOrder[] // TODO remove OldSellOrder when full migration to GraphQL is complete
smallImageUrl: string smallImageUrl?: string
tokenId: string tokenId: string
tokenType: TokenType tokenType: TokenType
totalCount?: number // The totalCount from the query to /assets totalCount?: number // The totalCount from the query to /assets
collectionIsVerified?: boolean collectionIsVerified?: boolean
rarity?: Rarity rarity?: Rarity
owner: string owner?: string
creator: OpenSeaUser creator: OpenSeaUser
metadataUrl: string metadataUrl?: string
traits?: Trait[] traits?: Trait[]
} }
......
import { NftMarketplace, OrderStatus, OrderType } from 'graphql/data/nft/__generated__/DetailsQuery.graphql'
import { GenieCollection } from '../common' import { GenieCollection } from '../common'
export interface ListingMarket { export interface ListingMarket {
...@@ -10,7 +12,7 @@ export interface ListingWarning { ...@@ -10,7 +12,7 @@ export interface ListingWarning {
message: string message: string
} }
export interface SellOrder { export interface Deprecated_SellOrder {
assetId: string assetId: string
ethPrice: number ethPrice: number
basePrice: number basePrice: number
...@@ -28,6 +30,27 @@ export interface SellOrder { ...@@ -28,6 +30,27 @@ export interface SellOrder {
tokenReserves?: number tokenReserves?: number
} }
export interface SellOrder {
address: string
createdAt: number
endAt: number
id: string
maker: string
marketplace: NftMarketplace
marketplaceUrl: string
orderHash: string
price: {
currency: string
value: number
}
quantity: number
startAt: number
status: OrderStatus
tokenId: string
type: OrderType
protocolParameters: Record<string, unknown>
}
export interface Listing { export interface Listing {
price?: number price?: number
marketplace: ListingMarket marketplace: ListingMarket
...@@ -61,7 +84,7 @@ export interface WalletAsset { ...@@ -61,7 +84,7 @@ export interface WalletAsset {
creatorPercentage: number creatorPercentage: number
listing_date: string listing_date: string
date_acquired: string date_acquired: string
sellOrders: SellOrder[] sellOrders: Deprecated_SellOrder[]
floor_sell_order_price: number floor_sell_order_price: number
// Used for creating new listings // Used for creating new listings
expirationTime?: number expirationTime?: number
......
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { BagItem, BagItemStatus, GenieAsset, Markets, UpdatedGenieAsset } from 'nft/types' import {
BagItem,
BagItemStatus,
Deprecated_SellOrder,
GenieAsset,
Markets,
SellOrder,
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) => { export const calcPoolPrice = (asset: GenieAsset, position = 0) => {
let amountToBuy: BigNumber = BigNumber.from(0) let amountToBuy: BigNumber = BigNumber.from(0)
let marginalBuy: BigNumber = BigNumber.from(0) let marginalBuy: BigNumber = BigNumber.from(0)
const nft = asset.sellorders[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 decimals = BigNumber.from(1).mul(10).pow(18) const decimals = BigNumber.from(1).mul(10).pow(18)
const ammFee = nft.ammFeePercent ? (100 + nft.ammFeePercent) * 100 : 110 * 100 const ammFee = nft.ammFeePercent ? (100 + (nft.ammFeePercent as number)) * 100 : 110 * 100
if (asset.marketplace === Markets.NFTX) { if (asset.marketplace === Markets.NFTX) {
const sixteenmul = BigNumber.from(1).mul(10).pow(16) const sixteenmul = BigNumber.from(1).mul(10).pow(16)
...@@ -26,8 +41,32 @@ export const calcPoolPrice = (asset: GenieAsset, position = 0) => { ...@@ -26,8 +41,32 @@ export const calcPoolPrice = (asset: GenieAsset, position = 0) => {
marginalBuy = marginalBuy.mul(decimals) marginalBuy = marginalBuy.mul(decimals)
} }
const ethReserves = BigNumber.from(nft.ethReserves?.toLocaleString('fullwide', { useGrouping: false })) const ethReserves = BigNumber.from(
const tokenReserves = BigNumber.from(nft.tokenReserves?.toLocaleString('fullwide', { useGrouping: false })) (
(nft.ethReserves as number) ??
(
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
)
const numerator = ethReserves.mul(amountToBuy).mul(1000) const numerator = ethReserves.mul(amountToBuy).mul(1000)
const denominator = tokenReserves.sub(amountToBuy).mul(997) const denominator = tokenReserves.sub(amountToBuy).mul(997)
...@@ -71,7 +110,7 @@ export const recalculateBagUsingPooledAssets = (uncheckedItemsInBag: BagItem[]) ...@@ -71,7 +110,7 @@ export const recalculateBagUsingPooledAssets = (uncheckedItemsInBag: BagItem[])
const possibleMarkets = itemsInBag.reduce((markets, item) => { const possibleMarkets = itemsInBag.reduce((markets, item) => {
const asset = item.asset const asset = item.asset
const market = asset.marketplace const market = asset.marketplace
if (!isPooledMarket(market)) return markets if (!market || !isPooledMarket(market)) return markets
const key = asset.address + asset.marketplace const key = asset.address + asset.marketplace
if (Object.keys(markets).includes(key)) { if (Object.keys(markets).includes(key)) {
...@@ -85,8 +124,7 @@ export const recalculateBagUsingPooledAssets = (uncheckedItemsInBag: BagItem[]) ...@@ -85,8 +124,7 @@ export const recalculateBagUsingPooledAssets = (uncheckedItemsInBag: BagItem[])
const updatedPriceMarkets = itemsInBag.reduce((markets, item) => { const updatedPriceMarkets = itemsInBag.reduce((markets, item) => {
const asset = item.asset const asset = item.asset
const market = asset.marketplace const market = asset.marketplace
if (!asset.updatedPriceInfo) return markets if (!market || !asset.updatedPriceInfo || !isPooledMarket(market)) return markets
if (!isPooledMarket(market)) return markets
const key = asset.address + asset.marketplace const key = asset.address + asset.marketplace
if (Object.keys(markets).includes(key)) { if (Object.keys(markets).includes(key)) {
...@@ -103,18 +141,19 @@ export const recalculateBagUsingPooledAssets = (uncheckedItemsInBag: BagItem[]) ...@@ -103,18 +141,19 @@ export const recalculateBagUsingPooledAssets = (uncheckedItemsInBag: BagItem[])
}, {} as { [key: string]: string }) }, {} as { [key: string]: string })
itemsInBag.forEach((item) => { itemsInBag.forEach((item) => {
if (isPooledMarket(item.asset.marketplace)) { if (item.asset.marketplace)
const asset = item.asset if (isPooledMarket(item.asset.marketplace)) {
const isPriceChangedAsset = !!asset.updatedPriceInfo const asset = item.asset
const isPriceChangedAsset = !!asset.updatedPriceInfo
const calculatedPrice = isPriceChangedAsset
? calculatedAvgPoolPrices[asset.address + asset.marketplace] const calculatedPrice = isPriceChangedAsset
: calcPoolPrice(asset, possibleMarkets[asset.address + asset.marketplace].indexOf(item.asset.tokenId)) ? calculatedAvgPoolPrices[asset.address + asset.marketplace]
: calcPoolPrice(asset, possibleMarkets[asset.address + asset.marketplace].indexOf(item.asset.tokenId))
if (isPriceChangedAsset && item.asset.updatedPriceInfo)
item.asset.updatedPriceInfo.ETHPrice = item.asset.updatedPriceInfo.basePrice = calculatedPrice if (isPriceChangedAsset && item.asset.updatedPriceInfo)
else item.asset.priceInfo.ETHPrice = calculatedPrice item.asset.updatedPriceInfo.ETHPrice = item.asset.updatedPriceInfo.basePrice = calculatedPrice
} else item.asset.priceInfo.ETHPrice = calculatedPrice
}
}) })
return itemsInBag return itemsInBag
......
...@@ -18,7 +18,9 @@ export const fetchPrice = async (currency: Currency = Currency.ETH): Promise<num ...@@ -18,7 +18,9 @@ export const fetchPrice = async (currency: Currency = Currency.ETH): Promise<num
} }
} }
export function useUsdPrice(asset: GenieAsset): string { export function useUsdPrice(asset: GenieAsset): string | undefined {
const { data: fetchedPriceData } = useQuery(['fetchPrice', {}], () => fetchPrice(), {}) const { data: fetchedPriceData } = useQuery(['fetchPrice', {}], () => fetchPrice(), {})
return fetchedPriceData ? (parseFloat(formatEther(asset.priceInfo.ETHPrice)) * fetchedPriceData).toString() : '' return fetchedPriceData && asset.priceInfo.ETHPrice
? (parseFloat(formatEther(asset.priceInfo.ETHPrice)) * fetchedPriceData).toString()
: undefined
} }
...@@ -6,7 +6,6 @@ import TopLevelModals from 'components/TopLevelModals' ...@@ -6,7 +6,6 @@ import TopLevelModals from 'components/TopLevelModals'
import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useFeatureFlagsIsLoaded } from 'featureFlags'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft' import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton'
import { lazy, Suspense, useEffect, useState } from 'react' import { lazy, Suspense, useEffect, useState } from 'react'
import { Navigate, Route, Routes, useLocation } from 'react-router-dom' import { Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { useIsDarkMode } from 'state/user/hooks' import { useIsDarkMode } from 'state/user/hooks'
...@@ -245,23 +244,16 @@ export default function App() { ...@@ -245,23 +244,16 @@ export default function App() {
<> <>
<Route path="/profile" element={<Profile />} /> <Route path="/profile" element={<Profile />} />
<Route path="/nfts" element={<NftExplore />} /> <Route path="/nfts" element={<NftExplore />} />
<Route path="/nfts/asset/:contractAddress/:tokenId" element={<Asset />} />
<Route <Route
path="/nfts/collection/:contractAddress" path="/nfts/asset/:contractAddress/:tokenId"
element={ element={
<Suspense fallback={<CollectionPageSkeleton />}> <Suspense fallback={<div>Holder for loading ...</div>}>
<Collection /> <Asset />
</Suspense>
}
/>
<Route
path="/nfts/collection/:contractAddress/activity"
element={
<Suspense fallback={<CollectionPageSkeleton />}>
<Collection />
</Suspense> </Suspense>
} }
/> />
<Route path="/nfts/collection/:contractAddress" element={<Collection />} />
<Route path="/nfts/collection/:contractAddress/activity" element={<Collection />} />
</> </>
)} )}
</Routes> </Routes>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment