Commit 2aa1b18d authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

chore: Migrate from Relay to Apollo (#5754)

* feat: initial apollo configutation (#5565)

* initial apollo configutation

* add new files

* check in types-and-hooks

* config unused export

* deduplicate

* ignore checked in schema for linting

* remove prettier ignore

* test unchecking types and hooks file

* undo

* rename codegen, respond to comments
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* Remove maybe value from codegen

* add babel gql codegen

* correct ts graphql-tag

* remove plugin from craco

* chore: migrate Assets Query to Apollo (#5665)

* chore: migrate Assets Query to Apollo

* delete comment

* move length check back to collectionAssets

* remove uneeded check

* respond to comments

* working switching and filters

* change sweep fetch policy
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* chore: migrate collection query to apollo (#5647)

* migrate collection query to apollo

* remove page level suspense

* undo removing page level suspense

* rename query and hook

* guard returns

* add return type prop

* cleanup nullables

* memoizing

* use gql from apollo

* use babel gql and move empty trait

* add fetch policy
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* chore: migrate NFT details query to apollo (#5648)

* chore: migrate NFT details query to apollo

* update todo

* update imports

* remove no longer used hook

* rename query

* use babel gql and nonnullable type

* working page

* add fetchpolicy

* respond to comments
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* chore: migrate NftBalanceQuery (#5653)

* chore: migrate NftBalanceQuery

* cleanup

* update pagination

* better undefined handling

* move brake listing for invalid asset higher

* better handle loading

* memoize and cleanup
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* remove named gql query consts

* set default fetchPolicy

* null suspense

* chore: Migrate The Graph queries (#5727)

* migrate TheGraph queries to Apollo

* add new files

* ignore thegraph generated types

* use standard fetchPolicy

* update apollo codegen commands
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* chore: migrate token queries to Apollo (#5682)

* migrate utils to types-and-hooks

* too many TokenTable re-renders

* working token queries

* fixed sparkline for native asset

* onChangeTimePeriod

* define inline

* use query instead of data in naming

* sparklineQuery instead of sparklineData

* rename to usePriceHistory

* multiline if else

* remove optional

* remove unneeded eslint ignore

* rename tokenQueryLoading

* rename OnChangeTimePeriod

* token address fallback

* just address
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* chore: deprecate Relay (#5747)

* chore: deprecate Relay

* remove graph:ql generate step

* add new files

* apollo to graphql centric naming

* add new files
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>

* remove no longer needed config exclusions
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent a286e5b1
*.config.ts *.config.ts
*.d.ts *.d.ts
/src/graphql/data/__generated__/types-and-hooks.ts
/src/graphql/thegraph/__generated__/types-and-hooks.ts
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
/src/locales/**/pseudo.po /src/locales/**/pseudo.po
# generated graphql types # generated graphql types
__generated__/
schema.graphql schema.graphql
# dependencies # dependencies
......
import type { CodegenConfig } from '@graphql-codegen/cli'
// Generates TS objects from the schemas returned by graphql queries
// To learn more: https://www.apollographql.com/docs/react/development-testing/static-typing/#setting-up-your-project
const config: CodegenConfig = {
overwrite: true,
schema: './src/graphql/data/schema.graphql',
documents: ['./src/graphql/data/**', '!./src/graphql/data/__generated__/**', '!**/thegraph/**'],
generates: {
'src/graphql/data/__generated__/types-and-hooks.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-apollo'],
config: {
withHooks: true,
// This avoid all generated schemas being wrapped in Maybe https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#maybevalue-string-default-value-t--null
maybeValue: 'T',
},
},
},
}
// This is used in package.json when generating apollo schemas however the linter stills flags this as unused
// eslint-disable-next-line import/no-unused-modules
export default config
import type { CodegenConfig } from '@graphql-codegen/cli'
// Generates TS objects from the schemas returned by graphql queries
// To learn more: https://www.apollographql.com/docs/react/development-testing/static-typing/#setting-up-your-project
const config: CodegenConfig = {
overwrite: true,
schema: './src/graphql/thegraph/schema.graphql',
documents: ['!./src/graphql/data/**', '!./src/graphql/thegraph/__generated__/**', './src/graphql/thegraph/**'],
generates: {
'src/graphql/thegraph/__generated__/types-and-hooks.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-apollo'],
config: {
withHooks: true,
// This avoid all generated schemas being wrapped in Maybe https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#maybevalue-string-default-value-t--null
maybeValue: 'T',
},
},
},
}
// This is used in package.json when generating apollo schemas however the linter stills flags this as unused
// eslint-disable-next-line import/no-unused-modules
export default config
/* eslint-disable */ /* eslint-disable */
require('dotenv').config({ path: '.env.production' }) require('dotenv').config({ path: '.env.production' })
const { exec } = require('child_process') const { exec } = require('child_process')
const dataConfig = require('./relay.config') const dataConfig = require('./graphql.config')
const thegraphConfig = require('./relay_thegraph.config') const thegraphConfig = require('./graphql_thegraph.config')
/* eslint-enable */ /* eslint-enable */
function fetchSchema(url, outputFile) { function fetchSchema(url, outputFile) {
......
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const defaultConfig = require('./relay.config') const defaultConfig = require('./graphql.config')
module.exports = { module.exports = {
src: defaultConfig.src, src: defaultConfig.src,
......
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"", "contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"", "contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3", "contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
"relay": "relay-compiler relay.config.js",
"relay-thegraph": "relay-compiler relay_thegraph.config.js",
"graphql:fetch": "node fetch-schema.js", "graphql:fetch": "node fetch-schema.js",
"graphql:generate": "yarn relay && yarn relay-thegraph", "graphql:generate:data": "graphql-codegen --config apollo-codegen.ts",
"graphql:generate:thegraph": "graphql-codegen --config apollo-codegen_thegraph.ts",
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
"prei18n:extract": "node prei18n-extract.js", "prei18n:extract": "node prei18n-extract.js",
"i18n:extract": "lingui extract --locale en-US", "i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "yarn i18n:extract && lingui compile", "i18n:compile": "yarn i18n:extract && lingui compile",
...@@ -94,7 +94,6 @@ ...@@ -94,7 +94,6 @@
"@typescript-eslint/parser": "^4", "@typescript-eslint/parser": "^4",
"@vanilla-extract/babel-plugin": "^1.1.7", "@vanilla-extract/babel-plugin": "^1.1.7",
"@vanilla-extract/webpack-plugin": "^2.1.11", "@vanilla-extract/webpack-plugin": "^2.1.11",
"babel-plugin-relay": "^14.1.0",
"cypress": "^10.3.1", "cypress": "^10.3.1",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"eslint": "^7.11.0", "eslint": "^7.11.0",
...@@ -113,16 +112,23 @@ ...@@ -113,16 +112,23 @@
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
"relay-compiler": "^14.1.0",
"serve": "^11.3.2", "serve": "^11.3.2",
"ts-transform-graphql-tag": "^0.2.1",
"typechain": "^5.0.0", "typechain": "^5.0.0",
"typescript": "^4.4.3", "typescript": "^4.4.3",
"yarn-deduplicate": "^6.0.0" "yarn-deduplicate": "^6.0.0"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.7.2",
"@coinbase/wallet-sdk": "^3.3.0", "@coinbase/wallet-sdk": "^3.3.0",
"@fontsource/ibm-plex-mono": "^4.5.1", "@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1", "@fontsource/inter": "^4.5.1",
"@graphql-codegen/cli": "^2.15.0",
"@graphql-codegen/client-preset": "^1.2.1",
"@graphql-codegen/typescript": "^2.8.3",
"@graphql-codegen/typescript-operations": "^2.5.8",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@graphql-codegen/typescript-resolvers": "^2.7.8",
"@lingui/core": "^3.14.0", "@lingui/core": "^3.14.0",
"@lingui/macro": "^3.14.0", "@lingui/macro": "^3.14.0",
"@lingui/react": "^3.14.0", "@lingui/react": "^3.14.0",
...@@ -135,7 +141,6 @@ ...@@ -135,7 +141,6 @@
"@react-hook/window-scroll": "^1.3.0", "@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.6.1", "@reduxjs/toolkit": "^1.6.1",
"@sentry/react": "7.20.1", "@sentry/react": "7.20.1",
"@types/react-relay": "^13.0.2",
"@types/react-window-infinite-loader": "^1.0.6", "@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "1.2.0", "@uniswap/analytics": "1.2.0",
"@uniswap/analytics-events": "^1.5.0", "@uniswap/analytics-events": "^1.5.0",
...@@ -215,8 +220,6 @@ ...@@ -215,8 +220,6 @@
"react-popper": "^2.2.3", "react-popper": "^2.2.3",
"react-query": "^3.39.1", "react-query": "^3.39.1",
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
"react-relay": "^14.1.0",
"react-relay-network-modern": "^6.2.1",
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-spring": "^9.5.5", "react-spring": "^9.5.5",
"react-table": "^7.8.0", "react-table": "^7.8.0",
......
...@@ -2,7 +2,6 @@ import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow' ...@@ -2,7 +2,6 @@ import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow'
import { curveCardinal, scaleLinear } from 'd3' import { curveCardinal, scaleLinear } from 'd3'
import { SparklineMap, TopToken } from 'graphql/data/TopTokens' import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
import { PricePoint } from 'graphql/data/util' import { PricePoint } from 'graphql/data/util'
import { TimePeriod } from 'graphql/data/util'
import { memo } from 'react' import { memo } from 'react'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
...@@ -21,18 +20,10 @@ interface SparklineChartProps { ...@@ -21,18 +20,10 @@ interface SparklineChartProps {
height: number height: number
tokenData: TopToken tokenData: TopToken
pricePercentChange: number | undefined | null pricePercentChange: number | undefined | null
timePeriod: TimePeriod
sparklineMap: SparklineMap sparklineMap: SparklineMap
} }
function _SparklineChart({ function _SparklineChart({ width, height, tokenData, pricePercentChange, sparklineMap }: SparklineChartProps) {
width,
height,
tokenData,
pricePercentChange,
timePeriod,
sparklineMap,
}: SparklineChartProps) {
const theme = useTheme() const theme = useTheme()
// for sparkline // for sparkline
const pricePoints = tokenData?.address ? sparklineMap[tokenData.address] : null const pricePoints = tokenData?.address ? sparklineMap[tokenData.address] : null
......
import { ParentSize } from '@visx/responsive' import { ParentSize } from '@visx/responsive'
import { ChartContainer, LoadingChart } from 'components/Tokens/TokenDetails/Skeleton' import { ChartContainer, LoadingChart } from 'components/Tokens/TokenDetails/Skeleton'
import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice' import { TokenPriceQuery } from 'graphql/data/TokenPrice'
import { isPricePoint, PricePoint } from 'graphql/data/util' import { isPricePoint, PricePoint } from 'graphql/data/util'
import { TimePeriod } from 'graphql/data/util' import { TimePeriod } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import { pageTimePeriodAtom } from 'pages/TokenDetails' import { pageTimePeriodAtom } from 'pages/TokenDetails'
import { startTransition, Suspense, useMemo } from 'react' import { startTransition, Suspense, useMemo } from 'react'
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
import { PriceChart } from './PriceChart' import { PriceChart } from './PriceChart'
import TimePeriodSelector from './TimeSelector' import TimePeriodSelector from './TimeSelector'
function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPriceQuery>): PricePoint[] | undefined { function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined {
const queryData = usePreloadedQuery(tokenPriceQuery, priceQueryReference)
// Appends the current price to the end of the priceHistory array // Appends the current price to the end of the priceHistory array
const priceHistory = useMemo(() => { const priceHistory = useMemo(() => {
const market = queryData.tokens?.[0]?.market const market = tokenPriceData.tokens?.[0]?.market
const priceHistory = market?.priceHistory?.filter(isPricePoint) const priceHistory = market?.priceHistory?.filter(isPricePoint)
const currentPrice = market?.price?.value const currentPrice = market?.price?.value
if (Array.isArray(priceHistory) && currentPrice !== undefined) { if (Array.isArray(priceHistory) && currentPrice !== undefined) {
...@@ -24,39 +21,39 @@ function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPr ...@@ -24,39 +21,39 @@ function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPr
return [...priceHistory, { timestamp, value: currentPrice }] return [...priceHistory, { timestamp, value: currentPrice }]
} }
return priceHistory return priceHistory
}, [queryData]) }, [tokenPriceData])
return priceHistory return priceHistory
} }
export default function ChartSection({ export default function ChartSection({
priceQueryReference, tokenPriceQuery,
refetchTokenPrices, onChangeTimePeriod,
}: { }: {
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined tokenPriceQuery?: TokenPriceQuery
refetchTokenPrices: RefetchPricesFunction onChangeTimePeriod: OnChangeTimePeriod
}) { }) {
if (!priceQueryReference) { if (!tokenPriceQuery) {
return <LoadingChart /> return <LoadingChart />
} }
return ( return (
<Suspense fallback={<LoadingChart />}> <Suspense fallback={<LoadingChart />}>
<ChartContainer> <ChartContainer>
<Chart priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} /> <Chart tokenPriceQuery={tokenPriceQuery} onChangeTimePeriod={onChangeTimePeriod} />
</ChartContainer> </ChartContainer>
</Suspense> </Suspense>
) )
} }
export type RefetchPricesFunction = (t: TimePeriod) => void export type OnChangeTimePeriod = (t: TimePeriod) => void
function Chart({ function Chart({
priceQueryReference, tokenPriceQuery,
refetchTokenPrices, onChangeTimePeriod,
}: { }: {
priceQueryReference: PreloadedQuery<TokenPriceQuery> tokenPriceQuery: TokenPriceQuery
refetchTokenPrices: RefetchPricesFunction onChangeTimePeriod: OnChangeTimePeriod
}) { }) {
const prices = usePreloadedTokenPriceQuery(priceQueryReference) const prices = usePriceHistory(tokenPriceQuery)
// Initializes time period to global & maintain separate time period for subsequent changes // Initializes time period to global & maintain separate time period for subsequent changes
const timePeriod = useAtomValue(pageTimePeriodAtom) const timePeriod = useAtomValue(pageTimePeriodAtom)
...@@ -68,7 +65,7 @@ function Chart({ ...@@ -68,7 +65,7 @@ function Chart({
<TimePeriodSelector <TimePeriodSelector
currentTimePeriod={timePeriod} currentTimePeriod={timePeriod}
onTimeChange={(t: TimePeriod) => { onTimeChange={(t: TimePeriod) => {
startTransition(() => refetchTokenPrices(t)) startTransition(() => onChangeTimePeriod(t))
}} }}
/> />
</ChartContainer> </ChartContainer>
......
...@@ -27,21 +27,20 @@ import Widget from 'components/Widget' ...@@ -27,21 +27,20 @@ import Widget from 'components/Widget'
import { getChainInfo } from 'constants/chainInfo' import { getChainInfo } from 'constants/chainInfo'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { checkWarning } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { TokenPriceQuery } from 'graphql/data/__generated__/TokenPriceQuery.graphql' import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token' import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token'
import { QueryToken, tokenQuery } from 'graphql/data/Token' import { QueryToken } from 'graphql/data/Token'
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util' import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
import { useIsUserAddedTokenOnChain } from 'hooks/Tokens' import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch' import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency' import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
import { useCallback, useMemo, useState, useTransition } from 'react' import { useCallback, useMemo, useState, useTransition } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { isAddress } from 'utils' import { isAddress } from 'utils'
import { RefetchPricesFunction } from './ChartSection' import { OnChangeTimePeriod } from './ChartSection'
import InvalidTokenDetails from './InvalidTokenDetails' import InvalidTokenDetails from './InvalidTokenDetails'
const TokenSymbol = styled.span` const TokenSymbol = styled.span`
...@@ -75,7 +74,7 @@ function useRelevantToken( ...@@ -75,7 +74,7 @@ function useRelevantToken(
const queryToken = useMemo(() => { const queryToken = useMemo(() => {
if (!address) return undefined if (!address) return undefined
if (address === NATIVE_CHAIN_ID) return nativeOnChain(pageChainId) if (address === NATIVE_CHAIN_ID) return nativeOnChain(pageChainId)
if (tokenQueryData) return new QueryToken(tokenQueryData) if (tokenQueryData) return new QueryToken(address, tokenQueryData)
return undefined return undefined
}, [pageChainId, address, tokenQueryData]) }, [pageChainId, address, tokenQueryData])
// fetches on-chain token if query data is missing and page chain matches global chain (else fetch won't work) // fetches on-chain token if query data is missing and page chain matches global chain (else fetch won't work)
...@@ -91,16 +90,16 @@ function useRelevantToken( ...@@ -91,16 +90,16 @@ function useRelevantToken(
type TokenDetailsProps = { type TokenDetailsProps = {
urlAddress: string | undefined urlAddress: string | undefined
chain: Chain chain: Chain
tokenQueryReference: PreloadedQuery<TokenQuery> tokenQuery: TokenQuery
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined tokenPriceQuery: TokenPriceQuery | undefined
refetchTokenPrices: RefetchPricesFunction onChangeTimePeriod: OnChangeTimePeriod
} }
export default function TokenDetails({ export default function TokenDetails({
urlAddress, urlAddress,
chain, chain,
tokenQueryReference, tokenQuery,
priceQueryReference, tokenPriceQuery,
refetchTokenPrices, onChangeTimePeriod,
}: TokenDetailsProps) { }: TokenDetailsProps) {
if (!urlAddress) { if (!urlAddress) {
throw new Error('Invalid token details route: tokenAddress param is undefined') throw new Error('Invalid token details route: tokenAddress param is undefined')
...@@ -112,7 +111,7 @@ export default function TokenDetails({ ...@@ -112,7 +111,7 @@ export default function TokenDetails({
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain] const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const tokenQueryData = usePreloadedQuery(tokenQuery, tokenQueryReference).tokens?.[0] const tokenQueryData = tokenQuery.tokens?.[0]
const crossChainMap = useMemo( const crossChainMap = useMemo(
() => () =>
tokenQueryData?.project?.tokens.reduce((map, current) => { tokenQueryData?.project?.tokens.reduce((map, current) => {
...@@ -200,7 +199,7 @@ export default function TokenDetails({ ...@@ -200,7 +199,7 @@ export default function TokenDetails({
<ShareButton currency={token} /> <ShareButton currency={token} />
</TokenActions> </TokenActions>
</TokenInfoContainer> </TokenInfoContainer>
<ChartSection priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} /> <ChartSection tokenPriceQuery={tokenPriceQuery} onChangeTimePeriod={onChangeTimePeriod} />
<StatsSection <StatsSection
TVL={tokenQueryData?.market?.totalValueLocked?.value} TVL={tokenQueryData?.market?.totalValueLocked?.value}
volume24H={tokenQueryData?.market?.volume24H?.value} volume24H={tokenQueryData?.market?.volume24H?.value}
......
...@@ -459,7 +459,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT ...@@ -459,7 +459,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
return ( return (
<div ref={ref} data-testid={`token-table-row-${tokenName}`}> <div ref={ref} data-testid={`token-table-row-${tokenName}`}>
<StyledLink <StyledLink
to={getTokenDetailsURL(token.address, token.chain)} to={getTokenDetailsURL(token.address ?? '', token.chain)}
onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)} onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)}
> >
<TokenRow <TokenRow
...@@ -512,7 +512,6 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT ...@@ -512,7 +512,6 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
height={height} height={height}
tokenData={token} tokenData={token}
pricePercentChange={token.market?.pricePercentChange?.value} pricePercentChange={token.market?.pricePercentChange?.value}
timePeriod={timePeriod}
sparklineMap={props.sparklineMap} sparklineMap={props.sparklineMap}
/> />
) )
......
...@@ -64,7 +64,7 @@ const LoadingRows = ({ rowCount }: { rowCount: number }) => ( ...@@ -64,7 +64,7 @@ const LoadingRows = ({ rowCount }: { rowCount: number }) => (
</> </>
) )
export function LoadingTokenTable({ rowCount = PAGE_SIZE }: { rowCount?: number }) { function LoadingTokenTable({ rowCount = PAGE_SIZE }: { rowCount?: number }) {
return ( return (
<GridContainer> <GridContainer>
<HeaderRow /> <HeaderRow />
...@@ -75,14 +75,15 @@ export function LoadingTokenTable({ rowCount = PAGE_SIZE }: { rowCount?: number ...@@ -75,14 +75,15 @@ export function LoadingTokenTable({ rowCount = PAGE_SIZE }: { rowCount?: number
) )
} }
export default function TokenTable({ setRowCount }: { setRowCount: (c: number) => void }) { export default function TokenTable() {
// TODO: consider moving prefetched call into app.tsx and passing it here, use a preloaded call & updated on interval every 60s // TODO: consider moving prefetched call into app.tsx and passing it here, use a preloaded call & updated on interval every 60s
const chainName = validateUrlChainParam(useParams<{ chainName?: string }>().chainName) const chainName = validateUrlChainParam(useParams<{ chainName?: string }>().chainName)
const { tokens, sparklines } = useTopTokens(chainName) const { tokens, loadingTokens, sparklines } = useTopTokens(chainName)
setRowCount(tokens?.length ?? PAGE_SIZE)
/* loading and error state */ /* loading and error state */
if (!tokens) { if (loadingTokens) {
return <LoadingTokenTable rowCount={PAGE_SIZE} />
} else if (!tokens) {
return ( return (
<NoTokensState <NoTokensState
message={ message={
......
import ms from 'ms.macro'
import {
RelayNetworkLayer,
RelayNetworkLayerResponse,
retryMiddleware,
urlMiddleware,
} from 'react-relay-network-modern'
import { Environment, RecordSource, Store } from 'relay-runtime'
// This makes it possible (and more likely) to be able to reuse data when navigating back to a page,
// tab or piece of content that has been visited before. These settings together configure the cache
// to serve the last 250 records, so long as they are less than 5 minutes old:
const gcReleaseBufferSize = 250
const queryCacheExpirationTime = ms`5m`
const GRAPHQL_URL = process.env.REACT_APP_AWS_API_ENDPOINT
if (!GRAPHQL_URL) {
throw new Error('AWS URL MISSING FROM ENVIRONMENT')
}
const RETRY_TIME_MS = [3200, 6400, 12800]
// This network layer must not cache, or it will break cache-evicting network policies
const network = new RelayNetworkLayer(
[
urlMiddleware({
url: GRAPHQL_URL,
headers: {
'Content-Type': 'application/json',
},
}),
function logAndIgnoreErrors(next) {
return async (req) => {
try {
const res = await next(req)
if (!res || !res.data) throw new Error('Missing response data')
return res
} catch (e) {
console.error(e)
return RelayNetworkLayerResponse.createFromGraphQL({ data: [] })
}
}
},
retryMiddleware({
fetchTimeout: ms`30s`, // mirrors backend's timeout in case that fails
retryDelays: RETRY_TIME_MS,
statusCodes: (statusCode) => statusCode >= 500 && statusCode < 600,
}),
],
{ noThrow: true }
)
const CachingEnvironment = new Environment({
network,
store: new Store(new RecordSource(), { gcReleaseBufferSize, queryCacheExpirationTime }),
})
export default CachingEnvironment
import graphql from 'babel-plugin-relay/macro'
import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens' import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens'
import gql from 'graphql-tag'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { TokenQuery$data } from './__generated__/TokenQuery.graphql' import { TokenQuery } from './__generated__/types-and-hooks'
import { CHAIN_NAME_TO_CHAIN_ID } from './util' import { CHAIN_NAME_TO_CHAIN_ID } from './util'
/* /*
...@@ -13,14 +13,14 @@ The difference between Token and TokenProject: ...@@ -13,14 +13,14 @@ The difference between Token and TokenProject:
TokenMarket is per-chain market data for contracts pulled from the graph. TokenMarket is per-chain market data for contracts pulled from the graph.
TokenProjectMarket is aggregated market data (aggregated over multiple dexes and centralized exchanges) that we get from coingecko. TokenProjectMarket is aggregated market data (aggregated over multiple dexes and centralized exchanges) that we get from coingecko.
*/ */
export const tokenQuery = graphql` gql`
query TokenQuery($contract: ContractInput!) { query Token($contract: ContractInput!) {
tokens(contracts: [$contract]) { tokens(contracts: [$contract]) {
id @required(action: LOG) id
decimals decimals
name name
chain @required(action: LOG) chain
address @required(action: LOG) address
symbol symbol
market(currency: USD) { market(currency: USD) {
totalValueLocked { totalValueLocked {
...@@ -48,23 +48,24 @@ export const tokenQuery = graphql` ...@@ -48,23 +48,24 @@ export const tokenQuery = graphql`
twitterName twitterName
logoUrl logoUrl
tokens { tokens {
chain @required(action: LOG) chain
address @required(action: LOG) address
} }
} }
} }
} }
` `
export type { Chain, TokenQuery } from './__generated__/TokenQuery.graphql'
export type TokenQueryData = NonNullable<TokenQuery$data['tokens']>[number] export type { Chain, TokenQuery } from './__generated__/types-and-hooks'
export type TokenQueryData = NonNullable<TokenQuery['tokens']>[number]
// TODO: Return a QueryToken from useTokenQuery instead of TokenQueryData to make it more usable in Currency-centric interfaces. // TODO: Return a QueryToken from useTokenQuery instead of TokenQueryData to make it more usable in Currency-centric interfaces.
export class QueryToken extends WrappedTokenInfo { export class QueryToken extends WrappedTokenInfo {
constructor(data: NonNullable<TokenQueryData>, logoSrc?: string) { constructor(address: string, data: NonNullable<TokenQueryData>, logoSrc?: string) {
super({ super({
chainId: CHAIN_NAME_TO_CHAIN_ID[data.chain], chainId: CHAIN_NAME_TO_CHAIN_ID[data.chain],
address: data.address, address,
decimals: data.decimals ?? DEFAULT_ERC20_DECIMALS, decimals: data.decimals ?? DEFAULT_ERC20_DECIMALS,
symbol: data.symbol ?? '', symbol: data.symbol ?? '',
name: data.name ?? '', name: data.name ?? '',
......
import graphql from 'babel-plugin-relay/macro' import gql from 'graphql-tag'
// TODO: Implemnt this as a refetchable fragment on tokenQuery when backend adds support // TODO: Implemnt this as a refetchable fragment on tokenQuery when backend adds support
export const tokenPriceQuery = graphql` gql`
query TokenPriceQuery($contract: ContractInput!, $duration: HistoryDuration!) { query TokenPrice($contract: ContractInput!, $duration: HistoryDuration!) {
tokens(contracts: [$contract]) { tokens(contracts: [$contract]) {
market(currency: USD) @required(action: LOG) { market(currency: USD) {
price { price {
value @required(action: LOG) value
} }
priceHistory(duration: $duration) { priceHistory(duration: $duration) {
timestamp @required(action: LOG) timestamp
value @required(action: LOG) value
} }
} }
} }
} }
` `
export type { TokenPriceQuery } from './__generated__/TokenPriceQuery.graphql' export type { TokenPriceQuery } from './__generated__/types-and-hooks'
import graphql from 'babel-plugin-relay/macro'
import { import {
filterStringAtom, filterStringAtom,
filterTimeAtom, filterTimeAtom,
...@@ -6,22 +5,25 @@ import { ...@@ -6,22 +5,25 @@ import {
sortMethodAtom, sortMethodAtom,
TokenSortMethod, TokenSortMethod,
} from 'components/Tokens/state' } from 'components/Tokens/state'
import gql from 'graphql-tag'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import { useEffect, useMemo, useState } from 'react' import { useMemo } from 'react'
import { fetchQuery, useLazyLoadQuery, useRelayEnvironment } from 'react-relay'
import type { Chain, TopTokens100Query } from './__generated__/TopTokens100Query.graphql' import {
import { TopTokensSparklineQuery } from './__generated__/TopTokensSparklineQuery.graphql' Chain,
import { isPricePoint, PricePoint } from './util' TopTokens100Query,
import { CHAIN_NAME_TO_CHAIN_ID, toHistoryDuration, unwrapToken } from './util' useTopTokens100Query,
useTopTokensSparklineQuery,
} from './__generated__/types-and-hooks'
import { CHAIN_NAME_TO_CHAIN_ID, isPricePoint, PricePoint, toHistoryDuration, unwrapToken } from './util'
const topTokens100Query = graphql` gql`
query TopTokens100Query($duration: HistoryDuration!, $chain: Chain!) { query TopTokens100($duration: HistoryDuration!, $chain: Chain!) {
topTokens(pageSize: 100, page: 1, chain: $chain) { topTokens(pageSize: 100, page: 1, chain: $chain) {
id @required(action: LOG) id
name name
chain @required(action: LOG) chain
address @required(action: LOG) address
symbol symbol
market(currency: USD) { market(currency: USD) {
totalValueLocked { totalValueLocked {
...@@ -48,21 +50,21 @@ const topTokens100Query = graphql` ...@@ -48,21 +50,21 @@ const topTokens100Query = graphql`
} }
` `
const tokenSparklineQuery = graphql` gql`
query TopTokensSparklineQuery($duration: HistoryDuration!, $chain: Chain!) { query TopTokensSparkline($duration: HistoryDuration!, $chain: Chain!) {
topTokens(pageSize: 100, page: 1, chain: $chain) { topTokens(pageSize: 100, page: 1, chain: $chain) {
address address
market(currency: USD) { market(currency: USD) {
priceHistory(duration: $duration) { priceHistory(duration: $duration) {
timestamp @required(action: LOG) timestamp
value @required(action: LOG) value
} }
} }
} }
} }
` `
function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) { function useSortedTokens(tokens: NonNullable<TopTokens100Query['topTokens']>) {
const sortMethod = useAtomValue(sortMethodAtom) const sortMethod = useAtomValue(sortMethodAtom)
const sortAscending = useAtomValue(sortAscendingAtom) const sortAscending = useAtomValue(sortAscendingAtom)
...@@ -91,7 +93,7 @@ function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topT ...@@ -91,7 +93,7 @@ function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topT
}, [tokens, sortMethod, sortAscending]) }, [tokens, sortMethod, sortAscending])
} }
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) { function useFilteredTokens(tokens: NonNullable<TopTokens100Query['topTokens']>) {
const filterString = useAtomValue(filterStringAtom) const filterString = useAtomValue(filterStringAtom)
const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString]) const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString])
...@@ -112,11 +114,12 @@ function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['to ...@@ -112,11 +114,12 @@ function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['to
// Number of items to render in each fetch in infinite scroll. // Number of items to render in each fetch in infinite scroll.
export const PAGE_SIZE = 20 export const PAGE_SIZE = 20
export type TopToken = NonNullable<NonNullable<TopTokens100Query['response']>['topTokens']>[number]
export type SparklineMap = { [key: string]: PricePoint[] | undefined } export type SparklineMap = { [key: string]: PricePoint[] | undefined }
export type TopToken = NonNullable<NonNullable<TopTokens100Query>['topTokens']>[number]
interface UseTopTokensReturnValue { interface UseTopTokensReturnValue {
tokens: TopToken[] | undefined tokens: TopToken[] | undefined
loadingTokens: boolean
sparklines: SparklineMap sparklines: SparklineMap
} }
...@@ -124,33 +127,27 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue { ...@@ -124,33 +127,27 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
const chainId = CHAIN_NAME_TO_CHAIN_ID[chain] const chainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const duration = toHistoryDuration(useAtomValue(filterTimeAtom)) const duration = toHistoryDuration(useAtomValue(filterTimeAtom))
const environment = useRelayEnvironment() const { data: sparklineQuery } = useTopTokensSparklineQuery({
const [sparklines, setSparklines] = useState<SparklineMap>({}) variables: { duration, chain },
useEffect(() => { })
const subscription = fetchQuery<TopTokensSparklineQuery>(environment, tokenSparklineQuery, { duration, chain })
.map((data) => ({
topTokens: data.topTokens?.map((token) => unwrapToken(chainId, token)),
}))
.subscribe({
next(data) {
const map: SparklineMap = {}
data.topTokens?.forEach(
(current) =>
current?.address && (map[current.address] = current?.market?.priceHistory?.filter(isPricePoint))
)
setSparklines(map)
},
})
return () => subscription.unsubscribe()
}, [chain, chainId, duration, environment])
useEffect(() => { const sparklines = useMemo(() => {
setSparklines({}) const unwrappedTokens = sparklineQuery?.topTokens?.map((topToken) => unwrapToken(chainId, topToken))
}, [duration]) const map: SparklineMap = {}
unwrappedTokens?.forEach(
(current) => current?.address && (map[current.address] = current?.market?.priceHistory?.filter(isPricePoint))
)
return map
}, [chainId, sparklineQuery?.topTokens])
const { topTokens } = useLazyLoadQuery<TopTokens100Query>(topTokens100Query, { duration, chain }) const { data, loading: loadingTokens } = useTopTokens100Query({
const mappedTokens = useMemo(() => topTokens?.map((token) => unwrapToken(chainId, token)) ?? [], [chainId, topTokens]) variables: { duration, chain },
})
const mappedTokens = useMemo(
() => data?.topTokens?.map((token) => unwrapToken(chainId, token)) ?? [],
[chainId, data]
)
const filteredTokens = useFilteredTokens(mappedTokens) const filteredTokens = useFilteredTokens(mappedTokens)
const sortedTokens = useSortedTokens(filteredTokens) const sortedTokens = useSortedTokens(filteredTokens)
return useMemo(() => ({ tokens: sortedTokens, sparklines }), [sortedTokens, sparklines]) return useMemo(() => ({ tokens: sortedTokens, loadingTokens, sparklines }), [loadingTokens, sortedTokens, sparklines])
} }
This diff is collapsed.
import { ApolloClient, InMemoryCache } from '@apollo/client'
import { relayStylePagination } from '@apollo/client/utilities'
const GRAPHQL_URL = process.env.REACT_APP_AWS_API_ENDPOINT
if (!GRAPHQL_URL) {
throw new Error('AWS URL MISSING FROM ENVIRONMENT')
}
export const apolloClient = new ApolloClient({
uri: GRAPHQL_URL,
headers: {
'Content-Type': 'application/json',
Origin: 'https://app.uniswap.org',
},
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
nftBalances: relayStylePagination(),
nftAssets: relayStylePagination(),
},
},
},
}),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
},
})
This diff is collapsed.
import graphql from 'babel-plugin-relay/macro' import gql from 'graphql-tag'
import { GenieCollection, Trait } from 'nft/types' import { GenieCollection, Trait } from 'nft/types'
import { useEffect } from 'react' import { useMemo } from 'react'
import { useLazyLoadQuery, useQueryLoader } from 'react-relay'
import { CollectionQuery } from './__generated__/CollectionQuery.graphql' import { NftCollection, useCollectionQuery } from '../__generated__/types-and-hooks'
const collectionQuery = graphql` gql`
query CollectionQuery($addresses: [String!]!) { query Collection($addresses: [String!]!) {
nftCollections(filter: { addresses: $addresses }) { nftCollections(filter: { addresses: $addresses }) {
edges { edges {
cursor cursor
...@@ -87,28 +86,23 @@ const collectionQuery = graphql` ...@@ -87,28 +86,23 @@ const collectionQuery = graphql`
} }
` `
export function useLoadCollectionQuery(address?: string | string[]): void { interface useCollectionReturnProps {
const [, loadQuery] = useQueryLoader(collectionQuery) data: GenieCollection
useEffect(() => { loading: boolean
if (address) {
loadQuery({ addresses: Array.isArray(address) ? address : [address] })
}
}, [address, loadQuery])
} }
// Lazy-loads an already loaded CollectionQuery. export function useCollection(address: string): useCollectionReturnProps {
// This will *not* trigger a query - that must be done from a parent component to ensure proper query coalescing and to const { data: queryData, loading } = useCollectionQuery({
// prevent waterfalling. Use useLoadCollectionQuery to trigger the query. variables: {
export function useCollectionQuery(address: string): GenieCollection { addresses: address,
const queryData = useLazyLoadQuery<CollectionQuery>( // this will suspend if not yet loaded },
collectionQuery, })
{ addresses: [address] },
{ fetchPolicy: 'store-or-network' }
)
const queryCollection = queryData.nftCollections?.edges[0]?.node const queryCollection = queryData?.nftCollections?.edges?.[0]?.node as NonNullable<NftCollection>
const market = queryCollection?.markets && queryCollection?.markets[0] const market = queryCollection?.markets?.[0]
const traits = {} as Record<string, Trait[]> const traits = useMemo(() => {
return {} as Record<string, Trait[]>
}, [])
if (queryCollection?.traits) { if (queryCollection?.traits) {
queryCollection?.traits.forEach((trait) => { queryCollection?.traits.forEach((trait) => {
if (trait.name && trait.stats) { if (trait.name && trait.stats) {
...@@ -122,42 +116,43 @@ export function useCollectionQuery(address: string): GenieCollection { ...@@ -122,42 +116,43 @@ export function useCollectionQuery(address: string): GenieCollection {
} }
}) })
} }
return { return useMemo(() => {
address, return {
isVerified: queryCollection?.isVerified ?? undefined, data: {
name: queryCollection?.name ?? undefined, address,
description: queryCollection?.description ?? undefined, isVerified: queryCollection?.isVerified,
standard: queryCollection?.nftContracts ? queryCollection?.nftContracts[0]?.standard ?? undefined : undefined, name: queryCollection?.name,
bannerImageUrl: queryCollection?.bannerImage?.url ?? undefined, description: queryCollection?.description,
stats: queryCollection?.markets standard: queryCollection?.nftContracts?.[0]?.standard,
? { bannerImageUrl: queryCollection?.bannerImage?.url,
num_owners: market?.owners ?? undefined, stats: {
floor_price: market?.floorPrice?.value ?? undefined, num_owners: market?.owners,
one_day_volume: market?.volume?.value ?? undefined, floor_price: market?.floorPrice?.value,
one_day_change: market?.volumePercentChange?.value ?? undefined, one_day_volume: market?.volume?.value,
one_day_floor_change: market?.floorPricePercentChange?.value ?? undefined, one_day_change: market?.volumePercentChange?.value,
banner_image_url: queryCollection?.bannerImage?.url ?? undefined, one_day_floor_change: market?.floorPricePercentChange?.value,
total_supply: queryCollection?.numAssets ?? undefined, banner_image_url: queryCollection?.bannerImage?.url,
total_listings: market?.listings?.value ?? undefined, total_supply: queryCollection?.numAssets,
total_volume: market?.totalVolume?.value ?? undefined, total_listings: market?.listings?.value,
} total_volume: market?.totalVolume?.value,
: {}, },
traits, traits,
marketplaceCount: queryCollection?.markets marketplaceCount: market?.marketplaces?.map((market) => {
? market?.marketplaces?.map((market) => {
return { return {
marketplace: market.marketplace?.toLowerCase() ?? '', marketplace: market.marketplace?.toLowerCase() ?? '',
count: market.listings ?? 0, count: market.listings ?? 0,
floorPrice: market.floorPrice ?? 0, floorPrice: market.floorPrice ?? 0,
} }
}) }),
: undefined, imageUrl: queryCollection?.image?.url ?? '',
imageUrl: queryCollection?.image?.url ?? '', twitterUrl: queryCollection?.twitterName,
twitterUrl: queryCollection?.twitterName ?? '', instagram: queryCollection?.instagramName,
instagram: queryCollection?.instagramName ?? undefined, discordUrl: queryCollection?.discordUrl,
discordUrl: queryCollection?.discordUrl ?? undefined, externalUrl: queryCollection?.homepageUrl,
externalUrl: queryCollection?.homepageUrl ?? undefined, rarityVerified: false, // TODO update when backend supports
rarityVerified: false, // TODO update when backend supports // isFoundation: boolean, // TODO ask backend to add
// isFoundation: boolean, // TODO ask backend to add },
} loading,
}
}, [address, loading, market, queryCollection, traits])
} }
import { parseEther } from '@ethersproject/units' import { parseEther } from '@ethersproject/units'
import graphql from 'babel-plugin-relay/macro' import gql from 'graphql-tag'
import { CollectionInfoForAsset, GenieAsset, SellOrder, TokenType } from 'nft/types' import { CollectionInfoForAsset, GenieAsset, Markets, SellOrder } from 'nft/types'
import { useEffect } from 'react' import { useMemo } from 'react'
import { useLazyLoadQuery, useQueryLoader } from 'react-relay'
import { DetailsQuery } from './__generated__/DetailsQuery.graphql' import { NftAsset, useDetailsQuery } from '../__generated__/types-and-hooks'
const detailsQuery = graphql` gql`
query DetailsQuery($address: String!, $tokenId: String!) { query Details($address: String!, $tokenId: String!) {
nftAssets(address: $address, filter: { listed: false, tokenIds: [$tokenId] }) { nftAssets(address: $address, filter: { listed: false, tokenIds: [$tokenId] }) {
edges { edges {
node { node {
...@@ -92,92 +91,87 @@ const detailsQuery = graphql` ...@@ -92,92 +91,87 @@ const detailsQuery = graphql`
} }
` `
export function useLoadDetailsQuery(address?: string, tokenId?: string): void { export function useNftAssetDetails(
const [, loadQuery] = useQueryLoader(detailsQuery) address: string,
useEffect(() => { tokenId: string
if (address && tokenId) { ): { data: [GenieAsset, CollectionInfoForAsset]; loading: boolean } {
loadQuery({ address, tokenId }) const { data: queryData, loading } = useDetailsQuery({
} variables: {
}, [address, tokenId, loadQuery])
}
export function useDetailsQuery(address: string, tokenId: string): [GenieAsset, CollectionInfoForAsset] | undefined {
const queryData = useLazyLoadQuery<DetailsQuery>(
detailsQuery,
{
address, address,
tokenId, tokenId,
}, },
{ fetchPolicy: 'store-or-network' } })
)
const asset = queryData.nftAssets?.edges[0]?.node const asset = queryData?.nftAssets?.edges[0]?.node as NonNullable<NftAsset> | undefined
const collection = asset?.collection const collection = asset?.collection
const listing = asset?.listings?.edges[0]?.node const listing = asset?.listings?.edges[0]?.node
const ethPrice = parseEther(listing?.price?.value?.toString() ?? '0').toString() const ethPrice = parseEther(listing?.price?.value?.toString() ?? '0').toString()
return [ return useMemo(
{ () => ({
id: asset?.id, data: [
address, {
notForSale: asset?.listings === null, id: asset?.id,
collectionName: asset?.collection?.name ?? undefined, address,
collectionSymbol: asset?.collection?.image?.url ?? undefined, notForSale: asset?.listings === null,
imageUrl: asset?.image?.url ?? undefined, collectionName: asset?.collection?.name,
animationUrl: asset?.animationUrl ?? undefined, collectionSymbol: asset?.collection?.image?.url,
// todo: fix the back/frontend discrepency here and drop the any imageUrl: asset?.image?.url,
marketplace: listing?.marketplace.toLowerCase() as any, animationUrl: asset?.animationUrl,
name: asset?.name ?? undefined, marketplace: listing?.marketplace.toLowerCase() as unknown as Markets,
priceInfo: { name: asset?.name,
ETHPrice: ethPrice, priceInfo: {
baseAsset: 'ETH', ETHPrice: ethPrice,
baseDecimals: '18', baseAsset: 'ETH',
basePrice: ethPrice, baseDecimals: '18',
}, basePrice: ethPrice,
susFlag: asset?.suspiciousFlag ?? undefined, },
sellorders: asset?.listings?.edges.map((listingNode) => { susFlag: asset?.suspiciousFlag,
return { sellorders: asset?.listings?.edges.map((listingNode) => {
...listingNode.node, return {
protocolParameters: listingNode.node.protocolParameters ...listingNode.node,
? JSON.parse(listingNode.node.protocolParameters.toString()) protocolParameters: listingNode.node.protocolParameters
: undefined, ? JSON.parse(listingNode.node.protocolParameters.toString())
} as SellOrder : undefined,
}), } as SellOrder
smallImageUrl: asset?.smallImage?.url ?? undefined, }),
tokenId, smallImageUrl: asset?.smallImage?.url,
tokenType: (asset?.collection?.nftContracts && asset?.collection.nftContracts[0]?.standard) as TokenType, tokenId,
collectionIsVerified: asset?.collection?.isVerified ?? undefined, tokenType: asset?.collection?.nftContracts?.[0]?.standard,
rarity: { collectionIsVerified: asset?.collection?.isVerified,
primaryProvider: 'Rarity Sniper', // TODO update when backend adds more providers rarity: {
providers: asset?.rarities primaryProvider: 'Rarity Sniper', // TODO update when backend adds more providers
? asset?.rarities?.map((rarity) => { providers: asset?.rarities?.map((rarity) => {
return { return {
rank: rarity.rank ?? undefined, rank: rarity.rank,
score: rarity.score ?? undefined, score: rarity.score,
provider: 'Rarity Sniper', provider: 'Rarity Sniper',
} }
}) }),
: undefined, },
}, ownerAddress: asset?.ownerAddress,
owner: { address: asset?.ownerAddress ?? '' }, creator: {
creator: { profile_img_url: asset?.creator?.profileImage?.url ?? '',
profile_img_url: asset?.creator?.profileImage?.url ?? '', address: asset?.creator?.address ?? '',
address: asset?.creator?.address ?? '', },
}, metadataUrl: asset?.metadataUrl ?? '',
metadataUrl: asset?.metadataUrl ?? '', traits: asset?.traits?.map((trait) => {
traits: asset?.traits?.map((trait) => { return { trait_type: trait.name ?? '', trait_value: trait.value ?? '' }
return { trait_type: trait.name ?? '', trait_value: trait.value ?? '' } }),
}), },
}, {
{ collectionDescription: collection?.description,
collectionDescription: collection?.description ?? undefined, collectionImageUrl: collection?.image?.url,
collectionImageUrl: collection?.image?.url ?? undefined, collectionName: collection?.name,
collectionName: collection?.name ?? undefined, isVerified: collection?.isVerified,
isVerified: collection?.isVerified ?? undefined, totalSupply: collection?.numAssets,
totalSupply: collection?.numAssets ?? undefined, twitterUrl: collection?.twitterName,
twitterUrl: collection?.twitterName ?? undefined, discordUrl: collection?.discordUrl,
discordUrl: collection?.discordUrl ?? undefined, externalUrl: collection?.homepageUrl,
externalUrl: collection?.homepageUrl ?? undefined, },
}, ],
] loading,
}),
[address, asset, collection, ethPrice, listing?.marketplace, loading, tokenId]
)
} }
import graphql from 'babel-plugin-relay/macro'
import { parseEther } from 'ethers/lib/utils' import { parseEther } from 'ethers/lib/utils'
import { DEFAULT_WALLET_ASSET_QUERY_AMOUNT } from 'nft/components/profile/view/ProfilePage' import gql from 'graphql-tag'
import { WalletAsset } from 'nft/types' import { GenieCollection, WalletAsset } from 'nft/types'
import { wrapScientificNotation } from 'nft/utils' import { wrapScientificNotation } from 'nft/utils'
import { useEffect } from 'react' import { useCallback, useMemo } from 'react'
import { useLazyLoadQuery, usePaginationFragment, useQueryLoader } from 'react-relay'
import { NftBalancePaginationQuery } from './__generated__/NftBalancePaginationQuery.graphql' import { NftAsset, useNftBalanceQuery } from '../__generated__/types-and-hooks'
import { NftBalanceQuery } from './__generated__/NftBalanceQuery.graphql'
import { NftBalanceQuery_nftBalances$data } from './__generated__/NftBalanceQuery_nftBalances.graphql'
const nftBalancePaginationQuery = graphql` gql`
fragment NftBalanceQuery_nftBalances on Query @refetchable(queryName: "NftBalancePaginationQuery") { query NftBalance(
$ownerAddress: String!
$filter: NftBalancesFilterInput
$first: Int
$after: String
$last: Int
$before: String
) {
nftBalances( nftBalances(
ownerAddress: $ownerAddress ownerAddress: $ownerAddress
filter: $filter filter: $filter
...@@ -19,7 +22,7 @@ const nftBalancePaginationQuery = graphql` ...@@ -19,7 +22,7 @@ const nftBalancePaginationQuery = graphql`
after: $after after: $after
last: $last last: $last
before: $before before: $before
) @connection(key: "NftBalanceQuery_nftBalances") { ) {
edges { edges {
node { node {
ownedAsset { ownedAsset {
...@@ -99,43 +102,7 @@ const nftBalancePaginationQuery = graphql` ...@@ -99,43 +102,7 @@ const nftBalancePaginationQuery = graphql`
} }
` `
const nftBalanceQuery = graphql` export function useNftBalance(
query NftBalanceQuery(
$ownerAddress: String!
$filter: NftBalancesFilterInput
$first: Int
$after: String
$last: Int
$before: String
) {
...NftBalanceQuery_nftBalances
}
`
type NftBalanceQueryAsset = NonNullable<
NonNullable<NonNullable<NftBalanceQuery_nftBalances$data['nftBalances']>['edges']>[number]
>
export function useLoadNftBalanceQuery(
ownerAddress?: string,
collectionAddress?: string | string[],
tokenId?: string
): void {
const [, loadQuery] = useQueryLoader(nftBalanceQuery)
useEffect(() => {
if (ownerAddress) {
loadQuery({
ownerAddress,
filter: tokenId
? { assets: [{ address: collectionAddress, tokenId }] }
: { addresses: Array.isArray(collectionAddress) ? collectionAddress : [collectionAddress] },
first: tokenId ? 1 : DEFAULT_WALLET_ASSET_QUERY_AMOUNT,
})
}
}, [ownerAddress, loadQuery, collectionAddress, tokenId])
}
export function useNftBalanceQuery(
ownerAddress: string, ownerAddress: string,
collectionFilters?: string[], collectionFilters?: string[],
assetsFilter?: { address: string; tokenId: string }[], assetsFilter?: { address: string; tokenId: string }[],
...@@ -144,9 +111,8 @@ export function useNftBalanceQuery( ...@@ -144,9 +111,8 @@ export function useNftBalanceQuery(
last?: number, last?: number,
before?: string before?: string
) { ) {
const queryData = useLazyLoadQuery<NftBalanceQuery>( const { data, loading, fetchMore } = useNftBalanceQuery({
nftBalanceQuery, variables: {
{
ownerAddress, ownerAddress,
filter: filter:
assetsFilter && assetsFilter.length > 0 assetsFilter && assetsFilter.length > 0
...@@ -161,14 +127,21 @@ export function useNftBalanceQuery( ...@@ -161,14 +127,21 @@ export function useNftBalanceQuery(
last, last,
before, before,
}, },
{ fetchPolicy: 'store-or-network' } })
)
const { data, hasNext, loadNext, isLoadingNext } = usePaginationFragment<NftBalancePaginationQuery, any>( const hasNext = data?.nftBalances?.pageInfo?.hasNextPage
nftBalancePaginationQuery, const loadMore = useCallback(
queryData () =>
fetchMore({
variables: {
after: data?.nftBalances?.pageInfo?.endCursor,
},
}),
[data?.nftBalances?.pageInfo?.endCursor, fetchMore]
) )
const walletAssets: WalletAsset[] = data.nftBalances?.edges?.map((queryAsset: NftBalanceQueryAsset) => {
const asset = queryAsset.node.ownedAsset const walletAssets: WalletAsset[] | undefined = data?.nftBalances?.edges?.map((queryAsset) => {
const asset = queryAsset?.node.ownedAsset as NonNullable<NftAsset>
const ethPrice = parseEther(wrapScientificNotation(asset?.listings?.edges[0]?.node.price.value ?? 0)).toString() const ethPrice = parseEther(wrapScientificNotation(asset?.listings?.edges[0]?.node.price.value ?? 0)).toString()
return { return {
id: asset?.id, id: asset?.id,
...@@ -177,35 +150,32 @@ export function useNftBalanceQuery( ...@@ -177,35 +150,32 @@ export function useNftBalanceQuery(
notForSale: asset?.listings?.edges?.length === 0, notForSale: asset?.listings?.edges?.length === 0,
animationUrl: asset?.animationUrl, animationUrl: asset?.animationUrl,
susFlag: asset?.suspiciousFlag, susFlag: asset?.suspiciousFlag,
priceInfo: asset?.listings priceInfo: {
? { ETHPrice: ethPrice,
ETHPrice: ethPrice, baseAsset: 'ETH',
baseAsset: 'ETH', baseDecimals: '18',
baseDecimals: '18', basePrice: ethPrice,
basePrice: ethPrice, },
}
: undefined,
name: asset?.name, name: asset?.name,
tokenId: asset?.tokenId, tokenId: asset?.tokenId,
asset_contract: { asset_contract: {
address: asset?.collection?.nftContracts?.[0]?.address, address: asset?.collection?.nftContracts?.[0]?.address,
schema_name: asset?.collection?.nftContracts?.[0]?.standard, tokenType: asset?.collection?.nftContracts?.[0]?.standard,
name: asset?.collection?.name, name: asset?.collection?.name,
description: asset?.description, description: asset?.description,
image_url: asset?.collection?.image?.url, image_url: asset?.collection?.image?.url,
payout_address: queryAsset?.node?.listingFees?.[0]?.payoutAddress, payout_address: queryAsset?.node?.listingFees?.[0]?.payoutAddress,
tokenType: asset?.collection?.nftContracts?.[0].standard,
}, },
collection: asset?.collection, collection: asset?.collection as unknown as GenieCollection,
collectionIsVerified: asset?.collection?.isVerified, collectionIsVerified: asset?.collection?.isVerified,
lastPrice: queryAsset.node.lastPrice?.value, lastPrice: queryAsset.node.lastPrice?.value,
floorPrice: asset?.collection?.markets?.[0]?.floorPrice?.value, floorPrice: asset?.collection?.markets?.[0]?.floorPrice?.value,
basisPoints: queryAsset?.node?.listingFees?.[0]?.basisPoints ?? 0 / 10000, basisPoints: queryAsset?.node?.listingFees?.[0]?.basisPoints ?? 0 / 10000,
listing_date: asset?.listings?.edges?.[0]?.node?.createdAt, listing_date: asset?.listings?.edges?.[0]?.node?.createdAt?.toString(),
date_acquired: queryAsset.node.lastPrice?.timestamp, date_acquired: queryAsset.node.lastPrice?.timestamp?.toString(),
sellOrders: asset?.listings?.edges.map((edge: any) => edge.node), sellOrders: asset?.listings?.edges.map((edge: any) => edge.node),
floor_sell_order_price: asset?.listings?.edges?.[0]?.node?.price?.value, floor_sell_order_price: asset?.listings?.edges?.[0]?.node?.price?.value,
} }
}) })
return { walletAssets, hasNext, isLoadingNext, loadNext } return useMemo(() => ({ walletAssets, hasNext, loadMore, loading }), [hasNext, loadMore, loading, walletAssets])
} }
...@@ -2,7 +2,7 @@ import { SupportedChainId } from 'constants/chains' ...@@ -2,7 +2,7 @@ import { SupportedChainId } from 'constants/chains'
import { ZERO_ADDRESS } from 'constants/misc' import { ZERO_ADDRESS } from 'constants/misc'
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens' import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { Chain, HistoryDuration } from './__generated__/TopTokens100Query.graphql' import { Chain, HistoryDuration } from './__generated__/types-and-hooks'
export enum TimePeriod { export enum TimePeriod {
HOUR, HOUR,
...@@ -15,15 +15,15 @@ export enum TimePeriod { ...@@ -15,15 +15,15 @@ export enum TimePeriod {
export function toHistoryDuration(timePeriod: TimePeriod): HistoryDuration { export function toHistoryDuration(timePeriod: TimePeriod): HistoryDuration {
switch (timePeriod) { switch (timePeriod) {
case TimePeriod.HOUR: case TimePeriod.HOUR:
return 'HOUR' return HistoryDuration.Hour
case TimePeriod.DAY: case TimePeriod.DAY:
return 'DAY' return HistoryDuration.Day
case TimePeriod.WEEK: case TimePeriod.WEEK:
return 'WEEK' return HistoryDuration.Week
case TimePeriod.MONTH: case TimePeriod.MONTH:
return 'MONTH' return HistoryDuration.Month
case TimePeriod.YEAR: case TimePeriod.YEAR:
return 'YEAR' return HistoryDuration.Year
} }
} }
...@@ -34,16 +34,16 @@ export function isPricePoint(p: PricePoint | null): p is PricePoint { ...@@ -34,16 +34,16 @@ export function isPricePoint(p: PricePoint | null): p is PricePoint {
} }
export const CHAIN_ID_TO_BACKEND_NAME: { [key: number]: Chain } = { export const CHAIN_ID_TO_BACKEND_NAME: { [key: number]: Chain } = {
[SupportedChainId.MAINNET]: 'ETHEREUM', [SupportedChainId.MAINNET]: Chain.Ethereum,
[SupportedChainId.GOERLI]: 'ETHEREUM_GOERLI', [SupportedChainId.GOERLI]: Chain.EthereumGoerli,
[SupportedChainId.POLYGON]: 'POLYGON', [SupportedChainId.POLYGON]: Chain.Polygon,
[SupportedChainId.POLYGON_MUMBAI]: 'POLYGON', [SupportedChainId.POLYGON_MUMBAI]: Chain.Polygon,
[SupportedChainId.CELO]: 'CELO', [SupportedChainId.CELO]: Chain.Celo,
[SupportedChainId.CELO_ALFAJORES]: 'CELO', [SupportedChainId.CELO_ALFAJORES]: Chain.Celo,
[SupportedChainId.ARBITRUM_ONE]: 'ARBITRUM', [SupportedChainId.ARBITRUM_ONE]: Chain.Arbitrum,
[SupportedChainId.ARBITRUM_RINKEBY]: 'ARBITRUM', [SupportedChainId.ARBITRUM_RINKEBY]: Chain.Arbitrum,
[SupportedChainId.OPTIMISM]: 'OPTIMISM', [SupportedChainId.OPTIMISM]: Chain.Optimism,
[SupportedChainId.OPTIMISM_GOERLI]: 'OPTIMISM', [SupportedChainId.OPTIMISM_GOERLI]: Chain.Optimism,
} }
export function chainIdToBackendName(chainId: number | undefined) { export function chainIdToBackendName(chainId: number | undefined) {
...@@ -53,15 +53,15 @@ export function chainIdToBackendName(chainId: number | undefined) { ...@@ -53,15 +53,15 @@ export function chainIdToBackendName(chainId: number | undefined) {
} }
const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: Chain } = { const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: Chain } = {
ethereum: 'ETHEREUM', ethereum: Chain.Ethereum,
polygon: 'POLYGON', polygon: Chain.Polygon,
celo: 'CELO', celo: Chain.Celo,
arbitrum: 'ARBITRUM', arbitrum: Chain.Arbitrum,
optimism: 'OPTIMISM', optimism: Chain.Optimism,
} }
export function validateUrlChainParam(chainName: string | undefined) { export function validateUrlChainParam(chainName: string | undefined) {
return chainName && URL_CHAIN_PARAM_TO_BACKEND[chainName] ? URL_CHAIN_PARAM_TO_BACKEND[chainName] : 'ETHEREUM' return chainName && URL_CHAIN_PARAM_TO_BACKEND[chainName] ? URL_CHAIN_PARAM_TO_BACKEND[chainName] : Chain.Ethereum
} }
export const CHAIN_NAME_TO_CHAIN_ID: { [key: string]: SupportedChainId } = { export const CHAIN_NAME_TO_CHAIN_ID: { [key: string]: SupportedChainId } = {
...@@ -72,7 +72,7 @@ export const CHAIN_NAME_TO_CHAIN_ID: { [key: string]: SupportedChainId } = { ...@@ -72,7 +72,7 @@ export const CHAIN_NAME_TO_CHAIN_ID: { [key: string]: SupportedChainId } = {
OPTIMISM: SupportedChainId.OPTIMISM, OPTIMISM: SupportedChainId.OPTIMISM,
} }
export const BACKEND_CHAIN_NAMES: Chain[] = ['ETHEREUM', 'POLYGON', 'OPTIMISM', 'ARBITRUM', 'CELO'] export const BACKEND_CHAIN_NAMES: Chain[] = [Chain.Ethereum, Chain.Polygon, Chain.Optimism, Chain.Arbitrum, Chain.Celo]
export function isValidBackendChainName(chainName: string | undefined): chainName is Chain { export function isValidBackendChainName(chainName: string | undefined): chainName is Chain {
if (!chainName) return false if (!chainName) return false
...@@ -95,7 +95,11 @@ export function getTokenDetailsURL(address: string, chainName?: Chain, chainId?: ...@@ -95,7 +95,11 @@ export function getTokenDetailsURL(address: string, chainName?: Chain, chainId?:
} }
} }
export function unwrapToken<T extends { address: string | null } | null>(chainId: number, token: T): T { export function unwrapToken<
T extends {
address?: string | null | undefined
} | null
>(chainId: number, token: T): T {
if (!token?.address) return token if (!token?.address) return token
const address = token.address.toLowerCase() const address = token.address.toLowerCase()
......
import graphql from 'babel-plugin-relay/macro' import { useQuery } from '@apollo/client'
import useInterval from 'lib/hooks/useInterval' import gql from 'graphql-tag'
import { useCallback, useEffect, useState } from 'react' import { useMemo } from 'react'
import { fetchQuery } from 'react-relay'
import { useAppSelector } from 'state/hooks'
import type { import { AllV3TicksQuery } from './__generated__/types-and-hooks'
AllV3TicksQuery as AllV3TicksQueryType, import { apolloClient } from './apollo'
AllV3TicksQuery$data,
} from './__generated__/AllV3TicksQuery.graphql'
import environment from './RelayEnvironment'
const query = graphql` const query = gql`
query AllV3TicksQuery($poolAddress: String!, $skip: Int!) { query AllV3Ticks($poolAddress: String!, $skip: Int!) {
ticks(first: 1000, skip: $skip, where: { poolAddress: $poolAddress }, orderBy: tickIdx) { ticks(first: 1000, skip: $skip, where: { poolAddress: $poolAddress }, orderBy: tickIdx) {
tick: tickIdx tick: tickIdx
liquidityNet liquidityNet
...@@ -21,33 +16,29 @@ const query = graphql` ...@@ -21,33 +16,29 @@ const query = graphql`
} }
` `
export type Ticks = AllV3TicksQuery$data['ticks'] export type Ticks = AllV3TicksQuery['ticks']
export type TickData = Ticks[number] export type TickData = Ticks[number]
export default function useAllV3TicksQuery(poolAddress: string | undefined, skip: number, interval: number) { export default function useAllV3TicksQuery(poolAddress: string | undefined, skip: number, interval: number) {
const [data, setData] = useState<AllV3TicksQuery$data | null>(null) const {
const [error, setError] = useState<any>(null) data,
const [isLoading, setIsLoading] = useState(true) loading: isLoading,
const chainId = useAppSelector((state) => state.application.chainId) error,
} = useQuery(query, {
variables: {
poolAddress: poolAddress?.toLowerCase(),
skip,
},
pollInterval: interval,
client: apolloClient,
})
const refreshData = useCallback(() => { return useMemo(
if (poolAddress && chainId) { () => ({
fetchQuery<AllV3TicksQueryType>(environment, query, { error,
poolAddress: poolAddress.toLowerCase(), isLoading,
skip, data,
}).subscribe({ }),
next: setData, [data, error, isLoading]
error: setError, )
complete: () => setIsLoading(false),
})
} else {
setIsLoading(false)
}
}, [poolAddress, skip, chainId])
// Trigger fetch on first load
useEffect(refreshData, [refreshData, poolAddress, skip])
useInterval(refreshData, interval, true)
return { error, isLoading, data }
} }
import graphql from 'babel-plugin-relay/macro' import { ApolloError, useQuery } from '@apollo/client'
import useInterval from 'lib/hooks/useInterval' import gql from 'graphql-tag'
import { useCallback, useEffect, useState } from 'react' import { useMemo } from 'react'
import { fetchQuery } from 'react-relay'
import { useAppSelector } from 'state/hooks'
import type { import { FeeTierDistributionQuery } from './__generated__/types-and-hooks'
FeeTierDistributionQuery as FeeTierDistributionQueryType, import { apolloClient } from './apollo'
FeeTierDistributionQuery$data,
} from './__generated__/FeeTierDistributionQuery.graphql'
import environment from './RelayEnvironment'
const query = graphql` const query = gql`
query FeeTierDistributionQuery($token0: String!, $token1: String!) { query FeeTierDistribution($token0: String!, $token1: String!) {
_meta { _meta {
block { block {
number number
...@@ -42,28 +37,26 @@ export default function useFeeTierDistributionQuery( ...@@ -42,28 +37,26 @@ export default function useFeeTierDistributionQuery(
token0: string | undefined, token0: string | undefined,
token1: string | undefined, token1: string | undefined,
interval: number interval: number
) { ): { error: ApolloError | undefined; isLoading: boolean; data: FeeTierDistributionQuery } {
const [data, setData] = useState<FeeTierDistributionQuery$data | null>(null) const {
const [error, setError] = useState<any>(null) data,
const [isLoading, setIsLoading] = useState(true) loading: isLoading,
const chainId = useAppSelector((state) => state.application.chainId) error,
} = useQuery(query, {
variables: {
token0: token0?.toLowerCase(),
token1: token1?.toLowerCase(),
},
pollInterval: interval,
client: apolloClient,
})
const refreshData = useCallback(() => { return useMemo(
if (token0 && token1 && chainId) { () => ({
fetchQuery<FeeTierDistributionQueryType>(environment, query, { error,
token0: token0.toLowerCase(), isLoading,
token1: token1.toLowerCase(), data,
}).subscribe({ }),
next: setData, [data, error, isLoading]
error: setError, )
complete: () => setIsLoading(false),
})
}
}, [token0, token1, chainId])
// Trigger fetch on first load
useEffect(refreshData, [refreshData, token0, token1])
useInterval(refreshData, interval, true)
return { error, isLoading, data }
} }
import { Environment, Network, RecordSource, Store } from 'relay-runtime'
import fetchGraphQL from './fetchGraphQL'
// Export a singleton instance of Relay Environment configured with our network function:
export default new Environment({
network: Network.create(fetchGraphQL),
store: new Store(new RecordSource()),
})
This diff is collapsed.
/** import { ApolloClient, ApolloLink, concat, HttpLink, InMemoryCache } from '@apollo/client'
* Helpful Resources
* https://github.com/sibelius/create-react-app-relay-modern/blob/master/src/relay/fetchQuery.js
* https://github.com/relay-tools/relay-compiler-language-typescript/blob/master/example/ts/app.tsx
*/
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { Variables } from 'react-relay'
import { GraphQLResponse, ObservableFromValue, RequestParameters } from 'relay-runtime'
import store, { AppState } from '../../state/index' import store, { AppState } from '../../state/index'
...@@ -23,31 +16,25 @@ const CHAIN_SUBGRAPH_URL: Record<number, string> = { ...@@ -23,31 +16,25 @@ const CHAIN_SUBGRAPH_URL: Record<number, string> = {
[SupportedChainId.CELO]: 'https://api.thegraph.com/subgraphs/name/jesse-sawa/uniswap-celo', [SupportedChainId.CELO]: 'https://api.thegraph.com/subgraphs/name/jesse-sawa/uniswap-celo',
} }
const headers = { const httpLink = new HttpLink({ uri: CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET] })
Accept: 'application/json',
'Content-type': 'application/json',
}
// Define a function that fetches the results of a request (query/mutation/etc) // This middleware will allow us to dynamically update the uri for the requests based off chainId
// and returns its results as a Promise: // For more information: https://www.apollographql.com/docs/react/networking/advanced-http-networking/
const fetchQuery = (params: RequestParameters, variables: Variables): ObservableFromValue<GraphQLResponse> => { const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const chainId = (store.getState() as AppState).application.chainId const chainId = (store.getState() as AppState).application.chainId
const subgraphUrl = operation.setContext(() => ({
chainId && CHAIN_SUBGRAPH_URL[chainId] ? CHAIN_SUBGRAPH_URL[chainId] : CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET] uri:
chainId && CHAIN_SUBGRAPH_URL[chainId]
const body = JSON.stringify({ ? CHAIN_SUBGRAPH_URL[chainId]
query: params.text, // GraphQL text from input : CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET],
variables, }))
})
const response = fetch(subgraphUrl, { return forward(operation)
method: 'POST', })
headers,
body,
}).then((res) => res.json())
return response
}
export default fetchQuery export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: concat(authMiddleware, httpLink),
})
...@@ -165,7 +165,7 @@ function useAllV3Ticks( ...@@ -165,7 +165,7 @@ function useAllV3Ticks(
): { ): {
isLoading: boolean isLoading: boolean
error: unknown error: unknown
ticks: readonly TickData[] | undefined ticks: TickData[] | undefined
} { } {
const useSubgraph = currencyA ? !CHAIN_IDS_MISSING_SUBGRAPH_DATA.includes(currencyA.chainId) : true const useSubgraph = currencyA ? !CHAIN_IDS_MISSING_SUBGRAPH_DATA.includes(currencyA.chainId) : true
......
...@@ -3,16 +3,16 @@ import 'inter-ui' ...@@ -3,16 +3,16 @@ import 'inter-ui'
import 'polyfills' import 'polyfills'
import 'components/analytics' import 'components/analytics'
import { ApolloProvider } from '@apollo/client'
import * as Sentry from '@sentry/react' import * as Sentry from '@sentry/react'
import { FeatureFlagsProvider } from 'featureFlags' import { FeatureFlagsProvider } from 'featureFlags'
import RelayEnvironment from 'graphql/data/RelayEnvironment' import { apolloClient } from 'graphql/data/apollo'
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber' import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
import { MulticallUpdater } from 'lib/state/multicall' import { MulticallUpdater } from 'lib/state/multicall'
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from 'react-query' import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { RelayEnvironmentProvider } from 'react-relay'
import { HashRouter } from 'react-router-dom' import { HashRouter } from 'react-router-dom'
import { isProductionEnv } from 'utils/env' import { isProductionEnv } from 'utils/env'
...@@ -66,7 +66,7 @@ createRoot(container).render( ...@@ -66,7 +66,7 @@ createRoot(container).render(
<HashRouter> <HashRouter>
<LanguageProvider> <LanguageProvider>
<Web3Provider> <Web3Provider>
<RelayEnvironmentProvider environment={RelayEnvironment}> <ApolloProvider client={apolloClient}>
<BlockNumberProvider> <BlockNumberProvider>
<Updaters /> <Updaters />
<ThemeProvider> <ThemeProvider>
...@@ -74,7 +74,7 @@ createRoot(container).render( ...@@ -74,7 +74,7 @@ createRoot(container).render(
<App /> <App />
</ThemeProvider> </ThemeProvider>
</BlockNumberProvider> </BlockNumberProvider>
</RelayEnvironmentProvider> </ApolloProvider>
</Web3Provider> </Web3Provider>
</LanguageProvider> </LanguageProvider>
</HashRouter> </HashRouter>
......
...@@ -71,7 +71,7 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false ...@@ -71,7 +71,7 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
for (const listing of asset.newListings) { for (const listing of asset.newListings) {
if (!listing.price) listingsMissingPrice.push([asset, listing]) if (!listing.price) listingsMissingPrice.push([asset, listing])
else if (isNaN(listing.price) || listing.price < 0) invalidPrices.push([asset, listing]) else if (isNaN(listing.price) || listing.price < 0) invalidPrices.push([asset, listing])
else if (listing.price < asset.floorPrice && !listing.overrideFloorPrice) else if (listing.price < (asset?.floorPrice ?? 0) && !listing.overrideFloorPrice)
listingsBelowFloor.push([asset, listing]) listingsBelowFloor.push([asset, listing])
else if (asset.floor_sell_order_price && listing.price > asset.floor_sell_order_price) else if (asset.floor_sell_order_price && listing.price > asset.floor_sell_order_price)
listingsAboveSellOrderFloor.push([asset, listing]) listingsAboveSellOrderFloor.push([asset, listing])
......
...@@ -88,7 +88,7 @@ export const ListingSection = ({ ...@@ -88,7 +88,7 @@ export const ListingSection = ({
return ( return (
<Column key={index} gap="8"> <Column key={index} gap="8">
<Row> <Row>
{row.images.map((image, index) => { {row.images?.map((image, index) => {
return ( return (
<Box <Box
as="img" as="img"
......
...@@ -58,14 +58,15 @@ export async function approveCollectionRow( ...@@ -58,14 +58,15 @@ export async function approveCollectionRow(
: marketplace.name === 'X2Y2' : marketplace.name === 'X2Y2'
? X2Y2_TRANSFER_CONTRACT ? X2Y2_TRANSFER_CONTRACT
: looksRareAddress : looksRareAddress
await approveCollection(spender ?? '', collectionAddress, signer, (newStatus: ListingStatus) => !!collectionAddress &&
updateStatus({ (await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
listing: collectionRow, updateStatus({
newStatus, listing: collectionRow,
rows: collectionsRequiringApproval, newStatus,
setRows: setCollectionsRequiringApproval as Dispatch<AssetRow[]>, rows: collectionsRequiringApproval,
}) setRows: setCollectionsRequiringApproval as Dispatch<AssetRow[]>,
) })
))
if (collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) pauseAllRows() if (collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) pauseAllRows()
} }
...@@ -127,7 +128,7 @@ export const getTotalEthValue = (sellAssets: WalletAsset[]) => { ...@@ -127,7 +128,7 @@ export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
// LooksRare is a unique case where creator royalties are a flat 0.5% or 50 basis points // LooksRare is a unique case where creator royalties are a flat 0.5% or 50 basis points
const maxFee = const maxFee =
maxListing.marketplace.fee + maxListing.marketplace.fee +
(maxListing.marketplace.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints) / 100 (maxListing.marketplace.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset?.basisPoints ?? 0) / 100
return total + (maxListing.price ?? 0) - (maxListing.price ?? 0) * (maxFee / 100) return total + (maxListing.price ?? 0) - (maxListing.price ?? 0) * (maxFee / 100)
} }
return total return total
......
...@@ -2,6 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber' ...@@ -2,6 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import clsx from 'clsx' import clsx from 'clsx'
import { OpacityHoverState } from 'components/Common' import { OpacityHoverState } from 'components/Common'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { import {
...@@ -16,7 +17,7 @@ import { ...@@ -16,7 +17,7 @@ import {
import { body, bodySmall, buttonTextMedium, subhead } from 'nft/css/common.css' import { body, bodySmall, buttonTextMedium, subhead } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useIsMobile } from 'nft/hooks' import { useIsMobile } from 'nft/hooks'
import { GenieAsset, Rarity, TokenType, UniformAspectRatio, UniformAspectRatios, WalletAsset } from 'nft/types' import { GenieAsset, Rarity, UniformAspectRatio, UniformAspectRatios, WalletAsset } from 'nft/types'
import { fallbackProvider, isAudio, isVideo, putCommas } from 'nft/utils' import { fallbackProvider, isAudio, isVideo, putCommas } from 'nft/utils'
import { floorFormatter } from 'nft/utils/numbers' import { floorFormatter } from 'nft/utils/numbers'
import { import {
...@@ -579,8 +580,7 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => { ...@@ -579,8 +580,7 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
return !!asset.name ? asset.name : `#${asset.tokenId}` return !!asset.name ? asset.name : `#${asset.tokenId}`
} }
const shouldShowUserListedPrice = const shouldShowUserListedPrice = !asset.notForSale && asset.asset_contract.tokenType !== NftStandard.Erc1155
!!asset.floor_sell_order_price && !asset.notForSale && asset.asset_contract.tokenType !== TokenType.ERC1155
return ( return (
<Box overflow="hidden" width="full" flexWrap="nowrap"> <Box overflow="hidden" width="full" flexWrap="nowrap">
...@@ -605,7 +605,9 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => { ...@@ -605,7 +605,9 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
{asset.susFlag && <Suspicious />} {asset.susFlag && <Suspicious />}
</Row> </Row>
<TruncatedTextRow className={buttonTextMedium} style={{ color: themeVars.colors.textPrimary }}> <TruncatedTextRow className={buttonTextMedium} style={{ color: themeVars.colors.textPrimary }}>
{shouldShowUserListedPrice ? `${floorFormatter(asset.floor_sell_order_price)} ETH` : ' '} {shouldShowUserListedPrice && asset.floor_sell_order_price
? `${floorFormatter(asset.floor_sell_order_price)} ETH`
: ' '}
</TruncatedTextRow> </TruncatedTextRow>
</Box> </Box>
) )
......
...@@ -4,10 +4,11 @@ import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics' ...@@ -4,10 +4,11 @@ import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
import { EventName, PageName } from '@uniswap/analytics-events' import { EventName, PageName } from '@uniswap/analytics-events'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { bodySmall } from 'nft/css/common.css' import { bodySmall } from 'nft/css/common.css'
import { useBag } from 'nft/hooks' import { useBag } from 'nft/hooks'
import { GenieAsset, isPooledMarket, TokenType, UniformAspectRatio } from 'nft/types' import { GenieAsset, isPooledMarket, UniformAspectRatio } from 'nft/types'
import { formatWeiToDecimal, rarityProviderLogo } from 'nft/utils' import { formatWeiToDecimal, rarityProviderLogo } from 'nft/utils'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -212,7 +213,7 @@ export const CollectionAsset = ({ ...@@ -212,7 +213,7 @@ export const CollectionAsset = ({
</Card.SecondaryInfo> </Card.SecondaryInfo>
{isPooledMarket(asset.marketplace) && <Card.Pool />} {isPooledMarket(asset.marketplace) && <Card.Pool />}
</Card.SecondaryDetails> </Card.SecondaryDetails>
{asset.tokenType !== TokenType.ERC1155 && asset.marketplace && ( {asset.tokenType !== NftStandard.Erc1155 && asset.marketplace && (
<Card.MarketplaceIcon marketplace={asset.marketplace} /> <Card.MarketplaceIcon marketplace={asset.marketplace} />
)} )}
</Card.SecondaryRow> </Card.SecondaryRow>
......
...@@ -5,13 +5,8 @@ import { useWeb3React } from '@web3-react/core' ...@@ -5,13 +5,8 @@ import { useWeb3React } from '@web3-react/core'
import clsx from 'clsx' import clsx from 'clsx'
import { OpacityHoverState } from 'components/Common' import { OpacityHoverState } from 'components/Common'
import { parseEther } from 'ethers/lib/utils' import { parseEther } from 'ethers/lib/utils'
import { NftAssetTraitInput, NftMarketplace } from 'graphql/data/nft/__generated__/AssetQuery.graphql' import { NftAssetTraitInput, NftMarketplace, NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { import { ASSET_PAGE_SIZE, AssetFetcherParams, useNftAssets } from 'graphql/data/nft/Asset'
ASSET_PAGE_SIZE,
AssetFetcherParams,
useLazyLoadAssetsQuery,
useLoadSweepAssetsQuery,
} from 'graphql/data/nft/Asset'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import { useScreenSize } from 'hooks/useScreenSize' import { useScreenSize } from 'hooks/useScreenSize'
import { AnimatedBox, Box } from 'nft/components/Box' import { AnimatedBox, Box } from 'nft/components/Box'
...@@ -41,7 +36,6 @@ import { ...@@ -41,7 +36,6 @@ import {
GenieCollection, GenieCollection,
isPooledMarket, isPooledMarket,
Markets, Markets,
TokenType,
UniformAspectRatio, UniformAspectRatio,
UniformAspectRatios, UniformAspectRatios,
} from 'nft/types' } from 'nft/types'
...@@ -63,7 +57,7 @@ import { ThemedText } from 'theme' ...@@ -63,7 +57,7 @@ import { ThemedText } from 'theme'
import { CollectionAssetLoading } from './CollectionAssetLoading' import { CollectionAssetLoading } from './CollectionAssetLoading'
import { MARKETPLACE_ITEMS, MarketplaceLogo } from './MarketplaceSelect' import { MARKETPLACE_ITEMS, MarketplaceLogo } from './MarketplaceSelect'
import { Sweep, useSweepFetcherParams } from './Sweep' import { Sweep } from './Sweep'
import { TraitChip } from './TraitChip' import { TraitChip } from './TraitChip'
interface CollectionNftsProps { interface CollectionNftsProps {
...@@ -282,15 +276,6 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -282,15 +276,6 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const [renderedHeight, setRenderedHeight] = useState<number | undefined>() const [renderedHeight, setRenderedHeight] = useState<number | undefined>()
const [sweepIsOpen, setSweepOpen] = useState(false) const [sweepIsOpen, setSweepOpen] = useState(false)
// Load all sweep queries. Loading them on the parent allows lazy-loading, but avoids waterfalling requests.
const collectionParams = useSweepFetcherParams(contractAddress, 'others', debouncedMinPrice, debouncedMaxPrice)
const sudoSwapParams = useSweepFetcherParams(contractAddress, Markets.Sudoswap, debouncedMinPrice, debouncedMaxPrice)
const nftxParams = useSweepFetcherParams(contractAddress, Markets.NFTX, debouncedMinPrice, debouncedMaxPrice)
const nft20Params = useSweepFetcherParams(contractAddress, Markets.NFT20, debouncedMinPrice, debouncedMaxPrice)
useLoadSweepAssetsQuery(collectionParams, sweepIsOpen)
useLoadSweepAssetsQuery(sudoSwapParams, sweepIsOpen)
useLoadSweepAssetsQuery(nftxParams, sweepIsOpen)
useLoadSweepAssetsQuery(nft20Params, sweepIsOpen)
const assetQueryParams: AssetFetcherParams = { const assetQueryParams: AssetFetcherParams = {
address: contractAddress, address: contractAddress,
...@@ -312,8 +297,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -312,8 +297,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
first: ASSET_PAGE_SIZE, first: ASSET_PAGE_SIZE,
} }
const { assets: collectionNfts, loadNext, hasNext, isLoadingNext } = useLazyLoadAssetsQuery(assetQueryParams) const { data: collectionNfts, loading, hasNext, loadMore } = useNftAssets(assetQueryParams)
const handleNextPageLoad = useCallback(() => loadNext(ASSET_PAGE_SIZE), [loadNext])
const getPoolPosition = useCallback( const getPoolPosition = useCallback(
(asset: GenieAsset) => { (asset: GenieAsset) => {
...@@ -394,8 +378,8 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -394,8 +378,8 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const screenSize = useScreenSize() const screenSize = useScreenSize()
useEffect(() => { useEffect(() => {
setIsCollectionNftsLoading(isLoadingNext) setIsCollectionNftsLoading(loading)
}, [isLoadingNext, setIsCollectionNftsLoading]) }, [loading, setIsCollectionNftsLoading])
const hasRarity = useMemo(() => { const hasRarity = useMemo(() => {
const hasRarity = getRarityStatus(rarityStatusCache, collectionStats?.address, collectionAssets) ?? false const hasRarity = getRarityStatus(rarityStatusCache, collectionStats?.address, collectionAssets) ?? false
...@@ -434,7 +418,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -434,7 +418,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
}, [collectionAssets, isMobile, currentTokenPlayingMedia, rarityVerified, uniformAspectRatio, renderedHeight]) }, [collectionAssets, isMobile, currentTokenPlayingMedia, rarityVerified, uniformAspectRatio, renderedHeight])
const hasNfts = collectionAssets && collectionAssets.length > 0 const hasNfts = collectionAssets && collectionAssets.length > 0
const hasErc1155s = hasNfts && collectionAssets[0] && collectionAssets[0].tokenType === TokenType.ERC1155 const hasErc1155s = hasNfts && collectionAssets[0] && collectionAssets[0]?.tokenType === NftStandard.Erc1155
const minMaxPriceChipText: string | undefined = useMemo(() => { const minMaxPriceChipText: string | undefined = useMemo(() => {
if (debouncedMinPrice && debouncedMaxPrice) { if (debouncedMinPrice && debouncedMaxPrice) {
...@@ -619,35 +603,37 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -619,35 +603,37 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
</InfiniteScrollWrapper> </InfiniteScrollWrapper>
</AnimatedBox> </AnimatedBox>
<InfiniteScrollWrapper> <InfiniteScrollWrapper>
<InfiniteScroll {loading ? (
next={handleNextPageLoad} <CollectionNftsLoading height={renderedHeight} />
hasMore={hasNext} ) : (
loader={Boolean(hasNext && hasNfts) && <LoadingAssets height={renderedHeight} />} <InfiniteScroll
dataLength={collectionAssets?.length ?? 0} next={loadMore}
style={{ overflow: 'unset' }} hasMore={hasNext ?? false}
className={hasNfts || isLoadingNext ? styles.assetList : undefined} loader={Boolean(hasNext && hasNfts) && <LoadingAssets />}
> dataLength={collectionAssets?.length ?? 0}
{hasNfts ? ( style={{ overflow: 'unset' }}
assets className={hasNfts ? styles.assetList : undefined}
) : collectionAssets?.length === 0 ? ( >
<Center width="full" color="textSecondary" textAlign="center" style={{ height: '60vh' }}> {!hasNfts ? (
<EmptyCollectionWrapper> <Center width="full" color="textSecondary" textAlign="center" style={{ height: '60vh' }}>
<p className={headlineMedium}>No NFTS found</p> <EmptyCollectionWrapper>
<Box <p className={headlineMedium}>No NFTS found</p>
onClick={reset} <Box
type="button" onClick={reset}
className={clsx(bodySmall, buttonTextMedium)} type="button"
color="accentAction" className={clsx(bodySmall, buttonTextMedium)}
cursor="pointer" color="accentAction"
> cursor="pointer"
<ViewFullCollection>View full collection</ViewFullCollection> >
</Box> <ViewFullCollection>View full collection</ViewFullCollection>
</EmptyCollectionWrapper> </Box>
</Center> </EmptyCollectionWrapper>
) : ( </Center>
<CollectionNftsLoading height={renderedHeight} /> ) : (
)} assets
</InfiniteScroll> )}
</InfiniteScroll>
)}
</InfiniteScrollWrapper> </InfiniteScrollWrapper>
</> </>
) )
......
...@@ -2,7 +2,7 @@ import 'rc-slider/assets/index.css' ...@@ -2,7 +2,7 @@ import 'rc-slider/assets/index.css'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { formatEther, parseEther } from '@ethersproject/units' import { formatEther, parseEther } from '@ethersproject/units'
import { SweepFetcherParams, useLazyLoadSweepAssetsQuery } from 'graphql/data/nft/Asset' import { SweepFetcherParams, useSweepNftAssets } from 'graphql/data/nft/Asset'
import { useBag, useCollectionFilters } from 'nft/hooks' import { useBag, useCollectionFilters } from 'nft/hooks'
import { GenieAsset, isPooledMarket, Markets } from 'nft/types' import { GenieAsset, isPooledMarket, Markets } from 'nft/types'
import { calcPoolPrice, calcSudoSwapPrice, formatWeiToDecimal, isInSameSudoSwapPool } from 'nft/utils' import { calcPoolPrice, calcSudoSwapPrice, formatWeiToDecimal, isInSameSudoSwapPool } from 'nft/utils'
...@@ -178,13 +178,13 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => { ...@@ -178,13 +178,13 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
const nftxParams = useSweepFetcherParams(contractAddress, Markets.NFTX, minPrice, maxPrice) const nftxParams = useSweepFetcherParams(contractAddress, Markets.NFTX, minPrice, maxPrice)
const nft20Params = useSweepFetcherParams(contractAddress, Markets.NFT20, minPrice, maxPrice) const nft20Params = useSweepFetcherParams(contractAddress, Markets.NFT20, minPrice, maxPrice)
// These calls will suspend if the query is not yet loaded. // These calls will suspend if the query is not yet loaded.
const collectionAssets = useLazyLoadSweepAssetsQuery(collectionParams) const { data: collectionAssets } = useSweepNftAssets(collectionParams)
const sudoSwapAsssets = useLazyLoadSweepAssetsQuery(sudoSwapParams) const { data: sudoSwapAssets } = useSweepNftAssets(sudoSwapParams)
const nftxAssets = useLazyLoadSweepAssetsQuery(nftxParams) const { data: nftxAssets } = useSweepNftAssets(nftxParams)
const nft20Assets = useLazyLoadSweepAssetsQuery(nft20Params) const { data: nft20Assets } = useSweepNftAssets(nft20Params)
const { sortedAssets, sortedAssetsTotalEth } = useMemo(() => { const { sortedAssets, sortedAssetsTotalEth } = useMemo(() => {
if (!collectionAssets && !sudoSwapAsssets && !nftxAssets && !nft20Assets) { if (!collectionAssets && !sudoSwapAssets && !nftxAssets && !nft20Assets) {
return { sortedAssets: undefined, sortedAssetsTotalEth: BigNumber.from(0) } return { sortedAssets: undefined, sortedAssetsTotalEth: BigNumber.from(0) }
} }
...@@ -193,7 +193,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => { ...@@ -193,7 +193,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
let jointCollections: GenieAsset[] = [] let jointCollections: GenieAsset[] = []
if (sudoSwapAsssets) jointCollections = [...jointCollections, ...sudoSwapAsssets] if (sudoSwapAssets) jointCollections = [...jointCollections, ...sudoSwapAssets]
if (nftxAssets) jointCollections = [...jointCollections, ...nftxAssets] if (nftxAssets) jointCollections = [...jointCollections, ...nftxAssets]
if (nft20Assets) jointCollections = [...jointCollections, ...nft20Assets] if (nft20Assets) jointCollections = [...jointCollections, ...nft20Assets]
...@@ -236,7 +236,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => { ...@@ -236,7 +236,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
0, 0,
Math.max( Math.max(
collectionAssets?.length ?? 0, collectionAssets?.length ?? 0,
sudoSwapAsssets?.length ?? 0, sudoSwapAssets?.length ?? 0,
nftxAssets?.length ?? 0, nftxAssets?.length ?? 0,
nft20Assets?.length ?? 0 nft20Assets?.length ?? 0
) )
...@@ -249,7 +249,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => { ...@@ -249,7 +249,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
BigNumber.from(0) BigNumber.from(0)
), ),
} }
}, [collectionAssets, sudoSwapAsssets, nftxAssets, nft20Assets]) }, [collectionAssets, sudoSwapAssets, nftxAssets, nft20Assets])
const { sweepItemsInBag, sweepEthPrice } = useMemo(() => { const { sweepItemsInBag, sweepEthPrice } = useMemo(() => {
const sweepItemsInBag = itemsInBag const sweepItemsInBag = itemsInBag
...@@ -435,7 +435,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => { ...@@ -435,7 +435,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
const ALL_OTHER_MARKETS = [Markets.Opensea, Markets.X2Y2, Markets.LooksRare] const ALL_OTHER_MARKETS = [Markets.Opensea, Markets.X2Y2, Markets.LooksRare]
export function useSweepFetcherParams( function useSweepFetcherParams(
contractAddress: string, contractAddress: string,
market: Markets.Sudoswap | Markets.NFTX | Markets.NFT20 | 'others', market: Markets.Sudoswap | Markets.NFTX | Markets.NFT20 | 'others',
minPrice: string, minPrice: string,
......
...@@ -263,7 +263,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => { ...@@ -263,7 +263,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
return MediaType.Audio return MediaType.Audio
} else if (isVideo(asset.animationUrl ?? '')) { } else if (isVideo(asset.animationUrl ?? '')) {
return MediaType.Video return MediaType.Video
} else if (asset.animationUrl !== undefined) { } else if (!!asset.animationUrl) {
return MediaType.Embed return MediaType.Embed
} }
return MediaType.Image return MediaType.Image
......
...@@ -3,7 +3,7 @@ import { sendAnalyticsEvent } from '@uniswap/analytics' ...@@ -3,7 +3,7 @@ import { sendAnalyticsEvent } from '@uniswap/analytics'
import { EventName } from '@uniswap/analytics-events' import { EventName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { OpacityHoverState } from 'components/Common' import { OpacityHoverState } from 'components/Common'
import { useNftBalanceQuery } from 'graphql/data/nft/NftBalance' import { useNftBalance } from 'graphql/data/nft/NftBalance'
import { CancelListingIcon, VerifiedIcon } from 'nft/components/icons' import { CancelListingIcon, VerifiedIcon } from 'nft/components/icons'
import { useBag, useProfilePageState, useSellAsset } from 'nft/hooks' import { useBag, useProfilePageState, useSellAsset } from 'nft/hooks'
import { CollectionInfoForAsset, GenieAsset, ProfilePageStateType, WalletAsset } from 'nft/types' import { CollectionInfoForAsset, GenieAsset, ProfilePageStateType, WalletAsset } from 'nft/types'
...@@ -218,10 +218,10 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => { ...@@ -218,10 +218,10 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => {
const resetSellAssets = useSellAsset((state) => state.reset) const resetSellAssets = useSellAsset((state) => state.reset)
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.endAt) : undefined const expirationDate = listing?.endAt ? new Date(listing.endAt) : undefined
const USDPrice = useMemo( const USDPrice = useMemo(
() => (USDValue ? USDValue * asset.floor_sell_order_price : undefined), () => (USDValue && asset.floor_sell_order_price ? USDValue * asset.floor_sell_order_price : undefined),
[USDValue, asset.floor_sell_order_price] [USDValue, asset.floor_sell_order_price]
) )
const trace = useTrace() const trace = useTrace()
...@@ -254,7 +254,7 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => { ...@@ -254,7 +254,7 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => {
{listing ? ( {listing ? (
<> <>
<ThemedText.MediumHeader fontSize="28px" lineHeight="36px"> <ThemedText.MediumHeader fontSize="28px" lineHeight="36px">
{formatEthPrice(asset.priceInfo.ETHPrice)} ETH {formatEthPrice(asset.priceInfo?.ETHPrice)} ETH
</ThemedText.MediumHeader> </ThemedText.MediumHeader>
{USDPrice && ( {USDPrice && (
<ThemedText.BodySecondary lineHeight="24px"> <ThemedText.BodySecondary lineHeight="24px">
...@@ -320,7 +320,7 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) ...@@ -320,7 +320,7 @@ 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.endAt) : undefined const expirationDate = cheapestOrder?.endAt ? new Date(cheapestOrder.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)
...@@ -331,11 +331,8 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) ...@@ -331,11 +331,8 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
const USDPrice = useUsdPrice(asset) const USDPrice = useUsdPrice(asset)
const assetsFilter = [{ address: asset.address, tokenId: asset.tokenId }] const assetsFilter = [{ address: asset.address, tokenId: asset.tokenId }]
const { walletAssets: ownerAssets } = useNftBalanceQuery(account ?? '', [], assetsFilter, 1) const { walletAssets: ownerAssets } = useNftBalance(account ?? '', [], assetsFilter, 1)
const walletAsset: WalletAsset | undefined = useMemo( const walletAsset: WalletAsset | undefined = useMemo(() => ownerAssets?.[0], [ownerAssets])
() => (ownerAssets?.length > 0 ? ownerAssets[0] : undefined),
[ownerAssets]
)
const { assetInBag } = useMemo(() => { const { assetInBag } = useMemo(() => {
return { return {
...@@ -355,7 +352,7 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) ...@@ -355,7 +352,7 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
) )
} }
const isOwner = asset.owner && !!walletAsset && account?.toLowerCase() === asset.owner?.address?.toLowerCase() const isOwner = asset.ownerAddress && !!walletAsset && account?.toLowerCase() === asset.ownerAddress?.toLowerCase()
const isForSale = cheapestOrder && asset.priceInfo const isForSale = cheapestOrder && asset.priceInfo
return ( return (
...@@ -424,20 +421,20 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) ...@@ -424,20 +421,20 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
)} )}
{isForSale && ( {isForSale && (
<OwnerInformationContainer> <OwnerInformationContainer>
{asset.tokenType !== 'ERC1155' && asset.owner.address && ( {asset.tokenType !== 'ERC1155' && asset.ownerAddress && (
<ThemedText.BodySmall color="textSecondary" lineHeight="20px"> <ThemedText.BodySmall color="textSecondary" lineHeight="20px">
Seller: Seller:
</ThemedText.BodySmall> </ThemedText.BodySmall>
)} )}
<OwnerText <OwnerText
target="_blank" target="_blank"
href={`https://etherscan.io/address/${asset.owner.address}`} href={`https://etherscan.io/address/${asset.ownerAddress}`}
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{asset.tokenType === 'ERC1155' ? ( {asset.tokenType === 'ERC1155' ? (
'' ''
) : ( ) : (
<span> {isOwner ? 'You' : asset.owner.address && shortenAddress(asset.owner.address, 2, 4)}</span> <span> {isOwner ? 'You' : asset.ownerAddress && shortenAddress(asset.ownerAddress, 2, 4)}</span>
)} )}
</OwnerText> </OwnerText>
</OwnerInformationContainer> </OwnerInformationContainer>
......
import { useLoadCollectionQuery } from 'graphql/data/nft/Collection'
import { fetchTrendingCollections } from 'nft/queries' import { fetchTrendingCollections } from 'nft/queries'
import { TimePeriod } from 'nft/types' import { TimePeriod } from 'nft/types'
import { calculateCardIndex } from 'nft/utils' import { calculateCardIndex } from 'nft/utils'
import { Suspense, useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -137,10 +136,6 @@ const Banner = () => { ...@@ -137,10 +136,6 @@ const Banner = () => {
[data] [data]
) )
// Trigger queries for the top trending collections, so that the data is immediately available if the user clicks through.
const collectionAddresses = useMemo(() => collections?.map(({ address }) => address), [collections])
useLoadCollectionQuery(collectionAddresses)
const [activeCollectionIdx, setActiveCollectionIdx] = useState(0) const [activeCollectionIdx, setActiveCollectionIdx] = useState(0)
const onToggleNextSlide = useCallback( const onToggleNextSlide = useCallback(
(direction: number) => { (direction: number) => {
...@@ -169,13 +164,11 @@ const Banner = () => { ...@@ -169,13 +164,11 @@ const Banner = () => {
{collections ? ( {collections ? (
<Carousel activeIndex={activeCollectionIdx} toggleNextSlide={onToggleNextSlide}> <Carousel activeIndex={activeCollectionIdx} toggleNextSlide={onToggleNextSlide}>
{collections.map((collection) => ( {collections.map((collection) => (
<Suspense fallback={<LoadingCarouselCard collection={collection} />} key={collection.address}> <CarouselCard
<CarouselCard key={collection.address}
key={collection.address} collection={collection}
collection={collection} onClick={() => navigate(`/nfts/collection/${collection.address}`)}
onClick={() => navigate(`/nfts/collection/${collection.address}`)} />
/>
</Suspense>
))} ))}
</Carousel> </Carousel>
) : ( ) : (
......
import { formatNumberOrString, NumberType } from '@uniswap/conedison/format' import { formatNumberOrString, NumberType } from '@uniswap/conedison/format'
import { loadingAnimation } from 'components/Loader/styled' import { loadingAnimation } from 'components/Loader/styled'
import { LoadingBubble } from 'components/Tokens/loading' import { LoadingBubble } from 'components/Tokens/loading'
import { useCollectionQuery } from 'graphql/data/nft/Collection' import { useCollection } from 'graphql/data/nft/Collection'
import { VerifiedIcon } from 'nft/components/icons' import { VerifiedIcon } from 'nft/components/icons'
import { Markets, TrendingCollection } from 'nft/types' import { Markets, TrendingCollection } from 'nft/types'
import { formatWeiToDecimal } from 'nft/utils' import { formatWeiToDecimal } from 'nft/utils'
...@@ -235,7 +235,9 @@ const MARKETS_ENUM_TO_NAME = { ...@@ -235,7 +235,9 @@ const MARKETS_ENUM_TO_NAME = {
} }
export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => { export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
const gqlCollection = useCollectionQuery(collection.address) const { data: gqlCollection, loading } = useCollection(collection.address)
if (loading) return <LoadingCarouselCard />
return ( return (
<CarouselCardBorder> <CarouselCardBorder>
......
...@@ -152,7 +152,7 @@ const PriceTextInput = ({ ...@@ -152,7 +152,7 @@ const PriceTextInput = ({
inputRef.current.value = listPrice !== undefined ? `${listPrice}` : '' inputRef.current.value = listPrice !== undefined ? `${listPrice}` : ''
setWarningType(WarningType.NONE) setWarningType(WarningType.NONE)
if (!warning && listPrice) { if (!warning && listPrice) {
if (listPrice < asset.floorPrice) setWarningType(WarningType.BELOW_FLOOR) if (listPrice < (asset?.floorPrice ?? 0)) setWarningType(WarningType.BELOW_FLOOR)
else if (asset.floor_sell_order_price && listPrice >= asset.floor_sell_order_price) else if (asset.floor_sell_order_price && listPrice >= asset.floor_sell_order_price)
setWarningType(WarningType.ALREADY_LISTED) setWarningType(WarningType.ALREADY_LISTED)
} else if (warning && listPrice && listPrice >= 0) removeMarketplaceWarning(asset, warning) } else if (warning && listPrice && listPrice >= 0) removeMarketplaceWarning(asset, warning)
...@@ -226,10 +226,14 @@ const PriceTextInput = ({ ...@@ -226,10 +226,14 @@ const PriceTextInput = ({
> >
{focused ? ( {focused ? (
<> <>
<Row display={asset.lastPrice ? 'flex' : 'none'} marginRight="8"> {!!asset.lastPrice && (
LAST: {formatEth(asset.lastPrice)} ETH <Row display={asset.lastPrice ? 'flex' : 'none'} marginRight="8">
</Row> LAST: {formatEth(asset.lastPrice)} ETH
<Row display={asset.floorPrice ? 'flex' : 'none'}>FLOOR: {formatEth(asset.floorPrice)} ETH</Row> </Row>
)}
{!!asset.floorPrice && (
<Row display={asset.floorPrice ? 'flex' : 'none'}>FLOOR: {formatEth(asset.floorPrice)} ETH</Row>
)}
</> </>
) : ( ) : (
<> <>
...@@ -239,8 +243,8 @@ const PriceTextInput = ({ ...@@ -239,8 +243,8 @@ const PriceTextInput = ({
<> <>
{warningType} {warningType}
{warningType === WarningType.BELOW_FLOOR {warningType === WarningType.BELOW_FLOOR
? formatEth(asset.floorPrice) ? formatEth(asset?.floorPrice ?? 0)
: formatEth(asset.floor_sell_order_price)} : formatEth(asset?.floor_sell_order_price ?? 0)}
ETH ETH
<Box <Box
color={warningType === WarningType.BELOW_FLOOR ? 'accentAction' : 'orange'} color={warningType === WarningType.BELOW_FLOOR ? 'accentAction' : 'orange'}
...@@ -335,7 +339,7 @@ const MarketplaceRow = ({ ...@@ -335,7 +339,7 @@ const MarketplaceRow = ({
const royalties = const royalties =
(selectedMarkets.length === 1 && selectedMarkets[0].name === 'LooksRare' (selectedMarkets.length === 1 && selectedMarkets[0].name === 'LooksRare'
? LOOKS_RARE_CREATOR_BASIS_POINTS ? LOOKS_RARE_CREATOR_BASIS_POINTS
: asset.basisPoints) * 0.01 : asset?.basisPoints ?? 0) * 0.01
const feeInEth = price && (price * (royalties + marketplaceFee)) / 100 const feeInEth = price && (price * (royalties + marketplaceFee)) / 100
const userReceives = price && feeInEth && price - feeInEth const userReceives = price && feeInEth && price - feeInEth
......
import { useNftBalanceQuery } from 'graphql/data/nft/NftBalance' import { useNftBalance } from 'graphql/data/nft/NftBalance'
import { AnimatedBox, Box } from 'nft/components/Box' import { AnimatedBox, Box } from 'nft/components/Box'
import { ClearAllButton, LoadingAssets } from 'nft/components/collection/CollectionNfts' import { ClearAllButton, LoadingAssets } from 'nft/components/collection/CollectionNfts'
import { assetList } from 'nft/components/collection/CollectionNfts.css' import { assetList } from 'nft/components/collection/CollectionNfts.css'
...@@ -198,10 +198,10 @@ const ProfilePageNfts = ({ ...@@ -198,10 +198,10 @@ const ProfilePageNfts = ({
const { const {
walletAssets: ownerAssets, walletAssets: ownerAssets,
loadNext, loading,
hasNext, hasNext,
isLoadingNext, loadMore,
} = useNftBalanceQuery(address, collectionFilters, [], DEFAULT_WALLET_ASSET_QUERY_AMOUNT) } = useNftBalance(address, collectionFilters, [], DEFAULT_WALLET_ASSET_QUERY_AMOUNT)
const { gridX } = useSpring({ const { gridX } = useSpring({
gridX: isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING, gridX: isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING,
...@@ -211,6 +211,8 @@ const ProfilePageNfts = ({ ...@@ -211,6 +211,8 @@ const ProfilePageNfts = ({
}, },
}) })
if (loading) return <ProfileBodyLoadingSkeleton />
return ( return (
<Column width="full"> <Column width="full">
{ownerAssets?.length === 0 ? ( {ownerAssets?.length === 0 ? (
...@@ -242,13 +244,13 @@ const ProfilePageNfts = ({ ...@@ -242,13 +244,13 @@ const ProfilePageNfts = ({
/> />
</Row> </Row>
<InfiniteScroll <InfiniteScroll
next={() => loadNext(DEFAULT_WALLET_ASSET_QUERY_AMOUNT)} next={loadMore}
hasMore={hasNext} hasMore={hasNext ?? false}
loader={ loader={
Boolean(hasNext && ownerAssets?.length) && <LoadingAssets count={DEFAULT_WALLET_ASSET_QUERY_AMOUNT} /> Boolean(hasNext && ownerAssets?.length) && <LoadingAssets count={DEFAULT_WALLET_ASSET_QUERY_AMOUNT} />
} }
dataLength={ownerAssets?.length ?? 0} dataLength={ownerAssets?.length ?? 0}
className={ownerAssets?.length || isLoadingNext ? assetList : undefined} className={ownerAssets?.length ? assetList : undefined}
style={{ overflow: 'unset' }} style={{ overflow: 'unset' }}
> >
{ownerAssets?.length {ownerAssets?.length
......
...@@ -4,13 +4,14 @@ import { sendAnalyticsEvent } from '@uniswap/analytics' ...@@ -4,13 +4,14 @@ import { sendAnalyticsEvent } from '@uniswap/analytics'
import { EventName } from '@uniswap/analytics-events' import { EventName } from '@uniswap/analytics-events'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import * as Card from 'nft/components/collection/Card' import * as Card from 'nft/components/collection/Card'
import { AssetMediaType } from 'nft/components/collection/Card' import { AssetMediaType } from 'nft/components/collection/Card'
import { bodySmall } from 'nft/css/common.css' import { bodySmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useBag, useIsMobile, useSellAsset } from 'nft/hooks' import { useBag, useIsMobile, useSellAsset } from 'nft/hooks'
import { TokenType, WalletAsset } from 'nft/types' import { WalletAsset } from 'nft/types'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
const TOOLTIP_TIMEOUT = 2000 const TOOLTIP_TIMEOUT = 2000
...@@ -39,7 +40,7 @@ const getNftDisplayComponent = ( ...@@ -39,7 +40,7 @@ const getNftDisplayComponent = (
const getUnsupportedNftTextComponent = (asset: WalletAsset) => ( const getUnsupportedNftTextComponent = (asset: WalletAsset) => (
<Box as="span" className={bodySmall} style={{ color: themeVars.colors.textPrimary }}> <Box as="span" className={bodySmall} style={{ color: themeVars.colors.textPrimary }}>
{asset.asset_contract.tokenType === TokenType.ERC1155 ? ( {asset.asset_contract.tokenType === NftStandard.Erc1155 ? (
<Trans>Selling ERC-1155s coming soon</Trans> <Trans>Selling ERC-1155s coming soon</Trans>
) : ( ) : (
<Trans>Blocked from trading</Trans> <Trans>Blocked from trading</Trans>
...@@ -109,7 +110,7 @@ export const ViewMyNftsAsset = ({ ...@@ -109,7 +110,7 @@ export const ViewMyNftsAsset = ({
}, [isSelected, isSelectedRef]) }, [isSelected, isSelectedRef])
const assetMediaType = Card.useAssetMediaType(asset) const assetMediaType = Card.useAssetMediaType(asset)
const isDisabled = asset.asset_contract.tokenType === TokenType.ERC1155 || asset.susFlag const isDisabled = asset.asset_contract.tokenType === NftStandard.Erc1155 || asset.susFlag
return ( return (
<Card.Container <Card.Container
......
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { BagItem, BagItemStatus, BagStatus, TokenType, UpdatedGenieAsset } from 'nft/types' import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { BagItem, BagItemStatus, BagStatus, UpdatedGenieAsset } from 'nft/types'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import create from 'zustand' import create from 'zustand'
import { devtools } from 'zustand/middleware' import { devtools } from 'zustand/middleware'
...@@ -88,7 +89,7 @@ export const useBag = create<BagState>()( ...@@ -88,7 +89,7 @@ export const useBag = create<BagState>()(
const itemsInBagCopy = [...itemsInBag] const itemsInBagCopy = [...itemsInBag]
assets.forEach((asset) => { assets.forEach((asset) => {
let index = -1 let index = -1
if (asset.tokenType !== TokenType.ERC1155) { if (asset.tokenType !== NftStandard.Erc1155) {
index = itemsInBag.findIndex( index = itemsInBag.findIndex(
(n) => n.asset.tokenId === asset.tokenId && n.asset.address === asset.address (n) => n.asset.tokenId === asset.tokenId && n.asset.address === asset.address
) )
......
import { NftAssetSortableField } from 'graphql/data/nft/__generated__/AssetPaginationQuery.graphql' import { NftAssetSortableField } from 'graphql/data/__generated__/types-and-hooks'
import create from 'zustand' import create from 'zustand'
import { devtools } from 'zustand/middleware' import { devtools } from 'zustand/middleware'
......
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import create from 'zustand' import create from 'zustand'
import { devtools } from 'zustand/middleware' import { devtools } from 'zustand/middleware'
...@@ -28,7 +29,7 @@ export const useWalletCollections = create<WalletCollectionState>()( ...@@ -28,7 +29,7 @@ export const useWalletCollections = create<WalletCollectionState>()(
setWalletAssets: (assets) => setWalletAssets: (assets) =>
set(() => { set(() => {
return { return {
walletAssets: assets?.filter((asset) => asset.asset_contract?.schema_name === 'ERC721'), walletAssets: assets?.filter((asset) => asset.asset_contract?.tokenType === NftStandard.Erc721),
} }
}), }),
setWalletCollections: (collections) => setWalletCollections: (collections) =>
......
import { Trace } from '@uniswap/analytics' import { Trace } from '@uniswap/analytics'
import { PageName } from '@uniswap/analytics-events' import { PageName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core' import { useNftAssetDetails } from 'graphql/data/nft/Details'
import { useDetailsQuery, useLoadDetailsQuery } from 'graphql/data/nft/Details'
import { useLoadNftBalanceQuery } from 'graphql/data/nft/NftBalance'
import { AssetDetails } from 'nft/components/details/AssetDetails' import { AssetDetails } from 'nft/components/details/AssetDetails'
import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading' import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading'
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails' import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { useBag } from 'nft/hooks'
import { Suspense, useEffect, useMemo } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -38,11 +34,13 @@ const AssetPriceDetailsContainer = styled.div` ...@@ -38,11 +34,13 @@ const AssetPriceDetailsContainer = styled.div`
} }
` `
const Asset = () => { const AssetPage = () => {
const { tokenId = '', contractAddress = '' } = useParams() const { tokenId = '', contractAddress = '' } = useParams()
const data = useDetailsQuery(contractAddress, tokenId) const { data, loading } = useNftAssetDetails(contractAddress, tokenId)
const [asset, collection] = data
const [asset, collection] = useMemo(() => data ?? [], [data]) if (loading) return <AssetDetailsLoading />
return ( return (
<> <>
...@@ -51,35 +49,17 @@ const Asset = () => { ...@@ -51,35 +49,17 @@ const Asset = () => {
properties={{ collection_address: contractAddress, token_id: tokenId }} properties={{ collection_address: contractAddress, token_id: tokenId }}
shouldLogImpression shouldLogImpression
> >
{asset && collection ? ( {!!asset && !!collection && (
<AssetContainer> <AssetContainer>
<AssetDetails collection={collection} asset={asset} /> <AssetDetails collection={collection} asset={asset} />
<AssetPriceDetailsContainer> <AssetPriceDetailsContainer>
<AssetPriceDetails collection={collection} asset={asset} /> <AssetPriceDetails collection={collection} asset={asset} />
</AssetPriceDetailsContainer> </AssetPriceDetailsContainer>
</AssetContainer> </AssetContainer>
) : null} )}
</Trace> </Trace>
</> </>
) )
} }
const AssetPage = () => {
const { tokenId, contractAddress } = useParams()
const { account } = useWeb3React()
const setBagExpanded = useBag((state) => state.setBagExpanded)
useLoadDetailsQuery(contractAddress, tokenId)
useLoadNftBalanceQuery(account, contractAddress, tokenId)
useEffect(() => {
setBagExpanded({ bagExpanded: false, manualClose: false })
}, []) // eslint-disable-line react-hooks/exhaustive-deps
return (
<Suspense fallback={<AssetDetailsLoading />}>
<Asset />
</Suspense>
)
}
export default AssetPage export default AssetPage
...@@ -5,8 +5,7 @@ import Column from 'components/Column' ...@@ -5,8 +5,7 @@ import Column from 'components/Column'
import { OpacityHoverState } from 'components/Common' import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row' import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading' import { LoadingBubble } from 'components/Tokens/loading'
import { useLoadAssetsQuery } from 'graphql/data/nft/Asset' import { useCollection } from 'graphql/data/nft/Collection'
import { useCollectionQuery, useLoadCollectionQuery } from 'graphql/data/nft/Collection'
import { useScreenSize } from 'hooks/useScreenSize' import { useScreenSize } from 'hooks/useScreenSize'
import { BAG_WIDTH, XXXL_BAG_WIDTH } from 'nft/components/bag/Bag' import { BAG_WIDTH, XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag' import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag'
...@@ -16,7 +15,6 @@ import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPage ...@@ -16,7 +15,6 @@ import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPage
import { BagCloseIcon } from 'nft/components/icons' import { BagCloseIcon } from 'nft/components/icons'
import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks' import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks'
import * as styles from 'nft/pages/collection/index.css' import * as styles from 'nft/pages/collection/index.css'
import { GenieCollection } from 'nft/types'
import { Suspense, useEffect } from 'react' import { Suspense, useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom' import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { animated, easings, useSpring } from 'react-spring' import { animated, easings, useSpring } from 'react-spring'
...@@ -26,6 +24,7 @@ import { TRANSITION_DURATIONS } from 'theme/styles' ...@@ -26,6 +24,7 @@ import { TRANSITION_DURATIONS } from 'theme/styles'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
const FILTER_WIDTH = 332 const FILTER_WIDTH = 332
const EMPTY_TRAIT_OBJ = {}
export const CollectionBannerLoading = styled(LoadingBubble)` export const CollectionBannerLoading = styled(LoadingBubble)`
width: 100%; width: 100%;
...@@ -133,7 +132,7 @@ const Collection = () => { ...@@ -133,7 +132,7 @@ const Collection = () => {
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const screenSize = useScreenSize() const screenSize = useScreenSize()
const collectionStats = useCollectionQuery(contractAddress as string) const { data: collectionStats, loading } = useCollection(contractAddress as string)
const { CollectionContainerWidthChange } = useSpring({ const { CollectionContainerWidthChange } = useSpring({
CollectionContainerWidthChange: CollectionContainerWidthChange:
...@@ -169,6 +168,8 @@ const Collection = () => { ...@@ -169,6 +168,8 @@ const Collection = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
if (loading) return <CollectionPageSkeleton />
const toggleActivity = () => { const toggleActivity = () => {
isActivityToggled isActivityToggled
? navigate(`/nfts/collection/${contractAddress}`) ? navigate(`/nfts/collection/${contractAddress}`)
...@@ -197,9 +198,7 @@ const Collection = () => { ...@@ -197,9 +198,7 @@ const Collection = () => {
/> />
</BannerWrapper> </BannerWrapper>
<CollectionDescriptionSection> <CollectionDescriptionSection>
{collectionStats && ( {collectionStats && <CollectionStats stats={collectionStats} isMobile={isMobile} />}
<CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} />
)}
<div id="nft-anchor" /> <div id="nft-anchor" />
<ActivitySwitcher <ActivitySwitcher
showActivity={isActivityToggled} showActivity={isActivityToggled}
...@@ -221,7 +220,7 @@ const Collection = () => { ...@@ -221,7 +220,7 @@ const Collection = () => {
</IconWrapper> </IconWrapper>
</MobileFilterHeader> </MobileFilterHeader>
)} )}
<Filters traitsByGroup={collectionStats?.traits ?? {}} /> <Filters traitsByGroup={collectionStats?.traits ?? EMPTY_TRAIT_OBJ} />
</> </>
)} )}
</FiltersContainer> </FiltersContainer>
...@@ -246,7 +245,7 @@ const Collection = () => { ...@@ -246,7 +245,7 @@ const Collection = () => {
collectionStats && ( collectionStats && (
<Suspense fallback={<CollectionNftsAndMenuLoading />}> <Suspense fallback={<CollectionNftsAndMenuLoading />}>
<CollectionNfts <CollectionNfts
collectionStats={collectionStats || ({} as GenieCollection)} collectionStats={collectionStats}
contractAddress={contractAddress} contractAddress={contractAddress}
rarityVerified={collectionStats?.rarityVerified} rarityVerified={collectionStats?.rarityVerified}
/> />
...@@ -265,21 +264,4 @@ const Collection = () => { ...@@ -265,21 +264,4 @@ const Collection = () => {
) )
} }
// The page is responsible for any queries that must be run on initial load. export default Collection
// Triggering query load from the page prevents waterfalled requests, as lazy-loading them in components would prevent
// any children from rendering.
const CollectionPage = () => {
const { contractAddress } = useParams()
useLoadCollectionQuery(contractAddress)
useLoadAssetsQuery(contractAddress)
// The Collection must be wrapped in suspense so that it does not suspend the CollectionPage,
// which is needed to trigger query loads.
return (
<Suspense fallback={<CollectionPageSkeleton />}>
<Collection />
</Suspense>
)
}
export default CollectionPage
import { GenieAsset, RouteResponse, TokenType } from '../../types' import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { GenieAsset, RouteResponse } from 'nft/types'
export const fetchRoute = async ({ export const fetchRoute = async ({
toSell, toSell,
...@@ -41,7 +42,7 @@ type RouteItem = { ...@@ -41,7 +42,7 @@ type RouteItem = {
decimals: number decimals: number
address: string address: string
priceInfo: ApiPriceInfo priceInfo: ApiPriceInfo
tokenType: TokenType tokenType?: NftStandard
tokenId: string tokenId: string
amount: number amount: number
marketplace?: string marketplace?: string
......
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { SortBy } from 'nft/hooks' import { SortBy } from 'nft/hooks'
import { SellOrder } from '../sell' import { SellOrder } from '../sell'
...@@ -81,7 +82,7 @@ export interface Trait { ...@@ -81,7 +82,7 @@ export interface Trait {
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
...@@ -93,17 +94,15 @@ export interface GenieAsset { ...@@ -93,17 +94,15 @@ export interface GenieAsset {
sellorders?: SellOrder[] sellorders?: SellOrder[]
smallImageUrl?: string smallImageUrl?: string
tokenId: string tokenId: string
tokenType: TokenType tokenType?: NftStandard
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: { ownerAddress?: string
address: string metadataUrl?: string
}
metadataUrl: string
creator: { creator: {
address: string address?: string
profile_img_url: string profile_img_url?: string
} }
traits?: Trait[] traits?: Trait[]
} }
......
import { NftMarketplace, OrderStatus, OrderType } from 'graphql/data/nft/__generated__/DetailsQuery.graphql' import { NftMarketplace, NftStandard, OrderStatus, OrderType } from 'graphql/data/__generated__/types-and-hooks'
import { GenieCollection, PriceInfo, TokenType } from '../common' import { GenieCollection, PriceInfo } from '../common'
export interface ListingMarket { export interface ListingMarket {
name: string name: string
...@@ -15,20 +15,20 @@ export interface ListingWarning { ...@@ -15,20 +15,20 @@ export interface ListingWarning {
export interface SellOrder { export interface SellOrder {
address: string address: string
createdAt: number createdAt: number
endAt: number endAt?: number
id: string id: string
maker: string maker: string
marketplace: NftMarketplace marketplace: NftMarketplace
marketplaceUrl: string marketplaceUrl: string
orderHash: string orderHash?: string
price: { price: {
currency: string currency?: string
value: number value: number
} }
quantity: number quantity: number
startAt: number startAt: number
status: OrderStatus status: OrderStatus
tokenId: string tokenId?: string
type: OrderType type: OrderType
protocolParameters: Record<string, unknown> protocolParameters: Record<string, unknown>
} }
...@@ -41,32 +41,31 @@ export interface Listing { ...@@ -41,32 +41,31 @@ export interface Listing {
export interface WalletAsset { export interface WalletAsset {
id?: string id?: string
imageUrl: string imageUrl?: string
smallImageUrl: string smallImageUrl?: string
notForSale: boolean notForSale: boolean
animationUrl: string animationUrl?: string
susFlag: boolean susFlag?: boolean
priceInfo: PriceInfo priceInfo?: PriceInfo
name: string name?: string
tokenId: string tokenId?: string
asset_contract: { asset_contract: {
address: string address?: string
schema_name: 'ERC1155' | 'ERC721' | string name?: string
name: string description?: string
description: string image_url?: string
image_url: string payout_address?: string
payout_address: string tokenType?: NftStandard
tokenType: TokenType
} }
collection: GenieCollection collection?: GenieCollection
collectionIsVerified: boolean collectionIsVerified?: boolean
lastPrice: number lastPrice?: number
floorPrice: number floorPrice?: number
basisPoints: number basisPoints?: number
listing_date: string listing_date?: string
date_acquired: string date_acquired?: string
sellOrders: SellOrder[] sellOrders?: 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
marketAgnosticPrice?: number marketAgnosticPrice?: number
...@@ -95,8 +94,8 @@ export enum ListingStatus { ...@@ -95,8 +94,8 @@ export enum ListingStatus {
} }
export interface AssetRow { export interface AssetRow {
images: string[] images: (string | undefined)[]
name: string name?: string
status: ListingStatus status: ListingStatus
callback?: () => Promise<void> callback?: () => Promise<void>
} }
...@@ -108,7 +107,7 @@ export interface ListingRow extends AssetRow { ...@@ -108,7 +107,7 @@ export interface ListingRow extends AssetRow {
} }
export interface CollectionRow extends AssetRow { export interface CollectionRow extends AssetRow {
collectionAddress: string collectionAddress?: string
marketplace: ListingMarket marketplace: ListingMarket
} }
......
...@@ -62,7 +62,7 @@ const getConsiderationItems = ( ...@@ -62,7 +62,7 @@ const getConsiderationItems = (
creatorFee?: ConsiderationInputItem creatorFee?: ConsiderationInputItem
} => { } => {
const openSeaBasisPoints = OPENSEA_DEFAULT_FEE * INVERSE_BASIS_POINTS const openSeaBasisPoints = OPENSEA_DEFAULT_FEE * INVERSE_BASIS_POINTS
const creatorFeeBasisPoints = asset.basisPoints const creatorFeeBasisPoints = asset?.basisPoints ?? 0
const sellerBasisPoints = INVERSE_BASIS_POINTS - openSeaBasisPoints - creatorFeeBasisPoints const sellerBasisPoints = INVERSE_BASIS_POINTS - openSeaBasisPoints - creatorFeeBasisPoints
const openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString() const openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString()
...@@ -76,7 +76,9 @@ const getConsiderationItems = ( ...@@ -76,7 +76,9 @@ const getConsiderationItems = (
sellerFee: createConsiderationItem(sellerFee, signerAddress), sellerFee: createConsiderationItem(sellerFee, signerAddress),
openseaFee: createConsiderationItem(openseaFee, OPENSEA_FEE_ADDRESS), openseaFee: createConsiderationItem(openseaFee, OPENSEA_FEE_ADDRESS),
creatorFee: creatorFee:
creatorFeeBasisPoints > 0 ? createConsiderationItem(creatorFee, asset.asset_contract.payout_address) : undefined, creatorFeeBasisPoints > 0
? createConsiderationItem(creatorFee, asset?.asset_contract?.payout_address ?? '')
: undefined,
} }
} }
...@@ -128,7 +130,7 @@ export async function signListing( ...@@ -128,7 +130,7 @@ export async function signListing(
const signerAddress = await signer.getAddress() const signerAddress = await signer.getAddress()
const listingPrice = asset.newListings?.find((listing) => listing.marketplace.name === marketplace.name)?.price const listingPrice = asset.newListings?.find((listing) => listing.marketplace.name === marketplace.name)?.price
if (!listingPrice || !asset.expirationTime) return false if (!listingPrice || !asset.expirationTime || !asset.asset_contract.address || !asset.tokenId) return false
switch (marketplace.name) { switch (marketplace.name) {
case 'OpenSea': case 'OpenSea':
try { try {
......
...@@ -6,9 +6,6 @@ import TopLevelModals from 'components/TopLevelModals' ...@@ -6,9 +6,6 @@ import TopLevelModals from 'components/TopLevelModals'
import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useFeatureFlagsIsLoaded } from 'featureFlags'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton'
import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading'
import { ProfilePageLoadingSkeleton } from 'nft/components/profile/view/ProfilePageLoadingSkeleton'
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'
...@@ -251,7 +248,6 @@ export default function App() { ...@@ -251,7 +248,6 @@ export default function App() {
<Route <Route
path="/nfts" path="/nfts"
element={ element={
// TODO: replace loading state during Apollo migration
<Suspense fallback={null}> <Suspense fallback={null}>
<NftExplore /> <NftExplore />
</Suspense> </Suspense>
...@@ -260,7 +256,7 @@ export default function App() { ...@@ -260,7 +256,7 @@ export default function App() {
<Route <Route
path="/nfts/asset/:contractAddress/:tokenId" path="/nfts/asset/:contractAddress/:tokenId"
element={ element={
<Suspense fallback={<AssetDetailsLoading />}> <Suspense fallback={null}>
<Asset /> <Asset />
</Suspense> </Suspense>
} }
...@@ -268,7 +264,7 @@ export default function App() { ...@@ -268,7 +264,7 @@ export default function App() {
<Route <Route
path="/nfts/profile" path="/nfts/profile"
element={ element={
<Suspense fallback={<ProfilePageLoadingSkeleton />}> <Suspense fallback={null}>
<Profile /> <Profile />
</Suspense> </Suspense>
} }
...@@ -276,7 +272,7 @@ export default function App() { ...@@ -276,7 +272,7 @@ export default function App() {
<Route <Route
path="/nfts/collection/:contractAddress" path="/nfts/collection/:contractAddress"
element={ element={
<Suspense fallback={<CollectionPageSkeleton />}> <Suspense fallback={null}>
<Collection /> <Collection />
</Suspense> </Suspense>
} }
...@@ -284,7 +280,7 @@ export default function App() { ...@@ -284,7 +280,7 @@ export default function App() {
<Route <Route
path="/nfts/collection/:contractAddress/activity" path="/nfts/collection/:contractAddress/activity"
element={ element={
<Suspense fallback={<CollectionPageSkeleton />}> <Suspense fallback={null}>
<Collection /> <Collection />
</Suspense> </Suspense>
} }
......
import TokenDetails from 'components/Tokens/TokenDetails' import TokenDetails from 'components/Tokens/TokenDetails'
import { TokenDetailsPageSkeleton } from 'components/Tokens/TokenDetails/Skeleton' import { TokenDetailsPageSkeleton } from 'components/Tokens/TokenDetails/Skeleton'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { TokenQuery, tokenQuery } from 'graphql/data/Token' import { useTokenPriceQuery, useTokenQuery } from 'graphql/data/__generated__/types-and-hooks'
import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice'
import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod, toHistoryDuration, validateUrlChainParam } from 'graphql/data/util' import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod, toHistoryDuration, validateUrlChainParam } from 'graphql/data/util'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils' import { atomWithStorage } from 'jotai/utils'
import { Suspense, useCallback, useEffect, useMemo } from 'react' import { useMemo } from 'react'
import { useQueryLoader } from 'react-relay'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
export const pageTimePeriodAtom = atomWithStorage<TimePeriod>('tokenDetailsTimePeriod', TimePeriod.DAY) export const pageTimePeriodAtom = atomWithStorage<TimePeriod>('tokenDetailsTimePeriod', TimePeriod.DAY)
...@@ -26,35 +24,28 @@ export default function TokenDetailsPage() { ...@@ -26,35 +24,28 @@ export default function TokenDetailsPage() {
[chain, isNative, pageChainId, timePeriod, tokenAddress] [chain, isNative, pageChainId, timePeriod, tokenAddress]
) )
const [tokenQueryReference, loadTokenQuery] = useQueryLoader<TokenQuery>(tokenQuery) const { data: tokenQuery, loading: tokenQueryLoading } = useTokenQuery({
const [priceQueryReference, loadPriceQuery] = useQueryLoader<TokenPriceQuery>(tokenPriceQuery) variables: {
contract,
useEffect(() => { },
loadTokenQuery({ contract }) })
loadPriceQuery({ contract, duration })
}, [contract, duration, loadPriceQuery, loadTokenQuery, timePeriod])
const refetchTokenPrices = useCallback( const { data: tokenPriceQuery } = useTokenPriceQuery({
(t: TimePeriod) => { variables: {
loadPriceQuery({ contract, duration: toHistoryDuration(t) }) contract,
setTimePeriod(t) duration,
}, },
[contract, loadPriceQuery, setTimePeriod] })
)
if (!tokenQueryReference) { if (!tokenQuery || tokenQueryLoading) return <TokenDetailsPageSkeleton />
return <TokenDetailsPageSkeleton />
}
return ( return (
<Suspense fallback={<TokenDetailsPageSkeleton />}> <TokenDetails
<TokenDetails urlAddress={tokenAddress}
urlAddress={tokenAddress} chain={chain}
chain={chain} tokenQuery={tokenQuery}
tokenQueryReference={tokenQueryReference} tokenPriceQuery={tokenPriceQuery}
priceQueryReference={priceQueryReference} onChangeTimePeriod={setTimePeriod}
refetchTokenPrices={refetchTokenPrices} />
/>
</Suspense>
) )
} }
...@@ -7,12 +7,11 @@ import { filterStringAtom } from 'components/Tokens/state' ...@@ -7,12 +7,11 @@ import { filterStringAtom } from 'components/Tokens/state'
import NetworkFilter from 'components/Tokens/TokenTable/NetworkFilter' import NetworkFilter from 'components/Tokens/TokenTable/NetworkFilter'
import SearchBar from 'components/Tokens/TokenTable/SearchBar' import SearchBar from 'components/Tokens/TokenTable/SearchBar'
import TimeSelector from 'components/Tokens/TokenTable/TimeSelector' import TimeSelector from 'components/Tokens/TokenTable/TimeSelector'
import TokenTable, { LoadingTokenTable } from 'components/Tokens/TokenTable/TokenTable' import TokenTable from 'components/Tokens/TokenTable/TokenTable'
import { PAGE_SIZE } from 'graphql/data/TopTokens'
import { chainIdToBackendName, isValidBackendChainName } from 'graphql/data/util' import { chainIdToBackendName, isValidBackendChainName } from 'graphql/data/util'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch' import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { useResetAtom } from 'jotai/utils' import { useResetAtom } from 'jotai/utils'
import { Suspense, useEffect, useState } from 'react' import { useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom' import { useLocation, useNavigate, useParams } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
...@@ -75,8 +74,6 @@ const Tokens = () => { ...@@ -75,8 +74,6 @@ const Tokens = () => {
const { chainId: connectedChainId } = useWeb3React() const { chainId: connectedChainId } = useWeb3React()
const connectedChainName = chainIdToBackendName(connectedChainId) const connectedChainName = chainIdToBackendName(connectedChainId)
const [rowCount, setRowCount] = useState(PAGE_SIZE)
useEffect(() => { useEffect(() => {
resetFilterString() resetFilterString()
}, [location, resetFilterString]) }, [location, resetFilterString])
...@@ -110,9 +107,7 @@ const Tokens = () => { ...@@ -110,9 +107,7 @@ const Tokens = () => {
<SearchBar /> <SearchBar />
</SearchContainer> </SearchContainer>
</FiltersWrapper> </FiltersWrapper>
<Suspense fallback={<LoadingTokenTable rowCount={rowCount} />}> <TokenTable />
<TokenTable setRowCount={setRowCount} />
</Suspense>
</ExploreContainer> </ExploreContainer>
</Trace> </Trace>
) )
......
...@@ -25,7 +25,3 @@ declare module 'multihashes' { ...@@ -25,7 +25,3 @@ declare module 'multihashes' {
declare function decode(buff: Uint8Array): { code: number; name: string; length: number; digest: Uint8Array } declare function decode(buff: Uint8Array): { code: number; name: string; length: number; digest: Uint8Array }
declare function toB58String(hash: Uint8Array): string declare function toB58String(hash: Uint8Array): string
} }
declare module 'babel-plugin-relay/macro' {
export { graphql as default } from 'react-relay'
}
This diff is collapsed.
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