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
*.d.ts
/src/graphql/data/__generated__/types-and-hooks.ts
/src/graphql/thegraph/__generated__/types-and-hooks.ts
......@@ -9,7 +9,6 @@
/src/locales/**/pseudo.po
# generated graphql types
__generated__/
schema.graphql
# 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 */
require('dotenv').config({ path: '.env.production' })
const { exec } = require('child_process')
const dataConfig = require('./relay.config')
const thegraphConfig = require('./relay_thegraph.config')
const dataConfig = require('./graphql.config')
const thegraphConfig = require('./graphql_thegraph.config')
/* eslint-enable */
function fetchSchema(url, outputFile) {
......
// eslint-disable-next-line @typescript-eslint/no-var-requires
const defaultConfig = require('./relay.config')
const defaultConfig = require('./graphql.config')
module.exports = {
src: defaultConfig.src,
......
......@@ -8,10 +8,10 @@
"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": "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: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",
"i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "yarn i18n:extract && lingui compile",
......@@ -94,7 +94,6 @@
"@typescript-eslint/parser": "^4",
"@vanilla-extract/babel-plugin": "^1.1.7",
"@vanilla-extract/webpack-plugin": "^2.1.11",
"babel-plugin-relay": "^14.1.0",
"cypress": "^10.3.1",
"env-cmd": "^10.1.0",
"eslint": "^7.11.0",
......@@ -113,16 +112,23 @@
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.7.1",
"react-scripts": "^4.0.3",
"relay-compiler": "^14.1.0",
"serve": "^11.3.2",
"ts-transform-graphql-tag": "^0.2.1",
"typechain": "^5.0.0",
"typescript": "^4.4.3",
"yarn-deduplicate": "^6.0.0"
},
"dependencies": {
"@apollo/client": "^3.7.2",
"@coinbase/wallet-sdk": "^3.3.0",
"@fontsource/ibm-plex-mono": "^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/macro": "^3.14.0",
"@lingui/react": "^3.14.0",
......@@ -135,7 +141,6 @@
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.6.1",
"@sentry/react": "7.20.1",
"@types/react-relay": "^13.0.2",
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "1.2.0",
"@uniswap/analytics-events": "^1.5.0",
......@@ -215,8 +220,6 @@
"react-popper": "^2.2.3",
"react-query": "^3.39.1",
"react-redux": "^8.0.2",
"react-relay": "^14.1.0",
"react-relay-network-modern": "^6.2.1",
"react-router-dom": "^6.3.0",
"react-spring": "^9.5.5",
"react-table": "^7.8.0",
......
......@@ -2,7 +2,6 @@ import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow'
import { curveCardinal, scaleLinear } from 'd3'
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
import { PricePoint } from 'graphql/data/util'
import { TimePeriod } from 'graphql/data/util'
import { memo } from 'react'
import styled, { useTheme } from 'styled-components/macro'
......@@ -21,18 +20,10 @@ interface SparklineChartProps {
height: number
tokenData: TopToken
pricePercentChange: number | undefined | null
timePeriod: TimePeriod
sparklineMap: SparklineMap
}
function _SparklineChart({
width,
height,
tokenData,
pricePercentChange,
timePeriod,
sparklineMap,
}: SparklineChartProps) {
function _SparklineChart({ width, height, tokenData, pricePercentChange, sparklineMap }: SparklineChartProps) {
const theme = useTheme()
// for sparkline
const pricePoints = tokenData?.address ? sparklineMap[tokenData.address] : null
......
import { ParentSize } from '@visx/responsive'
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 { TimePeriod } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { pageTimePeriodAtom } from 'pages/TokenDetails'
import { startTransition, Suspense, useMemo } from 'react'
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
import { PriceChart } from './PriceChart'
import TimePeriodSelector from './TimeSelector'
function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPriceQuery>): PricePoint[] | undefined {
const queryData = usePreloadedQuery(tokenPriceQuery, priceQueryReference)
function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined {
// Appends the current price to the end of the priceHistory array
const priceHistory = useMemo(() => {
const market = queryData.tokens?.[0]?.market
const market = tokenPriceData.tokens?.[0]?.market
const priceHistory = market?.priceHistory?.filter(isPricePoint)
const currentPrice = market?.price?.value
if (Array.isArray(priceHistory) && currentPrice !== undefined) {
......@@ -24,39 +21,39 @@ function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPr
return [...priceHistory, { timestamp, value: currentPrice }]
}
return priceHistory
}, [queryData])
}, [tokenPriceData])
return priceHistory
}
export default function ChartSection({
priceQueryReference,
refetchTokenPrices,
tokenPriceQuery,
onChangeTimePeriod,
}: {
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined
refetchTokenPrices: RefetchPricesFunction
tokenPriceQuery?: TokenPriceQuery
onChangeTimePeriod: OnChangeTimePeriod
}) {
if (!priceQueryReference) {
if (!tokenPriceQuery) {
return <LoadingChart />
}
return (
<Suspense fallback={<LoadingChart />}>
<ChartContainer>
<Chart priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} />
<Chart tokenPriceQuery={tokenPriceQuery} onChangeTimePeriod={onChangeTimePeriod} />
</ChartContainer>
</Suspense>
)
}
export type RefetchPricesFunction = (t: TimePeriod) => void
export type OnChangeTimePeriod = (t: TimePeriod) => void
function Chart({
priceQueryReference,
refetchTokenPrices,
tokenPriceQuery,
onChangeTimePeriod,
}: {
priceQueryReference: PreloadedQuery<TokenPriceQuery>
refetchTokenPrices: RefetchPricesFunction
tokenPriceQuery: TokenPriceQuery
onChangeTimePeriod: OnChangeTimePeriod
}) {
const prices = usePreloadedTokenPriceQuery(priceQueryReference)
const prices = usePriceHistory(tokenPriceQuery)
// Initializes time period to global & maintain separate time period for subsequent changes
const timePeriod = useAtomValue(pageTimePeriodAtom)
......@@ -68,7 +65,7 @@ function Chart({
<TimePeriodSelector
currentTimePeriod={timePeriod}
onTimeChange={(t: TimePeriod) => {
startTransition(() => refetchTokenPrices(t))
startTransition(() => onChangeTimePeriod(t))
}}
/>
</ChartContainer>
......
......@@ -27,21 +27,20 @@ import Widget from 'components/Widget'
import { getChainInfo } from 'constants/chainInfo'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
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 { QueryToken, tokenQuery } from 'graphql/data/Token'
import { QueryToken } from 'graphql/data/Token'
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
import { useCallback, useMemo, useState, useTransition } from 'react'
import { ArrowLeft } from 'react-feather'
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro'
import { isAddress } from 'utils'
import { RefetchPricesFunction } from './ChartSection'
import { OnChangeTimePeriod } from './ChartSection'
import InvalidTokenDetails from './InvalidTokenDetails'
const TokenSymbol = styled.span`
......@@ -75,7 +74,7 @@ function useRelevantToken(
const queryToken = useMemo(() => {
if (!address) return undefined
if (address === NATIVE_CHAIN_ID) return nativeOnChain(pageChainId)
if (tokenQueryData) return new QueryToken(tokenQueryData)
if (tokenQueryData) return new QueryToken(address, tokenQueryData)
return undefined
}, [pageChainId, address, tokenQueryData])
// 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(
type TokenDetailsProps = {
urlAddress: string | undefined
chain: Chain
tokenQueryReference: PreloadedQuery<TokenQuery>
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined
refetchTokenPrices: RefetchPricesFunction
tokenQuery: TokenQuery
tokenPriceQuery: TokenPriceQuery | undefined
onChangeTimePeriod: OnChangeTimePeriod
}
export default function TokenDetails({
urlAddress,
chain,
tokenQueryReference,
priceQueryReference,
refetchTokenPrices,
tokenQuery,
tokenPriceQuery,
onChangeTimePeriod,
}: TokenDetailsProps) {
if (!urlAddress) {
throw new Error('Invalid token details route: tokenAddress param is undefined')
......@@ -112,7 +111,7 @@ export default function TokenDetails({
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const tokenQueryData = usePreloadedQuery(tokenQuery, tokenQueryReference).tokens?.[0]
const tokenQueryData = tokenQuery.tokens?.[0]
const crossChainMap = useMemo(
() =>
tokenQueryData?.project?.tokens.reduce((map, current) => {
......@@ -200,7 +199,7 @@ export default function TokenDetails({
<ShareButton currency={token} />
</TokenActions>
</TokenInfoContainer>
<ChartSection priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} />
<ChartSection tokenPriceQuery={tokenPriceQuery} onChangeTimePeriod={onChangeTimePeriod} />
<StatsSection
TVL={tokenQueryData?.market?.totalValueLocked?.value}
volume24H={tokenQueryData?.market?.volume24H?.value}
......
......@@ -459,7 +459,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
return (
<div ref={ref} data-testid={`token-table-row-${tokenName}`}>
<StyledLink
to={getTokenDetailsURL(token.address, token.chain)}
to={getTokenDetailsURL(token.address ?? '', token.chain)}
onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)}
>
<TokenRow
......@@ -512,7 +512,6 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
height={height}
tokenData={token}
pricePercentChange={token.market?.pricePercentChange?.value}
timePeriod={timePeriod}
sparklineMap={props.sparklineMap}
/>
)
......
......@@ -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 (
<GridContainer>
<HeaderRow />
......@@ -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
const chainName = validateUrlChainParam(useParams<{ chainName?: string }>().chainName)
const { tokens, sparklines } = useTopTokens(chainName)
setRowCount(tokens?.length ?? PAGE_SIZE)
const { tokens, loadingTokens, sparklines } = useTopTokens(chainName)
/* loading and error state */
if (!tokens) {
if (loadingTokens) {
return <LoadingTokenTable rowCount={PAGE_SIZE} />
} else if (!tokens) {
return (
<NoTokensState
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 gql from 'graphql-tag'
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'
/*
......@@ -13,14 +13,14 @@ The difference between Token and TokenProject:
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.
*/
export const tokenQuery = graphql`
query TokenQuery($contract: ContractInput!) {
gql`
query Token($contract: ContractInput!) {
tokens(contracts: [$contract]) {
id @required(action: LOG)
id
decimals
name
chain @required(action: LOG)
address @required(action: LOG)
chain
address
symbol
market(currency: USD) {
totalValueLocked {
......@@ -48,23 +48,24 @@ export const tokenQuery = graphql`
twitterName
logoUrl
tokens {
chain @required(action: LOG)
address @required(action: LOG)
chain
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.
export class QueryToken extends WrappedTokenInfo {
constructor(data: NonNullable<TokenQueryData>, logoSrc?: string) {
constructor(address: string, data: NonNullable<TokenQueryData>, logoSrc?: string) {
super({
chainId: CHAIN_NAME_TO_CHAIN_ID[data.chain],
address: data.address,
address,
decimals: data.decimals ?? DEFAULT_ERC20_DECIMALS,
symbol: data.symbol ?? '',
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
export const tokenPriceQuery = graphql`
query TokenPriceQuery($contract: ContractInput!, $duration: HistoryDuration!) {
gql`
query TokenPrice($contract: ContractInput!, $duration: HistoryDuration!) {
tokens(contracts: [$contract]) {
market(currency: USD) @required(action: LOG) {
market(currency: USD) {
price {
value @required(action: LOG)
value
}
priceHistory(duration: $duration) {
timestamp @required(action: LOG)
value @required(action: LOG)
timestamp
value
}
}
}
}
`
export type { TokenPriceQuery } from './__generated__/TokenPriceQuery.graphql'
export type { TokenPriceQuery } from './__generated__/types-and-hooks'
import graphql from 'babel-plugin-relay/macro'
import {
filterStringAtom,
filterTimeAtom,
......@@ -6,22 +5,25 @@ import {
sortMethodAtom,
TokenSortMethod,
} from 'components/Tokens/state'
import gql from 'graphql-tag'
import { useAtomValue } from 'jotai/utils'
import { useEffect, useMemo, useState } from 'react'
import { fetchQuery, useLazyLoadQuery, useRelayEnvironment } from 'react-relay'
import { useMemo } from 'react'
import type { Chain, TopTokens100Query } from './__generated__/TopTokens100Query.graphql'
import { TopTokensSparklineQuery } from './__generated__/TopTokensSparklineQuery.graphql'
import { isPricePoint, PricePoint } from './util'
import { CHAIN_NAME_TO_CHAIN_ID, toHistoryDuration, unwrapToken } from './util'
import {
Chain,
TopTokens100Query,
useTopTokens100Query,
useTopTokensSparklineQuery,
} from './__generated__/types-and-hooks'
import { CHAIN_NAME_TO_CHAIN_ID, isPricePoint, PricePoint, toHistoryDuration, unwrapToken } from './util'
const topTokens100Query = graphql`
query TopTokens100Query($duration: HistoryDuration!, $chain: Chain!) {
gql`
query TopTokens100($duration: HistoryDuration!, $chain: Chain!) {
topTokens(pageSize: 100, page: 1, chain: $chain) {
id @required(action: LOG)
id
name
chain @required(action: LOG)
address @required(action: LOG)
chain
address
symbol
market(currency: USD) {
totalValueLocked {
......@@ -48,21 +50,21 @@ const topTokens100Query = graphql`
}
`
const tokenSparklineQuery = graphql`
query TopTokensSparklineQuery($duration: HistoryDuration!, $chain: Chain!) {
gql`
query TopTokensSparkline($duration: HistoryDuration!, $chain: Chain!) {
topTokens(pageSize: 100, page: 1, chain: $chain) {
address
market(currency: USD) {
priceHistory(duration: $duration) {
timestamp @required(action: LOG)
value @required(action: LOG)
timestamp
value
}
}
}
}
`
function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) {
function useSortedTokens(tokens: NonNullable<TopTokens100Query['topTokens']>) {
const sortMethod = useAtomValue(sortMethodAtom)
const sortAscending = useAtomValue(sortAscendingAtom)
......@@ -91,7 +93,7 @@ function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topT
}, [tokens, sortMethod, sortAscending])
}
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) {
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['topTokens']>) {
const filterString = useAtomValue(filterStringAtom)
const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString])
......@@ -112,11 +114,12 @@ function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['to
// Number of items to render in each fetch in infinite scroll.
export const PAGE_SIZE = 20
export type TopToken = NonNullable<NonNullable<TopTokens100Query['response']>['topTokens']>[number]
export type SparklineMap = { [key: string]: PricePoint[] | undefined }
export type TopToken = NonNullable<NonNullable<TopTokens100Query>['topTokens']>[number]
interface UseTopTokensReturnValue {
tokens: TopToken[] | undefined
loadingTokens: boolean
sparklines: SparklineMap
}
......@@ -124,33 +127,27 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
const chainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const duration = toHistoryDuration(useAtomValue(filterTimeAtom))
const environment = useRelayEnvironment()
const [sparklines, setSparklines] = useState<SparklineMap>({})
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])
const { data: sparklineQuery } = useTopTokensSparklineQuery({
variables: { duration, chain },
})
useEffect(() => {
setSparklines({})
}, [duration])
const sparklines = useMemo(() => {
const unwrappedTokens = sparklineQuery?.topTokens?.map((topToken) => unwrapToken(chainId, topToken))
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 mappedTokens = useMemo(() => topTokens?.map((token) => unwrapToken(chainId, token)) ?? [], [chainId, topTokens])
const { data, loading: loadingTokens } = useTopTokens100Query({
variables: { duration, chain },
})
const mappedTokens = useMemo(
() => data?.topTokens?.map((token) => unwrapToken(chainId, token)) ?? [],
[chainId, data]
)
const filteredTokens = useFilteredTokens(mappedTokens)
const sortedTokens = useSortedTokens(filteredTokens)
return useMemo(() => ({ tokens: sortedTokens, sparklines }), [sortedTokens, sparklines])
return useMemo(() => ({ tokens: sortedTokens, loadingTokens, sparklines }), [loadingTokens, sortedTokens, sparklines])
}
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T;
export type InputMaybe<T> = T;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
const defaultOptions = {} as const;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
/**
* The `AWSJSON` scalar type provided by AWS AppSync, represents a JSON string that
* complies with [RFC 8259](https://tools.ietf.org/html/rfc8259). Maps like
* "**{\\"upvotes\\": 10}**", lists like "**[1,2,3]**", and scalar values like
* "**\\"AWSJSON example string\\"**", "**1**", and "**true**" are accepted as
* valid JSON and will automatically be parsed and loaded in the resolver mapping
* templates as Maps, Lists, or Scalar values rather than as the literal input
* strings. Invalid JSON strings like "**{a: 1}**", "**{'a': 1}**" and "**Unquoted
* string**" will throw GraphQL validation errors.
*/
AWSJSON: any;
};
export enum ActivityType {
Approve = 'APPROVE',
Borrow = 'BORROW',
Burn = 'BURN',
Cancel = 'CANCEL',
Claim = 'CLAIM',
Deployment = 'DEPLOYMENT',
Lend = 'LEND',
Mint = 'MINT',
Nft = 'NFT',
Receive = 'RECEIVE',
Repay = 'REPAY',
Send = 'SEND',
Stake = 'STAKE',
Swap = 'SWAP',
Staking = 'Staking',
Unknown = 'UNKNOWN',
Unstake = 'UNSTAKE',
Withdraw = 'WITHDRAW',
Market = 'market',
Money = 'money'
}
export type Amount = IAmount & {
__typename?: 'Amount';
currency?: Maybe<Currency>;
id: Scalars['ID'];
value: Scalars['Float'];
};
export type AmountChange = {
__typename?: 'AmountChange';
absolute?: Maybe<Amount>;
id: Scalars['ID'];
percentage?: Maybe<Amount>;
};
export type AssetActivity = {
__typename?: 'AssetActivity';
assetChanges: Array<Maybe<AssetChange>>;
chain: Chain;
gasUsed?: Maybe<Scalars['Float']>;
id: Scalars['ID'];
timestamp: Scalars['Int'];
transaction: Transaction;
type: ActivityType;
};
export type AssetChange = NftApproval | NftApproveForAll | NftTransfer | TokenApproval | TokenTransfer;
export enum Chain {
Arbitrum = 'ARBITRUM',
Celo = 'CELO',
Ethereum = 'ETHEREUM',
EthereumGoerli = 'ETHEREUM_GOERLI',
Optimism = 'OPTIMISM',
Polygon = 'POLYGON'
}
export type ContractInput = {
address?: InputMaybe<Scalars['String']>;
chain: Chain;
};
export enum Currency {
Eth = 'ETH',
Usd = 'USD'
}
export type Dimensions = {
__typename?: 'Dimensions';
height?: Maybe<Scalars['Float']>;
id: Scalars['ID'];
width?: Maybe<Scalars['Float']>;
};
export enum HighLow {
High = 'HIGH',
Low = 'LOW'
}
export enum HistoryDuration {
Day = 'DAY',
Hour = 'HOUR',
Max = 'MAX',
Month = 'MONTH',
Week = 'WEEK',
Year = 'YEAR'
}
export type IAmount = {
currency?: Maybe<Currency>;
value: Scalars['Float'];
};
export type IContract = {
address?: Maybe<Scalars['String']>;
chain: Chain;
};
export type Image = {
__typename?: 'Image';
dimensions?: Maybe<Dimensions>;
id: Scalars['ID'];
url: Scalars['String'];
};
/** TODO: deprecate this enum */
export enum MarketSortableField {
MarketCap = 'MARKET_CAP',
Volume = 'VOLUME'
}
export type NftApproval = {
__typename?: 'NftApproval';
approvedAddress: Scalars['String'];
/** can be erc20 or erc1155 */
asset: NftAsset;
id: Scalars['ID'];
nftStandard: NftStandard;
};
export type NftApproveForAll = {
__typename?: 'NftApproveForAll';
approved: Scalars['Boolean'];
/** can be erc721 or erc1155 */
asset: NftAsset;
id: Scalars['ID'];
nftStandard: NftStandard;
operatorAddress: Scalars['String'];
};
export type NftAsset = {
__typename?: 'NftAsset';
animationUrl?: Maybe<Scalars['String']>;
collection?: Maybe<NftCollection>;
creator?: Maybe<NftProfile>;
description?: Maybe<Scalars['String']>;
flaggedBy?: Maybe<Scalars['String']>;
id: Scalars['ID'];
image?: Maybe<Image>;
imageUrl?: Maybe<Scalars['String']>;
listings?: Maybe<NftOrderConnection>;
metadataUrl?: Maybe<Scalars['String']>;
name?: Maybe<Scalars['String']>;
nftContract?: Maybe<NftContract>;
originalImage?: Maybe<Image>;
/** TODO: may need to be array to support erc1155 cases. not needed at the moment so will revisit. */
ownerAddress?: Maybe<Scalars['String']>;
rarities?: Maybe<Array<NftAssetRarity>>;
smallImage?: Maybe<Image>;
smallImageUrl?: Maybe<Scalars['String']>;
suspiciousFlag?: Maybe<Scalars['Boolean']>;
thumbnail?: Maybe<Image>;
thumbnailUrl?: Maybe<Scalars['String']>;
tokenId: Scalars['String'];
traits?: Maybe<Array<NftAssetTrait>>;
};
export type NftAssetListingsArgs = {
after?: InputMaybe<Scalars['String']>;
asc?: InputMaybe<Scalars['Boolean']>;
before?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
};
export type NftAssetConnection = {
__typename?: 'NftAssetConnection';
edges: Array<NftAssetEdge>;
pageInfo: PageInfo;
totalCount?: Maybe<Scalars['Int']>;
};
export type NftAssetEdge = {
__typename?: 'NftAssetEdge';
cursor: Scalars['String'];
node: NftAsset;
};
export type NftAssetInput = {
address: Scalars['String'];
tokenId: Scalars['String'];
};
export type NftAssetRarity = {
__typename?: 'NftAssetRarity';
id: Scalars['ID'];
provider?: Maybe<NftRarityProvider>;
rank?: Maybe<Scalars['Int']>;
score?: Maybe<Scalars['Float']>;
};
export enum NftAssetSortableField {
Price = 'PRICE',
Rarity = 'RARITY'
}
export type NftAssetTrait = {
__typename?: 'NftAssetTrait';
id: Scalars['ID'];
name?: Maybe<Scalars['String']>;
rarity?: Maybe<Scalars['Float']>;
value?: Maybe<Scalars['String']>;
};
export type NftAssetTraitInput = {
name: Scalars['String'];
values: Array<Scalars['String']>;
};
export type NftAssetsFilterInput = {
listed?: InputMaybe<Scalars['Boolean']>;
marketplaces?: InputMaybe<Array<NftMarketplace>>;
maxPrice?: InputMaybe<Scalars['String']>;
minPrice?: InputMaybe<Scalars['String']>;
tokenIds?: InputMaybe<Array<Scalars['String']>>;
tokenSearchQuery?: InputMaybe<Scalars['String']>;
traits?: InputMaybe<Array<NftAssetTraitInput>>;
};
export type NftBalance = {
__typename?: 'NftBalance';
id: Scalars['ID'];
lastPrice?: Maybe<TimestampedAmount>;
listedMarketplaces?: Maybe<Array<NftMarketplace>>;
listingFees?: Maybe<Array<Maybe<NftFee>>>;
ownedAsset?: Maybe<NftAsset>;
};
export type NftBalanceConnection = {
__typename?: 'NftBalanceConnection';
edges: Array<NftBalanceEdge>;
pageInfo: PageInfo;
};
export type NftBalanceEdge = {
__typename?: 'NftBalanceEdge';
cursor: Scalars['String'];
node: NftBalance;
};
export type NftBalancesFilterInput = {
addresses?: InputMaybe<Array<Scalars['String']>>;
assets?: InputMaybe<Array<NftAssetInput>>;
};
export type NftCollection = {
__typename?: 'NftCollection';
bannerImage?: Maybe<Image>;
/**
* TODO: support querying for collection assets here
* assets(page: Int, pageSize: Int, orderBy: NftAssetSortableField): [NftAsset]
*/
bannerImageUrl?: Maybe<Scalars['String']>;
collectionId: Scalars['String'];
creator?: Maybe<NftProfile>;
description?: Maybe<Scalars['String']>;
discordUrl?: Maybe<Scalars['String']>;
homepageUrl?: Maybe<Scalars['String']>;
id: Scalars['ID'];
image?: Maybe<Image>;
imageUrl?: Maybe<Scalars['String']>;
instagramName?: Maybe<Scalars['String']>;
isVerified?: Maybe<Scalars['Boolean']>;
markets?: Maybe<Array<NftCollectionMarket>>;
name?: Maybe<Scalars['String']>;
nftContracts?: Maybe<Array<NftContract>>;
numAssets?: Maybe<Scalars['Int']>;
openseaUrl?: Maybe<Scalars['String']>;
traits?: Maybe<Array<NftCollectionTrait>>;
twitterName?: Maybe<Scalars['String']>;
};
export type NftCollectionMarketsArgs = {
currencies: Array<Currency>;
};
export type NftCollectionConnection = {
__typename?: 'NftCollectionConnection';
edges: Array<NftCollectionEdge>;
pageInfo: PageInfo;
};
export type NftCollectionEdge = {
__typename?: 'NftCollectionEdge';
cursor: Scalars['String'];
node: NftCollection;
};
export type NftCollectionMarket = {
__typename?: 'NftCollectionMarket';
floorPrice?: Maybe<TimestampedAmount>;
floorPricePercentChange?: Maybe<TimestampedAmount>;
id: Scalars['ID'];
listings?: Maybe<TimestampedAmount>;
marketplaces?: Maybe<Array<NftCollectionMarketplace>>;
nftContracts?: Maybe<Array<NftContract>>;
owners?: Maybe<Scalars['Int']>;
sales?: Maybe<TimestampedAmount>;
totalVolume?: Maybe<TimestampedAmount>;
volume?: Maybe<TimestampedAmount>;
volume24h?: Maybe<Amount>;
volumePercentChange?: Maybe<TimestampedAmount>;
};
export type NftCollectionMarketFloorPricePercentChangeArgs = {
duration?: InputMaybe<HistoryDuration>;
};
export type NftCollectionMarketMarketplacesArgs = {
marketplaces?: InputMaybe<Array<NftMarketplace>>;
};
export type NftCollectionMarketSalesArgs = {
duration?: InputMaybe<HistoryDuration>;
};
export type NftCollectionMarketVolumeArgs = {
duration?: InputMaybe<HistoryDuration>;
};
export type NftCollectionMarketVolumePercentChangeArgs = {
duration?: InputMaybe<HistoryDuration>;
};
export type NftCollectionMarketplace = {
__typename?: 'NftCollectionMarketplace';
floorPrice?: Maybe<Scalars['Float']>;
id: Scalars['ID'];
listings?: Maybe<Scalars['Int']>;
marketplace?: Maybe<NftMarketplace>;
};
export type NftCollectionTrait = {
__typename?: 'NftCollectionTrait';
id: Scalars['ID'];
name?: Maybe<Scalars['String']>;
stats?: Maybe<Array<NftCollectionTraitStats>>;
values?: Maybe<Array<Scalars['String']>>;
};
export type NftCollectionTraitStats = {
__typename?: 'NftCollectionTraitStats';
assets?: Maybe<Scalars['Int']>;
id: Scalars['ID'];
listings?: Maybe<Scalars['Int']>;
name?: Maybe<Scalars['String']>;
value?: Maybe<Scalars['String']>;
};
export type NftCollectionsFilterInput = {
addresses?: InputMaybe<Array<Scalars['String']>>;
};
export type NftContract = IContract & {
__typename?: 'NftContract';
address: Scalars['String'];
chain: Chain;
id: Scalars['ID'];
name?: Maybe<Scalars['String']>;
standard?: Maybe<NftStandard>;
symbol?: Maybe<Scalars['String']>;
totalSupply?: Maybe<Scalars['Int']>;
};
export type NftFee = {
__typename?: 'NftFee';
basisPoints: Scalars['Int'];
id: Scalars['ID'];
payoutAddress: Scalars['String'];
};
export enum NftMarketSortableField {
FloorPrice = 'FLOOR_PRICE',
Volume = 'VOLUME'
}
export enum NftMarketplace {
Cryptopunks = 'CRYPTOPUNKS',
Foundation = 'FOUNDATION',
Looksrare = 'LOOKSRARE',
Nft20 = 'NFT20',
Nftx = 'NFTX',
Opensea = 'OPENSEA',
Sudoswap = 'SUDOSWAP',
X2Y2 = 'X2Y2'
}
export type NftOrder = {
__typename?: 'NftOrder';
address: Scalars['String'];
auctionType?: Maybe<Scalars['String']>;
createdAt: Scalars['Float'];
endAt?: Maybe<Scalars['Float']>;
id: Scalars['ID'];
maker: Scalars['String'];
marketplace: NftMarketplace;
marketplaceUrl: Scalars['String'];
orderHash?: Maybe<Scalars['String']>;
price: Amount;
protocolParameters?: Maybe<Scalars['AWSJSON']>;
quantity: Scalars['Int'];
startAt: Scalars['Float'];
status: OrderStatus;
taker?: Maybe<Scalars['String']>;
tokenId?: Maybe<Scalars['String']>;
type: OrderType;
};
export type NftOrderConnection = {
__typename?: 'NftOrderConnection';
edges: Array<NftOrderEdge>;
pageInfo: PageInfo;
};
export type NftOrderEdge = {
__typename?: 'NftOrderEdge';
cursor: Scalars['String'];
node: NftOrder;
};
export type NftProfile = {
__typename?: 'NftProfile';
address: Scalars['String'];
id: Scalars['ID'];
isVerified?: Maybe<Scalars['Boolean']>;
profileImage?: Maybe<Image>;
username?: Maybe<Scalars['String']>;
};
export enum NftRarityProvider {
RaritySniper = 'RARITY_SNIPER'
}
export enum NftStandard {
Erc721 = 'ERC721',
Erc1155 = 'ERC1155',
Noncompliant = 'NONCOMPLIANT'
}
export type NftTransfer = {
__typename?: 'NftTransfer';
asset: NftAsset;
direction: TransactionDirection;
id: Scalars['ID'];
nftStandard: NftStandard;
recipient: Scalars['String'];
sender: Scalars['String'];
};
export enum OrderStatus {
Cancelled = 'CANCELLED',
Executed = 'EXECUTED',
Expired = 'EXPIRED',
Valid = 'VALID'
}
export enum OrderType {
Listing = 'LISTING',
Offer = 'OFFER'
}
export type PageInfo = {
__typename?: 'PageInfo';
endCursor?: Maybe<Scalars['String']>;
hasNextPage?: Maybe<Scalars['Boolean']>;
hasPreviousPage?: Maybe<Scalars['Boolean']>;
startCursor?: Maybe<Scalars['String']>;
};
export type Portfolio = {
__typename?: 'Portfolio';
assetActivities?: Maybe<Array<Maybe<AssetActivity>>>;
id: Scalars['ID'];
/** TODO: (michael.zhang) replace with paginated query */
nftBalances?: Maybe<Array<Maybe<NftBalance>>>;
ownerAddress: Scalars['String'];
tokenBalances?: Maybe<Array<Maybe<TokenBalance>>>;
tokensTotalDenominatedValue?: Maybe<Amount>;
tokensTotalDenominatedValueChange?: Maybe<AmountChange>;
tokensTotalDenominatedValueHistory?: Maybe<Array<Maybe<TimestampedAmount>>>;
};
export type PortfolioAssetActivitiesArgs = {
page?: InputMaybe<Scalars['Int']>;
pageSize?: InputMaybe<Scalars['Int']>;
};
export type PortfolioTokensTotalDenominatedValueChangeArgs = {
duration?: InputMaybe<HistoryDuration>;
};
export type PortfolioTokensTotalDenominatedValueHistoryArgs = {
duration?: InputMaybe<HistoryDuration>;
};
export type Query = {
__typename?: 'Query';
assetActivities?: Maybe<Array<Maybe<AssetActivity>>>;
nftAssets?: Maybe<NftAssetConnection>;
nftBalances?: Maybe<NftBalanceConnection>;
nftCollections?: Maybe<NftCollectionConnection>;
nftCollectionsById?: Maybe<Array<Maybe<NftCollection>>>;
portfolios?: Maybe<Array<Maybe<Portfolio>>>;
searchTokenProjects?: Maybe<Array<Maybe<TokenProject>>>;
searchTokens?: Maybe<Array<Maybe<Token>>>;
token?: Maybe<Token>;
tokenProjects?: Maybe<Array<Maybe<TokenProject>>>;
tokens?: Maybe<Array<Maybe<Token>>>;
topTokens?: Maybe<Array<Maybe<Token>>>;
};
export type QueryAssetActivitiesArgs = {
address: Scalars['String'];
page?: InputMaybe<Scalars['Int']>;
pageSize?: InputMaybe<Scalars['Int']>;
};
export type QueryNftAssetsArgs = {
address: Scalars['String'];
after?: InputMaybe<Scalars['String']>;
asc?: InputMaybe<Scalars['Boolean']>;
before?: InputMaybe<Scalars['String']>;
chain?: InputMaybe<Chain>;
filter?: InputMaybe<NftAssetsFilterInput>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
orderBy?: InputMaybe<NftAssetSortableField>;
};
export type QueryNftBalancesArgs = {
after?: InputMaybe<Scalars['String']>;
before?: InputMaybe<Scalars['String']>;
chain?: InputMaybe<Chain>;
filter?: InputMaybe<NftBalancesFilterInput>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
ownerAddress: Scalars['String'];
};
export type QueryNftCollectionsArgs = {
after?: InputMaybe<Scalars['String']>;
before?: InputMaybe<Scalars['String']>;
filter?: InputMaybe<NftCollectionsFilterInput>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
};
export type QueryNftCollectionsByIdArgs = {
collectionIds?: InputMaybe<Array<InputMaybe<Scalars['String']>>>;
};
export type QueryPortfoliosArgs = {
ownerAddresses: Array<Scalars['String']>;
useAltDataSource?: InputMaybe<Scalars['Boolean']>;
};
export type QuerySearchTokenProjectsArgs = {
searchQuery: Scalars['String'];
};
export type QuerySearchTokensArgs = {
searchQuery: Scalars['String'];
};
export type QueryTokenArgs = {
address?: InputMaybe<Scalars['String']>;
chain: Chain;
};
export type QueryTokenProjectsArgs = {
contracts: Array<ContractInput>;
};
export type QueryTokensArgs = {
contracts: Array<ContractInput>;
};
export type QueryTopTokensArgs = {
chain?: InputMaybe<Chain>;
orderBy?: InputMaybe<TokenSortableField>;
page?: InputMaybe<Scalars['Int']>;
pageSize?: InputMaybe<Scalars['Int']>;
};
export enum SafetyLevel {
Blocked = 'BLOCKED',
MediumWarning = 'MEDIUM_WARNING',
StrongWarning = 'STRONG_WARNING',
Verified = 'VERIFIED'
}
export type TimestampedAmount = IAmount & {
__typename?: 'TimestampedAmount';
currency?: Maybe<Currency>;
id: Scalars['ID'];
timestamp: Scalars['Int'];
value: Scalars['Float'];
};
export type Token = IContract & {
__typename?: 'Token';
address?: Maybe<Scalars['String']>;
chain: Chain;
decimals?: Maybe<Scalars['Int']>;
id: Scalars['ID'];
market?: Maybe<TokenMarket>;
name?: Maybe<Scalars['String']>;
project?: Maybe<TokenProject>;
standard?: Maybe<TokenStandard>;
symbol?: Maybe<Scalars['String']>;
};
export type TokenMarketArgs = {
currency?: InputMaybe<Currency>;
};
export type TokenApproval = {
__typename?: 'TokenApproval';
approvedAddress: Scalars['String'];
/** can be erc20 or erc1155 */
asset: Token;
id: Scalars['ID'];
quantity: Scalars['String'];
tokenStandard: TokenStandard;
};
export type TokenBalance = {
__typename?: 'TokenBalance';
blockNumber?: Maybe<Scalars['Int']>;
blockTimestamp?: Maybe<Scalars['Int']>;
denominatedValue?: Maybe<Amount>;
id: Scalars['ID'];
ownerAddress: Scalars['String'];
quantity?: Maybe<Scalars['Float']>;
token?: Maybe<Token>;
tokenProjectMarket?: Maybe<TokenProjectMarket>;
};
export type TokenMarket = {
__typename?: 'TokenMarket';
id: Scalars['ID'];
price?: Maybe<Amount>;
priceHighLow?: Maybe<Amount>;
priceHistory?: Maybe<Array<Maybe<TimestampedAmount>>>;
pricePercentChange?: Maybe<Amount>;
token: Token;
totalValueLocked?: Maybe<Amount>;
volume?: Maybe<Amount>;
};
export type TokenMarketPriceHighLowArgs = {
duration: HistoryDuration;
highLow: HighLow;
};
export type TokenMarketPriceHistoryArgs = {
duration: HistoryDuration;
};
export type TokenMarketPricePercentChangeArgs = {
duration: HistoryDuration;
};
export type TokenMarketVolumeArgs = {
duration: HistoryDuration;
};
export type TokenProject = {
__typename?: 'TokenProject';
description?: Maybe<Scalars['String']>;
homepageUrl?: Maybe<Scalars['String']>;
id: Scalars['ID'];
isSpam?: Maybe<Scalars['Boolean']>;
logo?: Maybe<Image>;
logoUrl?: Maybe<Scalars['String']>;
markets?: Maybe<Array<Maybe<TokenProjectMarket>>>;
name?: Maybe<Scalars['String']>;
safetyLevel?: Maybe<SafetyLevel>;
smallLogo?: Maybe<Image>;
spamCode?: Maybe<Scalars['Int']>;
tokens: Array<Token>;
twitterName?: Maybe<Scalars['String']>;
};
export type TokenProjectMarketsArgs = {
currencies: Array<Currency>;
};
export type TokenProjectMarket = {
__typename?: 'TokenProjectMarket';
currency: Currency;
fullyDilutedMarketCap?: Maybe<Amount>;
id: Scalars['ID'];
marketCap?: Maybe<Amount>;
price?: Maybe<Amount>;
priceHighLow?: Maybe<Amount>;
priceHistory?: Maybe<Array<Maybe<TimestampedAmount>>>;
pricePercentChange?: Maybe<Amount>;
pricePercentChange24h?: Maybe<Amount>;
tokenProject: TokenProject;
volume?: Maybe<Amount>;
volume24h?: Maybe<Amount>;
};
export type TokenProjectMarketPriceHighLowArgs = {
duration: HistoryDuration;
highLow: HighLow;
};
export type TokenProjectMarketPriceHistoryArgs = {
duration: HistoryDuration;
};
export type TokenProjectMarketPricePercentChangeArgs = {
duration: HistoryDuration;
};
export type TokenProjectMarketVolumeArgs = {
duration: HistoryDuration;
};
export enum TokenSortableField {
MarketCap = 'MARKET_CAP',
Popularity = 'POPULARITY',
TotalValueLocked = 'TOTAL_VALUE_LOCKED',
Volume = 'VOLUME'
}
export enum TokenStandard {
Erc20 = 'ERC20',
Erc1155 = 'ERC1155',
Native = 'NATIVE'
}
export type TokenTransfer = {
__typename?: 'TokenTransfer';
asset: Token;
direction: TransactionDirection;
id: Scalars['ID'];
quantity: Scalars['String'];
recipient: Scalars['String'];
sender: Scalars['String'];
tokenStandard: TokenStandard;
transactedValue?: Maybe<Amount>;
};
export type Transaction = {
__typename?: 'Transaction';
blockNumber: Scalars['Int'];
from: Scalars['String'];
gasLimit?: Maybe<Scalars['Float']>;
hash: Scalars['String'];
id: Scalars['ID'];
maxFeePerGas?: Maybe<Scalars['Float']>;
nonce: Scalars['Int'];
status: TransactionStatus;
to: Scalars['String'];
};
export enum TransactionDirection {
In = 'IN',
Out = 'OUT',
Self = 'SELF'
}
export enum TransactionStatus {
Confirmed = 'CONFIRMED',
Failed = 'FAILED',
Pending = 'PENDING'
}
export type TokenQueryVariables = Exact<{
contract: ContractInput;
}>;
export type TokenQuery = { __typename?: 'Query', tokens?: Array<{ __typename?: 'Token', id: string, decimals?: number, name?: string, chain: Chain, address?: string, symbol?: string, market?: { __typename?: 'TokenMarket', totalValueLocked?: { __typename?: 'Amount', value: number, currency?: Currency }, price?: { __typename?: 'Amount', value: number, currency?: Currency }, volume24H?: { __typename?: 'Amount', value: number, currency?: Currency }, priceHigh52W?: { __typename?: 'Amount', value: number }, priceLow52W?: { __typename?: 'Amount', value: number } }, project?: { __typename?: 'TokenProject', description?: string, homepageUrl?: string, twitterName?: string, logoUrl?: string, tokens: Array<{ __typename?: 'Token', chain: Chain, address?: string }> } }> };
export type TokenPriceQueryVariables = Exact<{
contract: ContractInput;
duration: HistoryDuration;
}>;
export type TokenPriceQuery = { __typename?: 'Query', tokens?: Array<{ __typename?: 'Token', market?: { __typename?: 'TokenMarket', price?: { __typename?: 'Amount', value: number }, priceHistory?: Array<{ __typename?: 'TimestampedAmount', timestamp: number, value: number }> } }> };
export type TopTokens100QueryVariables = Exact<{
duration: HistoryDuration;
chain: Chain;
}>;
export type TopTokens100Query = { __typename?: 'Query', topTokens?: Array<{ __typename?: 'Token', id: string, name?: string, chain: Chain, address?: string, symbol?: string, market?: { __typename?: 'TokenMarket', totalValueLocked?: { __typename?: 'Amount', value: number, currency?: Currency }, price?: { __typename?: 'Amount', value: number, currency?: Currency }, pricePercentChange?: { __typename?: 'Amount', currency?: Currency, value: number }, volume?: { __typename?: 'Amount', value: number, currency?: Currency } }, project?: { __typename?: 'TokenProject', logoUrl?: string } }> };
export type TopTokensSparklineQueryVariables = Exact<{
duration: HistoryDuration;
chain: Chain;
}>;
export type TopTokensSparklineQuery = { __typename?: 'Query', topTokens?: Array<{ __typename?: 'Token', address?: string, market?: { __typename?: 'TokenMarket', priceHistory?: Array<{ __typename?: 'TimestampedAmount', timestamp: number, value: number }> } }> };
export type AssetQueryVariables = Exact<{
address: Scalars['String'];
orderBy?: InputMaybe<NftAssetSortableField>;
asc?: InputMaybe<Scalars['Boolean']>;
filter?: InputMaybe<NftAssetsFilterInput>;
first?: InputMaybe<Scalars['Int']>;
after?: InputMaybe<Scalars['String']>;
last?: InputMaybe<Scalars['Int']>;
before?: InputMaybe<Scalars['String']>;
}>;
export type AssetQuery = { __typename?: 'Query', nftAssets?: { __typename?: 'NftAssetConnection', totalCount?: number, edges: Array<{ __typename?: 'NftAssetEdge', cursor: string, node: { __typename?: 'NftAsset', id: string, name?: string, ownerAddress?: string, tokenId: string, description?: string, animationUrl?: string, suspiciousFlag?: boolean, metadataUrl?: string, image?: { __typename?: 'Image', url: string }, smallImage?: { __typename?: 'Image', url: string }, originalImage?: { __typename?: 'Image', url: string }, collection?: { __typename?: 'NftCollection', name?: string, isVerified?: boolean, image?: { __typename?: 'Image', url: string }, creator?: { __typename?: 'NftProfile', address: string, isVerified?: boolean, profileImage?: { __typename?: 'Image', url: string } }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, standard?: NftStandard }> }, listings?: { __typename?: 'NftOrderConnection', edges: Array<{ __typename?: 'NftOrderEdge', cursor: string, node: { __typename?: 'NftOrder', address: string, createdAt: number, endAt?: number, id: string, maker: string, marketplace: NftMarketplace, marketplaceUrl: string, orderHash?: string, quantity: number, startAt: number, status: OrderStatus, taker?: string, tokenId?: string, type: OrderType, protocolParameters?: any, price: { __typename?: 'Amount', currency?: Currency, value: number } } }> }, rarities?: Array<{ __typename?: 'NftAssetRarity', provider?: NftRarityProvider, rank?: number, score?: number }> } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string, hasNextPage?: boolean, hasPreviousPage?: boolean, startCursor?: string } } };
export type CollectionQueryVariables = Exact<{
addresses: Array<Scalars['String']> | Scalars['String'];
}>;
export type CollectionQuery = { __typename?: 'Query', nftCollections?: { __typename?: 'NftCollectionConnection', edges: Array<{ __typename?: 'NftCollectionEdge', cursor: string, node: { __typename?: 'NftCollection', collectionId: string, description?: string, discordUrl?: string, homepageUrl?: string, instagramName?: string, isVerified?: boolean, name?: string, numAssets?: number, twitterName?: string, bannerImage?: { __typename?: 'Image', url: string }, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, chain: Chain, name?: string, standard?: NftStandard, symbol?: string, totalSupply?: number }>, traits?: Array<{ __typename?: 'NftCollectionTrait', name?: string, values?: Array<string>, stats?: Array<{ __typename?: 'NftCollectionTraitStats', name?: string, value?: string, assets?: number, listings?: number }> }>, markets?: Array<{ __typename?: 'NftCollectionMarket', owners?: number, floorPrice?: { __typename?: 'TimestampedAmount', currency?: Currency, value: number }, totalVolume?: { __typename?: 'TimestampedAmount', value: number, currency?: Currency }, listings?: { __typename?: 'TimestampedAmount', value: number }, volume?: { __typename?: 'TimestampedAmount', value: number, currency?: Currency }, volumePercentChange?: { __typename?: 'TimestampedAmount', value: number, currency?: Currency }, floorPricePercentChange?: { __typename?: 'TimestampedAmount', value: number, currency?: Currency }, marketplaces?: Array<{ __typename?: 'NftCollectionMarketplace', marketplace?: NftMarketplace, listings?: number, floorPrice?: number }> }> } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string, hasNextPage?: boolean, hasPreviousPage?: boolean, startCursor?: string } } };
export type DetailsQueryVariables = Exact<{
address: Scalars['String'];
tokenId: Scalars['String'];
}>;
export type DetailsQuery = { __typename?: 'Query', nftAssets?: { __typename?: 'NftAssetConnection', edges: Array<{ __typename?: 'NftAssetEdge', node: { __typename?: 'NftAsset', id: string, name?: string, ownerAddress?: string, tokenId: string, description?: string, animationUrl?: string, suspiciousFlag?: boolean, metadataUrl?: string, image?: { __typename?: 'Image', url: string }, smallImage?: { __typename?: 'Image', url: string }, originalImage?: { __typename?: 'Image', url: string }, creator?: { __typename?: 'NftProfile', address: string, isVerified?: boolean, profileImage?: { __typename?: 'Image', url: string } }, collection?: { __typename?: 'NftCollection', name?: string, isVerified?: boolean, numAssets?: number, twitterName?: string, discordUrl?: string, homepageUrl?: string, description?: string, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, standard?: NftStandard }> }, listings?: { __typename?: 'NftOrderConnection', edges: Array<{ __typename?: 'NftOrderEdge', cursor: string, node: { __typename?: 'NftOrder', address: string, createdAt: number, endAt?: number, id: string, maker: string, marketplace: NftMarketplace, marketplaceUrl: string, orderHash?: string, quantity: number, startAt: number, status: OrderStatus, taker?: string, tokenId?: string, type: OrderType, protocolParameters?: any, price: { __typename?: 'Amount', currency?: Currency, value: number } } }> }, rarities?: Array<{ __typename?: 'NftAssetRarity', provider?: NftRarityProvider, rank?: number, score?: number }>, traits?: Array<{ __typename?: 'NftAssetTrait', name?: string, value?: string }> } }> } };
export type NftBalanceQueryVariables = Exact<{
ownerAddress: Scalars['String'];
filter?: InputMaybe<NftBalancesFilterInput>;
first?: InputMaybe<Scalars['Int']>;
after?: InputMaybe<Scalars['String']>;
last?: InputMaybe<Scalars['Int']>;
before?: InputMaybe<Scalars['String']>;
}>;
export type NftBalanceQuery = { __typename?: 'Query', nftBalances?: { __typename?: 'NftBalanceConnection', edges: Array<{ __typename?: 'NftBalanceEdge', node: { __typename?: 'NftBalance', listedMarketplaces?: Array<NftMarketplace>, ownedAsset?: { __typename?: 'NftAsset', id: string, animationUrl?: string, description?: string, flaggedBy?: string, name?: string, ownerAddress?: string, suspiciousFlag?: boolean, tokenId: string, collection?: { __typename?: 'NftCollection', isVerified?: boolean, name?: string, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, chain: Chain, name?: string, standard?: NftStandard, symbol?: string, totalSupply?: number }>, markets?: Array<{ __typename?: 'NftCollectionMarket', floorPrice?: { __typename?: 'TimestampedAmount', value: number } }> }, image?: { __typename?: 'Image', url: string }, originalImage?: { __typename?: 'Image', url: string }, smallImage?: { __typename?: 'Image', url: string }, thumbnail?: { __typename?: 'Image', url: string }, listings?: { __typename?: 'NftOrderConnection', edges: Array<{ __typename?: 'NftOrderEdge', node: { __typename?: 'NftOrder', createdAt: number, marketplace: NftMarketplace, endAt?: number, price: { __typename?: 'Amount', value: number, currency?: Currency } } }> } }, listingFees?: Array<{ __typename?: 'NftFee', payoutAddress: string, basisPoints: number }>, lastPrice?: { __typename?: 'TimestampedAmount', currency?: Currency, timestamp: number, value: number } } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string, hasNextPage?: boolean, hasPreviousPage?: boolean, startCursor?: string } } };
export const TokenDocument = gql`
query Token($contract: ContractInput!) {
tokens(contracts: [$contract]) {
id
decimals
name
chain
address
symbol
market(currency: USD) {
totalValueLocked {
value
currency
}
price {
value
currency
}
volume24H: volume(duration: DAY) {
value
currency
}
priceHigh52W: priceHighLow(duration: YEAR, highLow: HIGH) {
value
}
priceLow52W: priceHighLow(duration: YEAR, highLow: LOW) {
value
}
}
project {
description
homepageUrl
twitterName
logoUrl
tokens {
chain
address
}
}
}
}
`;
/**
* __useTokenQuery__
*
* To run a query within a React component, call `useTokenQuery` and pass it any options that fit your needs.
* When your component renders, `useTokenQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useTokenQuery({
* variables: {
* contract: // value for 'contract'
* },
* });
*/
export function useTokenQuery(baseOptions: Apollo.QueryHookOptions<TokenQuery, TokenQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<TokenQuery, TokenQueryVariables>(TokenDocument, options);
}
export function useTokenLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TokenQuery, TokenQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<TokenQuery, TokenQueryVariables>(TokenDocument, options);
}
export type TokenQueryHookResult = ReturnType<typeof useTokenQuery>;
export type TokenLazyQueryHookResult = ReturnType<typeof useTokenLazyQuery>;
export type TokenQueryResult = Apollo.QueryResult<TokenQuery, TokenQueryVariables>;
export const TokenPriceDocument = gql`
query TokenPrice($contract: ContractInput!, $duration: HistoryDuration!) {
tokens(contracts: [$contract]) {
market(currency: USD) {
price {
value
}
priceHistory(duration: $duration) {
timestamp
value
}
}
}
}
`;
/**
* __useTokenPriceQuery__
*
* To run a query within a React component, call `useTokenPriceQuery` and pass it any options that fit your needs.
* When your component renders, `useTokenPriceQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useTokenPriceQuery({
* variables: {
* contract: // value for 'contract'
* duration: // value for 'duration'
* },
* });
*/
export function useTokenPriceQuery(baseOptions: Apollo.QueryHookOptions<TokenPriceQuery, TokenPriceQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<TokenPriceQuery, TokenPriceQueryVariables>(TokenPriceDocument, options);
}
export function useTokenPriceLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TokenPriceQuery, TokenPriceQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<TokenPriceQuery, TokenPriceQueryVariables>(TokenPriceDocument, options);
}
export type TokenPriceQueryHookResult = ReturnType<typeof useTokenPriceQuery>;
export type TokenPriceLazyQueryHookResult = ReturnType<typeof useTokenPriceLazyQuery>;
export type TokenPriceQueryResult = Apollo.QueryResult<TokenPriceQuery, TokenPriceQueryVariables>;
export const TopTokens100Document = gql`
query TopTokens100($duration: HistoryDuration!, $chain: Chain!) {
topTokens(pageSize: 100, page: 1, chain: $chain) {
id
name
chain
address
symbol
market(currency: USD) {
totalValueLocked {
value
currency
}
price {
value
currency
}
pricePercentChange(duration: $duration) {
currency
value
}
volume(duration: $duration) {
value
currency
}
}
project {
logoUrl
}
}
}
`;
/**
* __useTopTokens100Query__
*
* To run a query within a React component, call `useTopTokens100Query` and pass it any options that fit your needs.
* When your component renders, `useTopTokens100Query` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useTopTokens100Query({
* variables: {
* duration: // value for 'duration'
* chain: // value for 'chain'
* },
* });
*/
export function useTopTokens100Query(baseOptions: Apollo.QueryHookOptions<TopTokens100Query, TopTokens100QueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<TopTokens100Query, TopTokens100QueryVariables>(TopTokens100Document, options);
}
export function useTopTokens100LazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TopTokens100Query, TopTokens100QueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<TopTokens100Query, TopTokens100QueryVariables>(TopTokens100Document, options);
}
export type TopTokens100QueryHookResult = ReturnType<typeof useTopTokens100Query>;
export type TopTokens100LazyQueryHookResult = ReturnType<typeof useTopTokens100LazyQuery>;
export type TopTokens100QueryResult = Apollo.QueryResult<TopTokens100Query, TopTokens100QueryVariables>;
export const TopTokensSparklineDocument = gql`
query TopTokensSparkline($duration: HistoryDuration!, $chain: Chain!) {
topTokens(pageSize: 100, page: 1, chain: $chain) {
address
market(currency: USD) {
priceHistory(duration: $duration) {
timestamp
value
}
}
}
}
`;
/**
* __useTopTokensSparklineQuery__
*
* To run a query within a React component, call `useTopTokensSparklineQuery` and pass it any options that fit your needs.
* When your component renders, `useTopTokensSparklineQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useTopTokensSparklineQuery({
* variables: {
* duration: // value for 'duration'
* chain: // value for 'chain'
* },
* });
*/
export function useTopTokensSparklineQuery(baseOptions: Apollo.QueryHookOptions<TopTokensSparklineQuery, TopTokensSparklineQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<TopTokensSparklineQuery, TopTokensSparklineQueryVariables>(TopTokensSparklineDocument, options);
}
export function useTopTokensSparklineLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TopTokensSparklineQuery, TopTokensSparklineQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<TopTokensSparklineQuery, TopTokensSparklineQueryVariables>(TopTokensSparklineDocument, options);
}
export type TopTokensSparklineQueryHookResult = ReturnType<typeof useTopTokensSparklineQuery>;
export type TopTokensSparklineLazyQueryHookResult = ReturnType<typeof useTopTokensSparklineLazyQuery>;
export type TopTokensSparklineQueryResult = Apollo.QueryResult<TopTokensSparklineQuery, TopTokensSparklineQueryVariables>;
export const AssetDocument = gql`
query Asset($address: String!, $orderBy: NftAssetSortableField, $asc: Boolean, $filter: NftAssetsFilterInput, $first: Int, $after: String, $last: Int, $before: String) {
nftAssets(
address: $address
orderBy: $orderBy
asc: $asc
filter: $filter
first: $first
after: $after
last: $last
before: $before
) {
edges {
node {
id
name
ownerAddress
image {
url
}
smallImage {
url
}
originalImage {
url
}
tokenId
description
animationUrl
suspiciousFlag
collection {
name
isVerified
image {
url
}
creator {
address
profileImage {
url
}
isVerified
}
nftContracts {
address
standard
}
}
listings(first: 1) {
edges {
node {
address
createdAt
endAt
id
maker
marketplace
marketplaceUrl
orderHash
price {
currency
value
}
quantity
startAt
status
taker
tokenId
type
protocolParameters
}
cursor
}
}
rarities {
provider
rank
score
}
metadataUrl
}
cursor
}
totalCount
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`;
/**
* __useAssetQuery__
*
* To run a query within a React component, call `useAssetQuery` and pass it any options that fit your needs.
* When your component renders, `useAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useAssetQuery({
* variables: {
* address: // value for 'address'
* orderBy: // value for 'orderBy'
* asc: // value for 'asc'
* filter: // value for 'filter'
* first: // value for 'first'
* after: // value for 'after'
* last: // value for 'last'
* before: // value for 'before'
* },
* });
*/
export function useAssetQuery(baseOptions: Apollo.QueryHookOptions<AssetQuery, AssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<AssetQuery, AssetQueryVariables>(AssetDocument, options);
}
export function useAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AssetQuery, AssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<AssetQuery, AssetQueryVariables>(AssetDocument, options);
}
export type AssetQueryHookResult = ReturnType<typeof useAssetQuery>;
export type AssetLazyQueryHookResult = ReturnType<typeof useAssetLazyQuery>;
export type AssetQueryResult = Apollo.QueryResult<AssetQuery, AssetQueryVariables>;
export const CollectionDocument = gql`
query Collection($addresses: [String!]!) {
nftCollections(filter: {addresses: $addresses}) {
edges {
cursor
node {
bannerImage {
url
}
collectionId
description
discordUrl
homepageUrl
image {
url
}
instagramName
isVerified
name
numAssets
twitterName
nftContracts {
address
chain
name
standard
symbol
totalSupply
}
traits {
name
values
stats {
name
value
assets
listings
}
}
markets(currencies: ETH) {
floorPrice {
currency
value
}
owners
totalVolume {
value
currency
}
listings {
value
}
volume(duration: DAY) {
value
currency
}
volumePercentChange(duration: DAY) {
value
currency
}
floorPricePercentChange(duration: DAY) {
value
currency
}
marketplaces {
marketplace
listings
floorPrice
}
}
}
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`;
/**
* __useCollectionQuery__
*
* To run a query within a React component, call `useCollectionQuery` and pass it any options that fit your needs.
* When your component renders, `useCollectionQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useCollectionQuery({
* variables: {
* addresses: // value for 'addresses'
* },
* });
*/
export function useCollectionQuery(baseOptions: Apollo.QueryHookOptions<CollectionQuery, CollectionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<CollectionQuery, CollectionQueryVariables>(CollectionDocument, options);
}
export function useCollectionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CollectionQuery, CollectionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<CollectionQuery, CollectionQueryVariables>(CollectionDocument, options);
}
export type CollectionQueryHookResult = ReturnType<typeof useCollectionQuery>;
export type CollectionLazyQueryHookResult = ReturnType<typeof useCollectionLazyQuery>;
export type CollectionQueryResult = Apollo.QueryResult<CollectionQuery, CollectionQueryVariables>;
export const DetailsDocument = gql`
query Details($address: String!, $tokenId: String!) {
nftAssets(address: $address, filter: {listed: false, tokenIds: [$tokenId]}) {
edges {
node {
id
name
ownerAddress
image {
url
}
smallImage {
url
}
originalImage {
url
}
tokenId
description
animationUrl
suspiciousFlag
creator {
address
profileImage {
url
}
isVerified
}
collection {
name
isVerified
numAssets
twitterName
discordUrl
homepageUrl
image {
url
}
nftContracts {
address
standard
}
description
}
listings(first: 1) {
edges {
node {
address
createdAt
endAt
id
maker
marketplace
marketplaceUrl
orderHash
price {
currency
value
}
quantity
startAt
status
taker
tokenId
type
protocolParameters
}
cursor
}
}
rarities {
provider
rank
score
}
metadataUrl
traits {
name
value
}
}
}
}
}
`;
/**
* __useDetailsQuery__
*
* To run a query within a React component, call `useDetailsQuery` and pass it any options that fit your needs.
* When your component renders, `useDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useDetailsQuery({
* variables: {
* address: // value for 'address'
* tokenId: // value for 'tokenId'
* },
* });
*/
export function useDetailsQuery(baseOptions: Apollo.QueryHookOptions<DetailsQuery, DetailsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<DetailsQuery, DetailsQueryVariables>(DetailsDocument, options);
}
export function useDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<DetailsQuery, DetailsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<DetailsQuery, DetailsQueryVariables>(DetailsDocument, options);
}
export type DetailsQueryHookResult = ReturnType<typeof useDetailsQuery>;
export type DetailsLazyQueryHookResult = ReturnType<typeof useDetailsLazyQuery>;
export type DetailsQueryResult = Apollo.QueryResult<DetailsQuery, DetailsQueryVariables>;
export const NftBalanceDocument = gql`
query NftBalance($ownerAddress: String!, $filter: NftBalancesFilterInput, $first: Int, $after: String, $last: Int, $before: String) {
nftBalances(
ownerAddress: $ownerAddress
filter: $filter
first: $first
after: $after
last: $last
before: $before
) {
edges {
node {
ownedAsset {
id
animationUrl
collection {
isVerified
image {
url
}
name
nftContracts {
address
chain
name
standard
symbol
totalSupply
}
markets(currencies: ETH) {
floorPrice {
value
}
}
}
description
flaggedBy
image {
url
}
originalImage {
url
}
name
ownerAddress
smallImage {
url
}
suspiciousFlag
tokenId
thumbnail {
url
}
listings(first: 1) {
edges {
node {
price {
value
currency
}
createdAt
marketplace
endAt
}
}
}
}
listedMarketplaces
listingFees {
payoutAddress
basisPoints
}
lastPrice {
currency
timestamp
value
}
}
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`;
/**
* __useNftBalanceQuery__
*
* To run a query within a React component, call `useNftBalanceQuery` and pass it any options that fit your needs.
* When your component renders, `useNftBalanceQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useNftBalanceQuery({
* variables: {
* ownerAddress: // value for 'ownerAddress'
* filter: // value for 'filter'
* first: // value for 'first'
* after: // value for 'after'
* last: // value for 'last'
* before: // value for 'before'
* },
* });
*/
export function useNftBalanceQuery(baseOptions: Apollo.QueryHookOptions<NftBalanceQuery, NftBalanceQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<NftBalanceQuery, NftBalanceQueryVariables>(NftBalanceDocument, options);
}
export function useNftBalanceLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<NftBalanceQuery, NftBalanceQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<NftBalanceQuery, NftBalanceQueryVariables>(NftBalanceDocument, options);
}
export type NftBalanceQueryHookResult = ReturnType<typeof useNftBalanceQuery>;
export type NftBalanceLazyQueryHookResult = ReturnType<typeof useNftBalanceLazyQuery>;
export type NftBalanceQueryResult = Apollo.QueryResult<NftBalanceQuery, NftBalanceQueryVariables>;
\ No newline at end of file
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',
},
},
})
import graphql from 'babel-plugin-relay/macro'
import { parseEther } from 'ethers/lib/utils'
import useInterval from 'lib/hooks/useInterval'
import ms from 'ms.macro'
import { GenieAsset, Trait } from 'nft/types'
import gql from 'graphql-tag'
import { GenieAsset, Markets, Trait } from 'nft/types'
import { wrapScientificNotation } from 'nft/utils'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { fetchQuery, useLazyLoadQuery, usePaginationFragment, useQueryLoader, useRelayEnvironment } from 'react-relay'
import { useCallback, useMemo } from 'react'
import { AssetPaginationQuery } from './__generated__/AssetPaginationQuery.graphql'
import {
AssetQuery,
AssetQuery$variables,
AssetQueryVariables,
NftAssetEdge,
NftAssetsFilterInput,
NftAssetSortableField,
NftAssetTraitInput,
NftMarketplace,
} from './__generated__/AssetQuery.graphql'
import { AssetQuery_nftAssets$data } from './__generated__/AssetQuery_nftAssets.graphql'
useAssetQuery,
} from '../__generated__/types-and-hooks'
const assetPaginationQuery = graphql`
fragment AssetQuery_nftAssets on Query @refetchable(queryName: "AssetPaginationQuery") {
gql`
query Asset(
$address: String!
$orderBy: NftAssetSortableField
$asc: Boolean
$filter: NftAssetsFilterInput
$first: Int
$after: String
$last: Int
$before: String
) {
nftAssets(
address: $address
orderBy: $orderBy
......@@ -29,7 +34,7 @@ const assetPaginationQuery = graphql`
after: $after
last: $last
before: $before
) @connection(key: "AssetQuery_nftAssets") {
) {
edges {
node {
id
......@@ -99,52 +104,38 @@ const assetPaginationQuery = graphql`
}
metadataUrl
}
cursor
}
totalCount
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`
const assetQuery = graphql`
query AssetQuery(
$address: String!
$orderBy: NftAssetSortableField
$asc: Boolean
$filter: NftAssetsFilterInput
$first: Int
$after: String
$last: Int
$before: String
) {
...AssetQuery_nftAssets
}
`
type NftAssetsQueryAsset = NonNullable<
NonNullable<NonNullable<AssetQuery_nftAssets$data['nftAssets']>['edges']>[number]
>
function formatAssetQueryData(queryAsset: NftAssetsQueryAsset, totalCount?: number) {
function formatAssetQueryData(queryAsset: NftAssetEdge, totalCount?: number) {
const asset = queryAsset.node
const ethPrice = parseEther(wrapScientificNotation(asset.listings?.edges[0]?.node.price.value ?? 0)).toString()
return {
id: asset.id,
address: asset?.collection?.nftContracts?.[0]?.address,
address: asset?.collection?.nftContracts?.[0]?.address ?? '',
notForSale: asset.listings?.edges?.length === 0,
collectionName: asset.collection?.name,
collectionSymbol: asset.collection?.image?.url,
imageUrl: asset.image?.url,
animationUrl: asset.animationUrl,
marketplace: asset.listings?.edges[0]?.node?.marketplace?.toLowerCase(),
marketplace: asset.listings?.edges[0]?.node?.marketplace?.toLowerCase() as unknown as Markets,
name: asset.name,
priceInfo: asset.listings
? {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
}
: undefined,
priceInfo: {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
},
susFlag: asset.suspiciousFlag,
sellorders: asset.listings?.edges.map((listingNode) => {
return {
......@@ -155,7 +146,7 @@ function formatAssetQueryData(queryAsset: NftAssetsQueryAsset, totalCount?: numb
}
}),
smallImageUrl: asset.smallImage?.url,
tokenId: asset.tokenId,
tokenId: asset.tokenId ?? '',
tokenType: asset.collection?.nftContracts?.[0]?.standard,
totalCount,
collectionIsVerified: asset.collection?.isVerified,
......@@ -168,7 +159,7 @@ function formatAssetQueryData(queryAsset: NftAssetsQueryAsset, totalCount?: numb
}
}),
},
owner: asset.ownerAddress,
ownerAddress: asset.ownerAddress,
creator: {
profile_img_url: asset.collection?.creator?.profileImage?.url,
address: asset.collection?.creator?.address,
......@@ -190,57 +181,50 @@ export interface AssetFetcherParams {
before?: string
}
const defaultAssetFetcherParams: Omit<AssetQuery$variables, 'address'> = {
orderBy: 'PRICE',
const defaultAssetFetcherParams: Omit<AssetQueryVariables, 'address'> = {
orderBy: NftAssetSortableField.Price,
asc: true,
// tokenSearchQuery must be specified so that this exactly matches the initial query.
filter: { listed: false, tokenSearchQuery: '' },
first: ASSET_PAGE_SIZE,
}
export function useLoadAssetsQuery(address?: string) {
const [, loadQuery] = useQueryLoader<AssetQuery>(assetQuery)
useEffect(() => {
if (address) {
loadQuery({ ...defaultAssetFetcherParams, address })
}
}, [address, loadQuery])
}
export function useLazyLoadAssetsQuery(params: AssetFetcherParams) {
const vars = useMemo(() => ({ ...defaultAssetFetcherParams, ...params }), [params])
const [fetchKey, setFetchKey] = useState(0)
// Use the store if it is available (eg from polling), or the network if it is not (eg from an incorrect preload).
const fetchPolicy = 'store-or-network'
const queryData = useLazyLoadQuery<AssetQuery>(assetQuery, vars, { fetchKey, fetchPolicy }) // this will suspend if not yet loaded
export function useNftAssets(params: AssetFetcherParams) {
const variables = useMemo(() => ({ ...defaultAssetFetcherParams, ...params }), [params])
const { data, hasNext, loadNext, isLoadingNext } = usePaginationFragment<AssetPaginationQuery, any>(
assetPaginationQuery,
queryData
const { data, loading, fetchMore } = useAssetQuery({
variables,
})
const hasNext = data?.nftAssets?.pageInfo?.hasNextPage
const loadMore = useCallback(
() =>
fetchMore({
variables: {
after: data?.nftAssets?.pageInfo?.endCursor,
},
}),
[data, fetchMore]
)
// Poll for updates.
const POLLING_INTERVAL = ms`5s`
const environment = useRelayEnvironment()
const poll = useCallback(async () => {
if (data.nftAssets?.edges?.length > ASSET_PAGE_SIZE) return
// Initiate a network request. When it resolves, refresh the UI from store (to avoid re-triggering Suspense);
// see: https://relay.dev/docs/guided-tour/refetching/refreshing-queries/#if-you-need-to-avoid-suspense-1.
await fetchQuery<AssetQuery>(environment, assetQuery, { ...vars }).toPromise()
setFetchKey((fetchKey) => fetchKey + 1)
}, [data.nftAssets?.edges?.length, environment, vars])
useInterval(poll, isLoadingNext ? null : POLLING_INTERVAL, /* leading= */ false)
// TODO: setup polling while handling pagination
// It is especially important for this to be memoized to avoid re-rendering from polling if data is unchanged.
const assets: GenieAsset[] = useMemo(
const assets: GenieAsset[] | undefined = useMemo(
() =>
data.nftAssets?.edges?.map((queryAsset: NftAssetsQueryAsset) => {
return formatAssetQueryData(queryAsset, data.nftAssets?.totalCount)
data?.nftAssets?.edges?.map((queryAsset) => {
return formatAssetQueryData(queryAsset as NonNullable<NftAssetEdge>, data.nftAssets?.totalCount)
}),
[data.nftAssets?.edges, data.nftAssets?.totalCount]
[data?.nftAssets?.edges, data?.nftAssets?.totalCount]
)
return { assets, hasNext, isLoadingNext, loadNext }
return useMemo(() => {
return {
data: assets,
hasNext,
loading,
loadMore,
}
}, [assets, hasNext, loadMore, loading])
}
const DEFAULT_SWEEP_AMOUNT = 50
......@@ -252,7 +236,7 @@ export interface SweepFetcherParams {
traits?: Trait[]
}
function useSweepFetcherVars({ contractAddress, markets, price, traits }: SweepFetcherParams): AssetQuery$variables {
function useSweepFetcherVars({ contractAddress, markets, price, traits }: SweepFetcherParams): AssetQueryVariables {
const filter: NftAssetsFilterInput = useMemo(
() => ({
listed: true,
......@@ -272,7 +256,7 @@ function useSweepFetcherVars({ contractAddress, markets, price, traits }: SweepF
return useMemo(
() => ({
address: contractAddress,
orderBy: 'PRICE',
orderBy: NftAssetSortableField.Price,
asc: true,
first: DEFAULT_SWEEP_AMOUNT,
filter,
......@@ -281,28 +265,19 @@ function useSweepFetcherVars({ contractAddress, markets, price, traits }: SweepF
)
}
export function useLoadSweepAssetsQuery(params: SweepFetcherParams, enabled = true) {
const [, loadQuery] = useQueryLoader<AssetQuery>(assetQuery)
const vars = useSweepFetcherVars(params)
useEffect(() => {
if (enabled) {
loadQuery(vars)
}
}, [loadQuery, enabled, vars])
}
// Lazy-loads an already loaded AssetsQuery.
// This will *not* trigger a query - that must be done from a parent component to ensure proper query coalescing and to
// prevent waterfalling. Use useLoadSweepAssetsQuery to trigger the query.
export function useLazyLoadSweepAssetsQuery(params: SweepFetcherParams): GenieAsset[] {
const vars = useSweepFetcherVars(params)
const queryData = useLazyLoadQuery(assetQuery, vars, { fetchPolicy: 'store-only' }) // this will suspend if not yet loaded
const { data } = usePaginationFragment<AssetPaginationQuery, any>(assetPaginationQuery, queryData)
return useMemo<GenieAsset[]>(
export function useSweepNftAssets(params: SweepFetcherParams) {
const variables = useSweepFetcherVars(params)
const { data, loading } = useAssetQuery({
variables,
// This prevents overwriting the page's call to assets for cards shown
fetchPolicy: 'no-cache',
})
const assets = useMemo<GenieAsset[] | undefined>(
() =>
data.nftAssets?.edges?.map((queryAsset: NftAssetsQueryAsset) => {
return formatAssetQueryData(queryAsset, data.nftAssets?.totalCount)
data?.nftAssets?.edges?.map((queryAsset) => {
return formatAssetQueryData(queryAsset as NonNullable<NftAssetEdge>, data.nftAssets?.totalCount)
}),
[data.nftAssets?.edges, data.nftAssets?.totalCount]
[data?.nftAssets?.edges, data?.nftAssets?.totalCount]
)
return useMemo(() => ({ data: assets, loading }), [assets, loading])
}
import graphql from 'babel-plugin-relay/macro'
import gql from 'graphql-tag'
import { GenieCollection, Trait } from 'nft/types'
import { useEffect } from 'react'
import { useLazyLoadQuery, useQueryLoader } from 'react-relay'
import { useMemo } from 'react'
import { CollectionQuery } from './__generated__/CollectionQuery.graphql'
import { NftCollection, useCollectionQuery } from '../__generated__/types-and-hooks'
const collectionQuery = graphql`
query CollectionQuery($addresses: [String!]!) {
gql`
query Collection($addresses: [String!]!) {
nftCollections(filter: { addresses: $addresses }) {
edges {
cursor
......@@ -87,28 +86,23 @@ const collectionQuery = graphql`
}
`
export function useLoadCollectionQuery(address?: string | string[]): void {
const [, loadQuery] = useQueryLoader(collectionQuery)
useEffect(() => {
if (address) {
loadQuery({ addresses: Array.isArray(address) ? address : [address] })
}
}, [address, loadQuery])
interface useCollectionReturnProps {
data: GenieCollection
loading: boolean
}
// Lazy-loads an already loaded CollectionQuery.
// This will *not* trigger a query - that must be done from a parent component to ensure proper query coalescing and to
// prevent waterfalling. Use useLoadCollectionQuery to trigger the query.
export function useCollectionQuery(address: string): GenieCollection {
const queryData = useLazyLoadQuery<CollectionQuery>( // this will suspend if not yet loaded
collectionQuery,
{ addresses: [address] },
{ fetchPolicy: 'store-or-network' }
)
export function useCollection(address: string): useCollectionReturnProps {
const { data: queryData, loading } = useCollectionQuery({
variables: {
addresses: address,
},
})
const queryCollection = queryData.nftCollections?.edges[0]?.node
const market = queryCollection?.markets && queryCollection?.markets[0]
const traits = {} as Record<string, Trait[]>
const queryCollection = queryData?.nftCollections?.edges?.[0]?.node as NonNullable<NftCollection>
const market = queryCollection?.markets?.[0]
const traits = useMemo(() => {
return {} as Record<string, Trait[]>
}, [])
if (queryCollection?.traits) {
queryCollection?.traits.forEach((trait) => {
if (trait.name && trait.stats) {
......@@ -122,42 +116,43 @@ export function useCollectionQuery(address: string): GenieCollection {
}
})
}
return {
address,
isVerified: queryCollection?.isVerified ?? undefined,
name: queryCollection?.name ?? undefined,
description: queryCollection?.description ?? undefined,
standard: queryCollection?.nftContracts ? queryCollection?.nftContracts[0]?.standard ?? undefined : undefined,
bannerImageUrl: queryCollection?.bannerImage?.url ?? undefined,
stats: queryCollection?.markets
? {
num_owners: market?.owners ?? undefined,
floor_price: market?.floorPrice?.value ?? undefined,
one_day_volume: market?.volume?.value ?? undefined,
one_day_change: market?.volumePercentChange?.value ?? undefined,
one_day_floor_change: market?.floorPricePercentChange?.value ?? undefined,
banner_image_url: queryCollection?.bannerImage?.url ?? undefined,
total_supply: queryCollection?.numAssets ?? undefined,
total_listings: market?.listings?.value ?? undefined,
total_volume: market?.totalVolume?.value ?? undefined,
}
: {},
traits,
marketplaceCount: queryCollection?.markets
? market?.marketplaces?.map((market) => {
return useMemo(() => {
return {
data: {
address,
isVerified: queryCollection?.isVerified,
name: queryCollection?.name,
description: queryCollection?.description,
standard: queryCollection?.nftContracts?.[0]?.standard,
bannerImageUrl: queryCollection?.bannerImage?.url,
stats: {
num_owners: market?.owners,
floor_price: market?.floorPrice?.value,
one_day_volume: market?.volume?.value,
one_day_change: market?.volumePercentChange?.value,
one_day_floor_change: market?.floorPricePercentChange?.value,
banner_image_url: queryCollection?.bannerImage?.url,
total_supply: queryCollection?.numAssets,
total_listings: market?.listings?.value,
total_volume: market?.totalVolume?.value,
},
traits,
marketplaceCount: market?.marketplaces?.map((market) => {
return {
marketplace: market.marketplace?.toLowerCase() ?? '',
count: market.listings ?? 0,
floorPrice: market.floorPrice ?? 0,
}
})
: undefined,
imageUrl: queryCollection?.image?.url ?? '',
twitterUrl: queryCollection?.twitterName ?? '',
instagram: queryCollection?.instagramName ?? undefined,
discordUrl: queryCollection?.discordUrl ?? undefined,
externalUrl: queryCollection?.homepageUrl ?? undefined,
rarityVerified: false, // TODO update when backend supports
// isFoundation: boolean, // TODO ask backend to add
}
}),
imageUrl: queryCollection?.image?.url ?? '',
twitterUrl: queryCollection?.twitterName,
instagram: queryCollection?.instagramName,
discordUrl: queryCollection?.discordUrl,
externalUrl: queryCollection?.homepageUrl,
rarityVerified: false, // TODO update when backend supports
// isFoundation: boolean, // TODO ask backend to add
},
loading,
}
}, [address, loading, market, queryCollection, traits])
}
import { parseEther } from '@ethersproject/units'
import graphql from 'babel-plugin-relay/macro'
import { CollectionInfoForAsset, GenieAsset, SellOrder, TokenType } from 'nft/types'
import { useEffect } from 'react'
import { useLazyLoadQuery, useQueryLoader } from 'react-relay'
import gql from 'graphql-tag'
import { CollectionInfoForAsset, GenieAsset, Markets, SellOrder } from 'nft/types'
import { useMemo } from 'react'
import { DetailsQuery } from './__generated__/DetailsQuery.graphql'
import { NftAsset, useDetailsQuery } from '../__generated__/types-and-hooks'
const detailsQuery = graphql`
query DetailsQuery($address: String!, $tokenId: String!) {
gql`
query Details($address: String!, $tokenId: String!) {
nftAssets(address: $address, filter: { listed: false, tokenIds: [$tokenId] }) {
edges {
node {
......@@ -92,92 +91,87 @@ const detailsQuery = graphql`
}
`
export function useLoadDetailsQuery(address?: string, tokenId?: string): void {
const [, loadQuery] = useQueryLoader(detailsQuery)
useEffect(() => {
if (address && tokenId) {
loadQuery({ address, tokenId })
}
}, [address, tokenId, loadQuery])
}
export function useDetailsQuery(address: string, tokenId: string): [GenieAsset, CollectionInfoForAsset] | undefined {
const queryData = useLazyLoadQuery<DetailsQuery>(
detailsQuery,
{
export function useNftAssetDetails(
address: string,
tokenId: string
): { data: [GenieAsset, CollectionInfoForAsset]; loading: boolean } {
const { data: queryData, loading } = useDetailsQuery({
variables: {
address,
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 listing = asset?.listings?.edges[0]?.node
const ethPrice = parseEther(listing?.price?.value?.toString() ?? '0').toString()
return [
{
id: asset?.id,
address,
notForSale: asset?.listings === null,
collectionName: asset?.collection?.name ?? undefined,
collectionSymbol: asset?.collection?.image?.url ?? undefined,
imageUrl: asset?.image?.url ?? undefined,
animationUrl: asset?.animationUrl ?? undefined,
// todo: fix the back/frontend discrepency here and drop the any
marketplace: listing?.marketplace.toLowerCase() as any,
name: asset?.name ?? undefined,
priceInfo: {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
},
susFlag: asset?.suspiciousFlag ?? undefined,
sellorders: asset?.listings?.edges.map((listingNode) => {
return {
...listingNode.node,
protocolParameters: listingNode.node.protocolParameters
? JSON.parse(listingNode.node.protocolParameters.toString())
: undefined,
} as SellOrder
}),
smallImageUrl: asset?.smallImage?.url ?? undefined,
tokenId,
tokenType: (asset?.collection?.nftContracts && asset?.collection.nftContracts[0]?.standard) as TokenType,
collectionIsVerified: asset?.collection?.isVerified ?? undefined,
rarity: {
primaryProvider: 'Rarity Sniper', // TODO update when backend adds more providers
providers: asset?.rarities
? asset?.rarities?.map((rarity) => {
return useMemo(
() => ({
data: [
{
id: asset?.id,
address,
notForSale: asset?.listings === null,
collectionName: asset?.collection?.name,
collectionSymbol: asset?.collection?.image?.url,
imageUrl: asset?.image?.url,
animationUrl: asset?.animationUrl,
marketplace: listing?.marketplace.toLowerCase() as unknown as Markets,
name: asset?.name,
priceInfo: {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
},
susFlag: asset?.suspiciousFlag,
sellorders: asset?.listings?.edges.map((listingNode) => {
return {
...listingNode.node,
protocolParameters: listingNode.node.protocolParameters
? JSON.parse(listingNode.node.protocolParameters.toString())
: undefined,
} as SellOrder
}),
smallImageUrl: asset?.smallImage?.url,
tokenId,
tokenType: asset?.collection?.nftContracts?.[0]?.standard,
collectionIsVerified: asset?.collection?.isVerified,
rarity: {
primaryProvider: 'Rarity Sniper', // TODO update when backend adds more providers
providers: asset?.rarities?.map((rarity) => {
return {
rank: rarity.rank ?? undefined,
score: rarity.score ?? undefined,
rank: rarity.rank,
score: rarity.score,
provider: 'Rarity Sniper',
}
})
: undefined,
},
owner: { address: asset?.ownerAddress ?? '' },
creator: {
profile_img_url: asset?.creator?.profileImage?.url ?? '',
address: asset?.creator?.address ?? '',
},
metadataUrl: asset?.metadataUrl ?? '',
traits: asset?.traits?.map((trait) => {
return { trait_type: trait.name ?? '', trait_value: trait.value ?? '' }
}),
},
{
collectionDescription: collection?.description ?? undefined,
collectionImageUrl: collection?.image?.url ?? undefined,
collectionName: collection?.name ?? undefined,
isVerified: collection?.isVerified ?? undefined,
totalSupply: collection?.numAssets ?? undefined,
twitterUrl: collection?.twitterName ?? undefined,
discordUrl: collection?.discordUrl ?? undefined,
externalUrl: collection?.homepageUrl ?? undefined,
},
]
}),
},
ownerAddress: asset?.ownerAddress,
creator: {
profile_img_url: asset?.creator?.profileImage?.url ?? '',
address: asset?.creator?.address ?? '',
},
metadataUrl: asset?.metadataUrl ?? '',
traits: asset?.traits?.map((trait) => {
return { trait_type: trait.name ?? '', trait_value: trait.value ?? '' }
}),
},
{
collectionDescription: collection?.description,
collectionImageUrl: collection?.image?.url,
collectionName: collection?.name,
isVerified: collection?.isVerified,
totalSupply: collection?.numAssets,
twitterUrl: collection?.twitterName,
discordUrl: collection?.discordUrl,
externalUrl: collection?.homepageUrl,
},
],
loading,
}),
[address, asset, collection, ethPrice, listing?.marketplace, loading, tokenId]
)
}
import graphql from 'babel-plugin-relay/macro'
import { parseEther } from 'ethers/lib/utils'
import { DEFAULT_WALLET_ASSET_QUERY_AMOUNT } from 'nft/components/profile/view/ProfilePage'
import { WalletAsset } from 'nft/types'
import gql from 'graphql-tag'
import { GenieCollection, WalletAsset } from 'nft/types'
import { wrapScientificNotation } from 'nft/utils'
import { useEffect } from 'react'
import { useLazyLoadQuery, usePaginationFragment, useQueryLoader } from 'react-relay'
import { useCallback, useMemo } from 'react'
import { NftBalancePaginationQuery } from './__generated__/NftBalancePaginationQuery.graphql'
import { NftBalanceQuery } from './__generated__/NftBalanceQuery.graphql'
import { NftBalanceQuery_nftBalances$data } from './__generated__/NftBalanceQuery_nftBalances.graphql'
import { NftAsset, useNftBalanceQuery } from '../__generated__/types-and-hooks'
const nftBalancePaginationQuery = graphql`
fragment NftBalanceQuery_nftBalances on Query @refetchable(queryName: "NftBalancePaginationQuery") {
gql`
query NftBalance(
$ownerAddress: String!
$filter: NftBalancesFilterInput
$first: Int
$after: String
$last: Int
$before: String
) {
nftBalances(
ownerAddress: $ownerAddress
filter: $filter
......@@ -19,7 +22,7 @@ const nftBalancePaginationQuery = graphql`
after: $after
last: $last
before: $before
) @connection(key: "NftBalanceQuery_nftBalances") {
) {
edges {
node {
ownedAsset {
......@@ -99,43 +102,7 @@ const nftBalancePaginationQuery = graphql`
}
`
const nftBalanceQuery = graphql`
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(
export function useNftBalance(
ownerAddress: string,
collectionFilters?: string[],
assetsFilter?: { address: string; tokenId: string }[],
......@@ -144,9 +111,8 @@ export function useNftBalanceQuery(
last?: number,
before?: string
) {
const queryData = useLazyLoadQuery<NftBalanceQuery>(
nftBalanceQuery,
{
const { data, loading, fetchMore } = useNftBalanceQuery({
variables: {
ownerAddress,
filter:
assetsFilter && assetsFilter.length > 0
......@@ -161,14 +127,21 @@ export function useNftBalanceQuery(
last,
before,
},
{ fetchPolicy: 'store-or-network' }
)
const { data, hasNext, loadNext, isLoadingNext } = usePaginationFragment<NftBalancePaginationQuery, any>(
nftBalancePaginationQuery,
queryData
})
const hasNext = data?.nftBalances?.pageInfo?.hasNextPage
const loadMore = useCallback(
() =>
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()
return {
id: asset?.id,
......@@ -177,35 +150,32 @@ export function useNftBalanceQuery(
notForSale: asset?.listings?.edges?.length === 0,
animationUrl: asset?.animationUrl,
susFlag: asset?.suspiciousFlag,
priceInfo: asset?.listings
? {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
}
: undefined,
priceInfo: {
ETHPrice: ethPrice,
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: ethPrice,
},
name: asset?.name,
tokenId: asset?.tokenId,
asset_contract: {
address: asset?.collection?.nftContracts?.[0]?.address,
schema_name: asset?.collection?.nftContracts?.[0]?.standard,
tokenType: asset?.collection?.nftContracts?.[0]?.standard,
name: asset?.collection?.name,
description: asset?.description,
image_url: asset?.collection?.image?.url,
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,
lastPrice: queryAsset.node.lastPrice?.value,
floorPrice: asset?.collection?.markets?.[0]?.floorPrice?.value,
basisPoints: queryAsset?.node?.listingFees?.[0]?.basisPoints ?? 0 / 10000,
listing_date: asset?.listings?.edges?.[0]?.node?.createdAt,
date_acquired: queryAsset.node.lastPrice?.timestamp,
listing_date: asset?.listings?.edges?.[0]?.node?.createdAt?.toString(),
date_acquired: queryAsset.node.lastPrice?.timestamp?.toString(),
sellOrders: asset?.listings?.edges.map((edge: any) => edge.node),
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'
import { ZERO_ADDRESS } from 'constants/misc'
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 {
HOUR,
......@@ -15,15 +15,15 @@ export enum TimePeriod {
export function toHistoryDuration(timePeriod: TimePeriod): HistoryDuration {
switch (timePeriod) {
case TimePeriod.HOUR:
return 'HOUR'
return HistoryDuration.Hour
case TimePeriod.DAY:
return 'DAY'
return HistoryDuration.Day
case TimePeriod.WEEK:
return 'WEEK'
return HistoryDuration.Week
case TimePeriod.MONTH:
return 'MONTH'
return HistoryDuration.Month
case TimePeriod.YEAR:
return 'YEAR'
return HistoryDuration.Year
}
}
......@@ -34,16 +34,16 @@ export function isPricePoint(p: PricePoint | null): p is PricePoint {
}
export const CHAIN_ID_TO_BACKEND_NAME: { [key: number]: Chain } = {
[SupportedChainId.MAINNET]: 'ETHEREUM',
[SupportedChainId.GOERLI]: 'ETHEREUM_GOERLI',
[SupportedChainId.POLYGON]: 'POLYGON',
[SupportedChainId.POLYGON_MUMBAI]: 'POLYGON',
[SupportedChainId.CELO]: 'CELO',
[SupportedChainId.CELO_ALFAJORES]: 'CELO',
[SupportedChainId.ARBITRUM_ONE]: 'ARBITRUM',
[SupportedChainId.ARBITRUM_RINKEBY]: 'ARBITRUM',
[SupportedChainId.OPTIMISM]: 'OPTIMISM',
[SupportedChainId.OPTIMISM_GOERLI]: 'OPTIMISM',
[SupportedChainId.MAINNET]: Chain.Ethereum,
[SupportedChainId.GOERLI]: Chain.EthereumGoerli,
[SupportedChainId.POLYGON]: Chain.Polygon,
[SupportedChainId.POLYGON_MUMBAI]: Chain.Polygon,
[SupportedChainId.CELO]: Chain.Celo,
[SupportedChainId.CELO_ALFAJORES]: Chain.Celo,
[SupportedChainId.ARBITRUM_ONE]: Chain.Arbitrum,
[SupportedChainId.ARBITRUM_RINKEBY]: Chain.Arbitrum,
[SupportedChainId.OPTIMISM]: Chain.Optimism,
[SupportedChainId.OPTIMISM_GOERLI]: Chain.Optimism,
}
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 } = {
ethereum: 'ETHEREUM',
polygon: 'POLYGON',
celo: 'CELO',
arbitrum: 'ARBITRUM',
optimism: 'OPTIMISM',
ethereum: Chain.Ethereum,
polygon: Chain.Polygon,
celo: Chain.Celo,
arbitrum: Chain.Arbitrum,
optimism: Chain.Optimism,
}
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 } = {
......@@ -72,7 +72,7 @@ export const CHAIN_NAME_TO_CHAIN_ID: { [key: string]: SupportedChainId } = {
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 {
if (!chainName) return false
......@@ -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
const address = token.address.toLowerCase()
......
import graphql from 'babel-plugin-relay/macro'
import useInterval from 'lib/hooks/useInterval'
import { useCallback, useEffect, useState } from 'react'
import { fetchQuery } from 'react-relay'
import { useAppSelector } from 'state/hooks'
import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
import { useMemo } from 'react'
import type {
AllV3TicksQuery as AllV3TicksQueryType,
AllV3TicksQuery$data,
} from './__generated__/AllV3TicksQuery.graphql'
import environment from './RelayEnvironment'
import { AllV3TicksQuery } from './__generated__/types-and-hooks'
import { apolloClient } from './apollo'
const query = graphql`
query AllV3TicksQuery($poolAddress: String!, $skip: Int!) {
const query = gql`
query AllV3Ticks($poolAddress: String!, $skip: Int!) {
ticks(first: 1000, skip: $skip, where: { poolAddress: $poolAddress }, orderBy: tickIdx) {
tick: tickIdx
liquidityNet
......@@ -21,33 +16,29 @@ const query = graphql`
}
`
export type Ticks = AllV3TicksQuery$data['ticks']
export type Ticks = AllV3TicksQuery['ticks']
export type TickData = Ticks[number]
export default function useAllV3TicksQuery(poolAddress: string | undefined, skip: number, interval: number) {
const [data, setData] = useState<AllV3TicksQuery$data | null>(null)
const [error, setError] = useState<any>(null)
const [isLoading, setIsLoading] = useState(true)
const chainId = useAppSelector((state) => state.application.chainId)
const {
data,
loading: isLoading,
error,
} = useQuery(query, {
variables: {
poolAddress: poolAddress?.toLowerCase(),
skip,
},
pollInterval: interval,
client: apolloClient,
})
const refreshData = useCallback(() => {
if (poolAddress && chainId) {
fetchQuery<AllV3TicksQueryType>(environment, query, {
poolAddress: poolAddress.toLowerCase(),
skip,
}).subscribe({
next: setData,
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 }
return useMemo(
() => ({
error,
isLoading,
data,
}),
[data, error, isLoading]
)
}
import graphql from 'babel-plugin-relay/macro'
import useInterval from 'lib/hooks/useInterval'
import { useCallback, useEffect, useState } from 'react'
import { fetchQuery } from 'react-relay'
import { useAppSelector } from 'state/hooks'
import { ApolloError, useQuery } from '@apollo/client'
import gql from 'graphql-tag'
import { useMemo } from 'react'
import type {
FeeTierDistributionQuery as FeeTierDistributionQueryType,
FeeTierDistributionQuery$data,
} from './__generated__/FeeTierDistributionQuery.graphql'
import environment from './RelayEnvironment'
import { FeeTierDistributionQuery } from './__generated__/types-and-hooks'
import { apolloClient } from './apollo'
const query = graphql`
query FeeTierDistributionQuery($token0: String!, $token1: String!) {
const query = gql`
query FeeTierDistribution($token0: String!, $token1: String!) {
_meta {
block {
number
......@@ -42,28 +37,26 @@ export default function useFeeTierDistributionQuery(
token0: string | undefined,
token1: string | undefined,
interval: number
) {
const [data, setData] = useState<FeeTierDistributionQuery$data | null>(null)
const [error, setError] = useState<any>(null)
const [isLoading, setIsLoading] = useState(true)
const chainId = useAppSelector((state) => state.application.chainId)
): { error: ApolloError | undefined; isLoading: boolean; data: FeeTierDistributionQuery } {
const {
data,
loading: isLoading,
error,
} = useQuery(query, {
variables: {
token0: token0?.toLowerCase(),
token1: token1?.toLowerCase(),
},
pollInterval: interval,
client: apolloClient,
})
const refreshData = useCallback(() => {
if (token0 && token1 && chainId) {
fetchQuery<FeeTierDistributionQueryType>(environment, query, {
token0: token0.toLowerCase(),
token1: token1.toLowerCase(),
}).subscribe({
next: setData,
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 }
return useMemo(
() => ({
error,
isLoading,
data,
}),
[data, error, isLoading]
)
}
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 source diff could not be displayed because it is too large. You can view the blob instead.
/**
* 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 { ApolloClient, ApolloLink, concat, HttpLink, InMemoryCache } from '@apollo/client'
import { SupportedChainId } from 'constants/chains'
import { Variables } from 'react-relay'
import { GraphQLResponse, ObservableFromValue, RequestParameters } from 'relay-runtime'
import store, { AppState } from '../../state/index'
......@@ -23,31 +16,25 @@ const CHAIN_SUBGRAPH_URL: Record<number, string> = {
[SupportedChainId.CELO]: 'https://api.thegraph.com/subgraphs/name/jesse-sawa/uniswap-celo',
}
const headers = {
Accept: 'application/json',
'Content-type': 'application/json',
}
const httpLink = new HttpLink({ uri: CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET] })
// Define a function that fetches the results of a request (query/mutation/etc)
// and returns its results as a Promise:
const fetchQuery = (params: RequestParameters, variables: Variables): ObservableFromValue<GraphQLResponse> => {
// This middleware will allow us to dynamically update the uri for the requests based off chainId
// For more information: https://www.apollographql.com/docs/react/networking/advanced-http-networking/
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const chainId = (store.getState() as AppState).application.chainId
const subgraphUrl =
chainId && CHAIN_SUBGRAPH_URL[chainId] ? CHAIN_SUBGRAPH_URL[chainId] : CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET]
const body = JSON.stringify({
query: params.text, // GraphQL text from input
variables,
})
operation.setContext(() => ({
uri:
chainId && CHAIN_SUBGRAPH_URL[chainId]
? CHAIN_SUBGRAPH_URL[chainId]
: CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET],
}))
const response = fetch(subgraphUrl, {
method: 'POST',
headers,
body,
}).then((res) => res.json())
return response
}
return forward(operation)
})
export default fetchQuery
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: concat(authMiddleware, httpLink),
})
......@@ -165,7 +165,7 @@ function useAllV3Ticks(
): {
isLoading: boolean
error: unknown
ticks: readonly TickData[] | undefined
ticks: TickData[] | undefined
} {
const useSubgraph = currencyA ? !CHAIN_IDS_MISSING_SUBGRAPH_DATA.includes(currencyA.chainId) : true
......
......@@ -3,16 +3,16 @@ import 'inter-ui'
import 'polyfills'
import 'components/analytics'
import { ApolloProvider } from '@apollo/client'
import * as Sentry from '@sentry/react'
import { FeatureFlagsProvider } from 'featureFlags'
import RelayEnvironment from 'graphql/data/RelayEnvironment'
import { apolloClient } from 'graphql/data/apollo'
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
import { MulticallUpdater } from 'lib/state/multicall'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux'
import { RelayEnvironmentProvider } from 'react-relay'
import { HashRouter } from 'react-router-dom'
import { isProductionEnv } from 'utils/env'
......@@ -66,7 +66,7 @@ createRoot(container).render(
<HashRouter>
<LanguageProvider>
<Web3Provider>
<RelayEnvironmentProvider environment={RelayEnvironment}>
<ApolloProvider client={apolloClient}>
<BlockNumberProvider>
<Updaters />
<ThemeProvider>
......@@ -74,7 +74,7 @@ createRoot(container).render(
<App />
</ThemeProvider>
</BlockNumberProvider>
</RelayEnvironmentProvider>
</ApolloProvider>
</Web3Provider>
</LanguageProvider>
</HashRouter>
......
......@@ -71,7 +71,7 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
for (const listing of asset.newListings) {
if (!listing.price) listingsMissingPrice.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])
else if (asset.floor_sell_order_price && listing.price > asset.floor_sell_order_price)
listingsAboveSellOrderFloor.push([asset, listing])
......
......@@ -88,7 +88,7 @@ export const ListingSection = ({
return (
<Column key={index} gap="8">
<Row>
{row.images.map((image, index) => {
{row.images?.map((image, index) => {
return (
<Box
as="img"
......
......@@ -58,14 +58,15 @@ export async function approveCollectionRow(
: marketplace.name === 'X2Y2'
? X2Y2_TRANSFER_CONTRACT
: looksRareAddress
await approveCollection(spender ?? '', collectionAddress, signer, (newStatus: ListingStatus) =>
updateStatus({
listing: collectionRow,
newStatus,
rows: collectionsRequiringApproval,
setRows: setCollectionsRequiringApproval as Dispatch<AssetRow[]>,
})
)
!!collectionAddress &&
(await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
updateStatus({
listing: collectionRow,
newStatus,
rows: collectionsRequiringApproval,
setRows: setCollectionsRequiringApproval as Dispatch<AssetRow[]>,
})
))
if (collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) pauseAllRows()
}
......@@ -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
const maxFee =
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
......
......@@ -2,6 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import clsx from 'clsx'
import { OpacityHoverState } from 'components/Common'
import { MouseoverTooltip } from 'components/Tooltip'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import {
......@@ -16,7 +17,7 @@ import {
import { body, bodySmall, buttonTextMedium, subhead } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
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 { floorFormatter } from 'nft/utils/numbers'
import {
......@@ -579,8 +580,7 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
return !!asset.name ? asset.name : `#${asset.tokenId}`
}
const shouldShowUserListedPrice =
!!asset.floor_sell_order_price && !asset.notForSale && asset.asset_contract.tokenType !== TokenType.ERC1155
const shouldShowUserListedPrice = !asset.notForSale && asset.asset_contract.tokenType !== NftStandard.Erc1155
return (
<Box overflow="hidden" width="full" flexWrap="nowrap">
......@@ -605,7 +605,9 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
{asset.susFlag && <Suspicious />}
</Row>
<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>
</Box>
)
......
......@@ -4,10 +4,11 @@ import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
import { EventName, PageName } from '@uniswap/analytics-events'
import { MouseoverTooltip } from 'components/Tooltip'
import Tooltip from 'components/Tooltip'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Box } from 'nft/components/Box'
import { bodySmall } from 'nft/css/common.css'
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 { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro'
......@@ -212,7 +213,7 @@ export const CollectionAsset = ({
</Card.SecondaryInfo>
{isPooledMarket(asset.marketplace) && <Card.Pool />}
</Card.SecondaryDetails>
{asset.tokenType !== TokenType.ERC1155 && asset.marketplace && (
{asset.tokenType !== NftStandard.Erc1155 && asset.marketplace && (
<Card.MarketplaceIcon marketplace={asset.marketplace} />
)}
</Card.SecondaryRow>
......
......@@ -5,13 +5,8 @@ import { useWeb3React } from '@web3-react/core'
import clsx from 'clsx'
import { OpacityHoverState } from 'components/Common'
import { parseEther } from 'ethers/lib/utils'
import { NftAssetTraitInput, NftMarketplace } from 'graphql/data/nft/__generated__/AssetQuery.graphql'
import {
ASSET_PAGE_SIZE,
AssetFetcherParams,
useLazyLoadAssetsQuery,
useLoadSweepAssetsQuery,
} from 'graphql/data/nft/Asset'
import { NftAssetTraitInput, NftMarketplace, NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { ASSET_PAGE_SIZE, AssetFetcherParams, useNftAssets } from 'graphql/data/nft/Asset'
import useDebounce from 'hooks/useDebounce'
import { useScreenSize } from 'hooks/useScreenSize'
import { AnimatedBox, Box } from 'nft/components/Box'
......@@ -41,7 +36,6 @@ import {
GenieCollection,
isPooledMarket,
Markets,
TokenType,
UniformAspectRatio,
UniformAspectRatios,
} from 'nft/types'
......@@ -63,7 +57,7 @@ import { ThemedText } from 'theme'
import { CollectionAssetLoading } from './CollectionAssetLoading'
import { MARKETPLACE_ITEMS, MarketplaceLogo } from './MarketplaceSelect'
import { Sweep, useSweepFetcherParams } from './Sweep'
import { Sweep } from './Sweep'
import { TraitChip } from './TraitChip'
interface CollectionNftsProps {
......@@ -282,15 +276,6 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const [renderedHeight, setRenderedHeight] = useState<number | undefined>()
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 = {
address: contractAddress,
......@@ -312,8 +297,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
first: ASSET_PAGE_SIZE,
}
const { assets: collectionNfts, loadNext, hasNext, isLoadingNext } = useLazyLoadAssetsQuery(assetQueryParams)
const handleNextPageLoad = useCallback(() => loadNext(ASSET_PAGE_SIZE), [loadNext])
const { data: collectionNfts, loading, hasNext, loadMore } = useNftAssets(assetQueryParams)
const getPoolPosition = useCallback(
(asset: GenieAsset) => {
......@@ -394,8 +378,8 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const screenSize = useScreenSize()
useEffect(() => {
setIsCollectionNftsLoading(isLoadingNext)
}, [isLoadingNext, setIsCollectionNftsLoading])
setIsCollectionNftsLoading(loading)
}, [loading, setIsCollectionNftsLoading])
const hasRarity = useMemo(() => {
const hasRarity = getRarityStatus(rarityStatusCache, collectionStats?.address, collectionAssets) ?? false
......@@ -434,7 +418,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
}, [collectionAssets, isMobile, currentTokenPlayingMedia, rarityVerified, uniformAspectRatio, renderedHeight])
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(() => {
if (debouncedMinPrice && debouncedMaxPrice) {
......@@ -619,35 +603,37 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
</InfiniteScrollWrapper>
</AnimatedBox>
<InfiniteScrollWrapper>
<InfiniteScroll
next={handleNextPageLoad}
hasMore={hasNext}
loader={Boolean(hasNext && hasNfts) && <LoadingAssets height={renderedHeight} />}
dataLength={collectionAssets?.length ?? 0}
style={{ overflow: 'unset' }}
className={hasNfts || isLoadingNext ? styles.assetList : undefined}
>
{hasNfts ? (
assets
) : collectionAssets?.length === 0 ? (
<Center width="full" color="textSecondary" textAlign="center" style={{ height: '60vh' }}>
<EmptyCollectionWrapper>
<p className={headlineMedium}>No NFTS found</p>
<Box
onClick={reset}
type="button"
className={clsx(bodySmall, buttonTextMedium)}
color="accentAction"
cursor="pointer"
>
<ViewFullCollection>View full collection</ViewFullCollection>
</Box>
</EmptyCollectionWrapper>
</Center>
) : (
<CollectionNftsLoading height={renderedHeight} />
)}
</InfiniteScroll>
{loading ? (
<CollectionNftsLoading height={renderedHeight} />
) : (
<InfiniteScroll
next={loadMore}
hasMore={hasNext ?? false}
loader={Boolean(hasNext && hasNfts) && <LoadingAssets />}
dataLength={collectionAssets?.length ?? 0}
style={{ overflow: 'unset' }}
className={hasNfts ? styles.assetList : undefined}
>
{!hasNfts ? (
<Center width="full" color="textSecondary" textAlign="center" style={{ height: '60vh' }}>
<EmptyCollectionWrapper>
<p className={headlineMedium}>No NFTS found</p>
<Box
onClick={reset}
type="button"
className={clsx(bodySmall, buttonTextMedium)}
color="accentAction"
cursor="pointer"
>
<ViewFullCollection>View full collection</ViewFullCollection>
</Box>
</EmptyCollectionWrapper>
</Center>
) : (
assets
)}
</InfiniteScroll>
)}
</InfiniteScrollWrapper>
</>
)
......
......@@ -2,7 +2,7 @@ import 'rc-slider/assets/index.css'
import { BigNumber } from '@ethersproject/bignumber'
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 { GenieAsset, isPooledMarket, Markets } from 'nft/types'
import { calcPoolPrice, calcSudoSwapPrice, formatWeiToDecimal, isInSameSudoSwapPool } from 'nft/utils'
......@@ -178,13 +178,13 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
const nftxParams = useSweepFetcherParams(contractAddress, Markets.NFTX, minPrice, maxPrice)
const nft20Params = useSweepFetcherParams(contractAddress, Markets.NFT20, minPrice, maxPrice)
// These calls will suspend if the query is not yet loaded.
const collectionAssets = useLazyLoadSweepAssetsQuery(collectionParams)
const sudoSwapAsssets = useLazyLoadSweepAssetsQuery(sudoSwapParams)
const nftxAssets = useLazyLoadSweepAssetsQuery(nftxParams)
const nft20Assets = useLazyLoadSweepAssetsQuery(nft20Params)
const { data: collectionAssets } = useSweepNftAssets(collectionParams)
const { data: sudoSwapAssets } = useSweepNftAssets(sudoSwapParams)
const { data: nftxAssets } = useSweepNftAssets(nftxParams)
const { data: nft20Assets } = useSweepNftAssets(nft20Params)
const { sortedAssets, sortedAssetsTotalEth } = useMemo(() => {
if (!collectionAssets && !sudoSwapAsssets && !nftxAssets && !nft20Assets) {
if (!collectionAssets && !sudoSwapAssets && !nftxAssets && !nft20Assets) {
return { sortedAssets: undefined, sortedAssetsTotalEth: BigNumber.from(0) }
}
......@@ -193,7 +193,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
let jointCollections: GenieAsset[] = []
if (sudoSwapAsssets) jointCollections = [...jointCollections, ...sudoSwapAsssets]
if (sudoSwapAssets) jointCollections = [...jointCollections, ...sudoSwapAssets]
if (nftxAssets) jointCollections = [...jointCollections, ...nftxAssets]
if (nft20Assets) jointCollections = [...jointCollections, ...nft20Assets]
......@@ -236,7 +236,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
0,
Math.max(
collectionAssets?.length ?? 0,
sudoSwapAsssets?.length ?? 0,
sudoSwapAssets?.length ?? 0,
nftxAssets?.length ?? 0,
nft20Assets?.length ?? 0
)
......@@ -249,7 +249,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
BigNumber.from(0)
),
}
}, [collectionAssets, sudoSwapAsssets, nftxAssets, nft20Assets])
}, [collectionAssets, sudoSwapAssets, nftxAssets, nft20Assets])
const { sweepItemsInBag, sweepEthPrice } = useMemo(() => {
const sweepItemsInBag = itemsInBag
......@@ -435,7 +435,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
const ALL_OTHER_MARKETS = [Markets.Opensea, Markets.X2Y2, Markets.LooksRare]
export function useSweepFetcherParams(
function useSweepFetcherParams(
contractAddress: string,
market: Markets.Sudoswap | Markets.NFTX | Markets.NFT20 | 'others',
minPrice: string,
......
......@@ -263,7 +263,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
return MediaType.Audio
} else if (isVideo(asset.animationUrl ?? '')) {
return MediaType.Video
} else if (asset.animationUrl !== undefined) {
} else if (!!asset.animationUrl) {
return MediaType.Embed
}
return MediaType.Image
......
......@@ -3,7 +3,7 @@ import { sendAnalyticsEvent } from '@uniswap/analytics'
import { EventName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core'
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 { useBag, useProfilePageState, useSellAsset } from 'nft/hooks'
import { CollectionInfoForAsset, GenieAsset, ProfilePageStateType, WalletAsset } from 'nft/types'
......@@ -218,10 +218,10 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => {
const resetSellAssets = useSellAsset((state) => state.reset)
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(
() => (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]
)
const trace = useTrace()
......@@ -254,7 +254,7 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => {
{listing ? (
<>
<ThemedText.MediumHeader fontSize="28px" lineHeight="36px">
{formatEthPrice(asset.priceInfo.ETHPrice)} ETH
{formatEthPrice(asset.priceInfo?.ETHPrice)} ETH
</ThemedText.MediumHeader>
{USDPrice && (
<ThemedText.BodySecondary lineHeight="24px">
......@@ -320,7 +320,7 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
const { account } = useWeb3React()
const cheapestOrder = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const expirationDate = cheapestOrder ? new Date(cheapestOrder.endAt) : undefined
const expirationDate = cheapestOrder?.endAt ? new Date(cheapestOrder.endAt) : undefined
const itemsInBag = useBag((s) => s.itemsInBag)
const addAssetsToBag = useBag((s) => s.addAssetsToBag)
......@@ -331,11 +331,8 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
const USDPrice = useUsdPrice(asset)
const assetsFilter = [{ address: asset.address, tokenId: asset.tokenId }]
const { walletAssets: ownerAssets } = useNftBalanceQuery(account ?? '', [], assetsFilter, 1)
const walletAsset: WalletAsset | undefined = useMemo(
() => (ownerAssets?.length > 0 ? ownerAssets[0] : undefined),
[ownerAssets]
)
const { walletAssets: ownerAssets } = useNftBalance(account ?? '', [], assetsFilter, 1)
const walletAsset: WalletAsset | undefined = useMemo(() => ownerAssets?.[0], [ownerAssets])
const { assetInBag } = useMemo(() => {
return {
......@@ -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
return (
......@@ -424,20 +421,20 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
)}
{isForSale && (
<OwnerInformationContainer>
{asset.tokenType !== 'ERC1155' && asset.owner.address && (
{asset.tokenType !== 'ERC1155' && asset.ownerAddress && (
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
Seller:
</ThemedText.BodySmall>
)}
<OwnerText
target="_blank"
href={`https://etherscan.io/address/${asset.owner.address}`}
href={`https://etherscan.io/address/${asset.ownerAddress}`}
rel="noopener noreferrer"
>
{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>
</OwnerInformationContainer>
......
import { useLoadCollectionQuery } from 'graphql/data/nft/Collection'
import { fetchTrendingCollections } from 'nft/queries'
import { TimePeriod } from 'nft/types'
import { calculateCardIndex } from 'nft/utils'
import { Suspense, useCallback, useMemo, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useQuery } from 'react-query'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro'
......@@ -137,10 +136,6 @@ const Banner = () => {
[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 onToggleNextSlide = useCallback(
(direction: number) => {
......@@ -169,13 +164,11 @@ const Banner = () => {
{collections ? (
<Carousel activeIndex={activeCollectionIdx} toggleNextSlide={onToggleNextSlide}>
{collections.map((collection) => (
<Suspense fallback={<LoadingCarouselCard collection={collection} />} key={collection.address}>
<CarouselCard
key={collection.address}
collection={collection}
onClick={() => navigate(`/nfts/collection/${collection.address}`)}
/>
</Suspense>
<CarouselCard
key={collection.address}
collection={collection}
onClick={() => navigate(`/nfts/collection/${collection.address}`)}
/>
))}
</Carousel>
) : (
......
import { formatNumberOrString, NumberType } from '@uniswap/conedison/format'
import { loadingAnimation } from 'components/Loader/styled'
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 { Markets, TrendingCollection } from 'nft/types'
import { formatWeiToDecimal } from 'nft/utils'
......@@ -235,7 +235,9 @@ const MARKETS_ENUM_TO_NAME = {
}
export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
const gqlCollection = useCollectionQuery(collection.address)
const { data: gqlCollection, loading } = useCollection(collection.address)
if (loading) return <LoadingCarouselCard />
return (
<CarouselCardBorder>
......
......@@ -152,7 +152,7 @@ const PriceTextInput = ({
inputRef.current.value = listPrice !== undefined ? `${listPrice}` : ''
setWarningType(WarningType.NONE)
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)
setWarningType(WarningType.ALREADY_LISTED)
} else if (warning && listPrice && listPrice >= 0) removeMarketplaceWarning(asset, warning)
......@@ -226,10 +226,14 @@ const PriceTextInput = ({
>
{focused ? (
<>
<Row display={asset.lastPrice ? 'flex' : 'none'} marginRight="8">
LAST: {formatEth(asset.lastPrice)} ETH
</Row>
<Row display={asset.floorPrice ? 'flex' : 'none'}>FLOOR: {formatEth(asset.floorPrice)} ETH</Row>
{!!asset.lastPrice && (
<Row display={asset.lastPrice ? 'flex' : 'none'} marginRight="8">
LAST: {formatEth(asset.lastPrice)} ETH
</Row>
)}
{!!asset.floorPrice && (
<Row display={asset.floorPrice ? 'flex' : 'none'}>FLOOR: {formatEth(asset.floorPrice)} ETH</Row>
)}
</>
) : (
<>
......@@ -239,8 +243,8 @@ const PriceTextInput = ({
<>
{warningType}
{warningType === WarningType.BELOW_FLOOR
? formatEth(asset.floorPrice)
: formatEth(asset.floor_sell_order_price)}
? formatEth(asset?.floorPrice ?? 0)
: formatEth(asset?.floor_sell_order_price ?? 0)}
ETH
<Box
color={warningType === WarningType.BELOW_FLOOR ? 'accentAction' : 'orange'}
......@@ -335,7 +339,7 @@ const MarketplaceRow = ({
const royalties =
(selectedMarkets.length === 1 && selectedMarkets[0].name === 'LooksRare'
? LOOKS_RARE_CREATOR_BASIS_POINTS
: asset.basisPoints) * 0.01
: asset?.basisPoints ?? 0) * 0.01
const feeInEth = price && (price * (royalties + marketplaceFee)) / 100
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 { ClearAllButton, LoadingAssets } from 'nft/components/collection/CollectionNfts'
import { assetList } from 'nft/components/collection/CollectionNfts.css'
......@@ -198,10 +198,10 @@ const ProfilePageNfts = ({
const {
walletAssets: ownerAssets,
loadNext,
loading,
hasNext,
isLoadingNext,
} = useNftBalanceQuery(address, collectionFilters, [], DEFAULT_WALLET_ASSET_QUERY_AMOUNT)
loadMore,
} = useNftBalance(address, collectionFilters, [], DEFAULT_WALLET_ASSET_QUERY_AMOUNT)
const { gridX } = useSpring({
gridX: isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING,
......@@ -211,6 +211,8 @@ const ProfilePageNfts = ({
},
})
if (loading) return <ProfileBodyLoadingSkeleton />
return (
<Column width="full">
{ownerAssets?.length === 0 ? (
......@@ -242,13 +244,13 @@ const ProfilePageNfts = ({
/>
</Row>
<InfiniteScroll
next={() => loadNext(DEFAULT_WALLET_ASSET_QUERY_AMOUNT)}
hasMore={hasNext}
next={loadMore}
hasMore={hasNext ?? false}
loader={
Boolean(hasNext && ownerAssets?.length) && <LoadingAssets count={DEFAULT_WALLET_ASSET_QUERY_AMOUNT} />
}
dataLength={ownerAssets?.length ?? 0}
className={ownerAssets?.length || isLoadingNext ? assetList : undefined}
className={ownerAssets?.length ? assetList : undefined}
style={{ overflow: 'unset' }}
>
{ownerAssets?.length
......
......@@ -4,13 +4,14 @@ import { sendAnalyticsEvent } from '@uniswap/analytics'
import { EventName } from '@uniswap/analytics-events'
import { MouseoverTooltip } from 'components/Tooltip'
import Tooltip from 'components/Tooltip'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Box } from 'nft/components/Box'
import * as Card from 'nft/components/collection/Card'
import { AssetMediaType } from 'nft/components/collection/Card'
import { bodySmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
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'
const TOOLTIP_TIMEOUT = 2000
......@@ -39,7 +40,7 @@ const getNftDisplayComponent = (
const getUnsupportedNftTextComponent = (asset: WalletAsset) => (
<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>Blocked from trading</Trans>
......@@ -109,7 +110,7 @@ export const ViewMyNftsAsset = ({
}, [isSelected, isSelectedRef])
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 (
<Card.Container
......
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 create from 'zustand'
import { devtools } from 'zustand/middleware'
......@@ -88,7 +89,7 @@ export const useBag = create<BagState>()(
const itemsInBagCopy = [...itemsInBag]
assets.forEach((asset) => {
let index = -1
if (asset.tokenType !== TokenType.ERC1155) {
if (asset.tokenType !== NftStandard.Erc1155) {
index = itemsInBag.findIndex(
(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 { devtools } from 'zustand/middleware'
......
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import create from 'zustand'
import { devtools } from 'zustand/middleware'
......@@ -28,7 +29,7 @@ export const useWalletCollections = create<WalletCollectionState>()(
setWalletAssets: (assets) =>
set(() => {
return {
walletAssets: assets?.filter((asset) => asset.asset_contract?.schema_name === 'ERC721'),
walletAssets: assets?.filter((asset) => asset.asset_contract?.tokenType === NftStandard.Erc721),
}
}),
setWalletCollections: (collections) =>
......
import { Trace } from '@uniswap/analytics'
import { PageName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core'
import { useDetailsQuery, useLoadDetailsQuery } from 'graphql/data/nft/Details'
import { useLoadNftBalanceQuery } from 'graphql/data/nft/NftBalance'
import { useNftAssetDetails } from 'graphql/data/nft/Details'
import { AssetDetails } from 'nft/components/details/AssetDetails'
import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading'
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 styled from 'styled-components/macro'
......@@ -38,11 +34,13 @@ const AssetPriceDetailsContainer = styled.div`
}
`
const Asset = () => {
const AssetPage = () => {
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 (
<>
......@@ -51,35 +49,17 @@ const Asset = () => {
properties={{ collection_address: contractAddress, token_id: tokenId }}
shouldLogImpression
>
{asset && collection ? (
{!!asset && !!collection && (
<AssetContainer>
<AssetDetails collection={collection} asset={asset} />
<AssetPriceDetailsContainer>
<AssetPriceDetails collection={collection} asset={asset} />
</AssetPriceDetailsContainer>
</AssetContainer>
) : null}
)}
</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
......@@ -5,8 +5,7 @@ import Column from 'components/Column'
import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { useLoadAssetsQuery } from 'graphql/data/nft/Asset'
import { useCollectionQuery, useLoadCollectionQuery } from 'graphql/data/nft/Collection'
import { useCollection } from 'graphql/data/nft/Collection'
import { useScreenSize } from 'hooks/useScreenSize'
import { BAG_WIDTH, XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag'
......@@ -16,7 +15,6 @@ import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPage
import { BagCloseIcon } from 'nft/components/icons'
import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks'
import * as styles from 'nft/pages/collection/index.css'
import { GenieCollection } from 'nft/types'
import { Suspense, useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { animated, easings, useSpring } from 'react-spring'
......@@ -26,6 +24,7 @@ import { TRANSITION_DURATIONS } from 'theme/styles'
import { Z_INDEX } from 'theme/zIndex'
const FILTER_WIDTH = 332
const EMPTY_TRAIT_OBJ = {}
export const CollectionBannerLoading = styled(LoadingBubble)`
width: 100%;
......@@ -133,7 +132,7 @@ const Collection = () => {
const { chainId } = useWeb3React()
const screenSize = useScreenSize()
const collectionStats = useCollectionQuery(contractAddress as string)
const { data: collectionStats, loading } = useCollection(contractAddress as string)
const { CollectionContainerWidthChange } = useSpring({
CollectionContainerWidthChange:
......@@ -169,6 +168,8 @@ const Collection = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
if (loading) return <CollectionPageSkeleton />
const toggleActivity = () => {
isActivityToggled
? navigate(`/nfts/collection/${contractAddress}`)
......@@ -197,9 +198,7 @@ const Collection = () => {
/>
</BannerWrapper>
<CollectionDescriptionSection>
{collectionStats && (
<CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} />
)}
{collectionStats && <CollectionStats stats={collectionStats} isMobile={isMobile} />}
<div id="nft-anchor" />
<ActivitySwitcher
showActivity={isActivityToggled}
......@@ -221,7 +220,7 @@ const Collection = () => {
</IconWrapper>
</MobileFilterHeader>
)}
<Filters traitsByGroup={collectionStats?.traits ?? {}} />
<Filters traitsByGroup={collectionStats?.traits ?? EMPTY_TRAIT_OBJ} />
</>
)}
</FiltersContainer>
......@@ -246,7 +245,7 @@ const Collection = () => {
collectionStats && (
<Suspense fallback={<CollectionNftsAndMenuLoading />}>
<CollectionNfts
collectionStats={collectionStats || ({} as GenieCollection)}
collectionStats={collectionStats}
contractAddress={contractAddress}
rarityVerified={collectionStats?.rarityVerified}
/>
......@@ -265,21 +264,4 @@ const Collection = () => {
)
}
// The page is responsible for any queries that must be run on initial load.
// 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
export default Collection
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 ({
toSell,
......@@ -41,7 +42,7 @@ type RouteItem = {
decimals: number
address: string
priceInfo: ApiPriceInfo
tokenType: TokenType
tokenType?: NftStandard
tokenId: string
amount: number
marketplace?: string
......
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { SortBy } from 'nft/hooks'
import { SellOrder } from '../sell'
......@@ -81,7 +82,7 @@ export interface Trait {
export interface GenieAsset {
id?: string // This would be a random id created and assigned by front end
address: string
notForSale: boolean
notForSale?: boolean
collectionName?: string
collectionSymbol?: string
imageUrl?: string
......@@ -93,17 +94,15 @@ export interface GenieAsset {
sellorders?: SellOrder[]
smallImageUrl?: string
tokenId: string
tokenType: TokenType
tokenType?: NftStandard
totalCount?: number // The totalCount from the query to /assets
collectionIsVerified?: boolean
rarity?: Rarity
owner: {
address: string
}
metadataUrl: string
ownerAddress?: string
metadataUrl?: string
creator: {
address: string
profile_img_url: string
address?: string
profile_img_url?: string
}
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 {
name: string
......@@ -15,20 +15,20 @@ export interface ListingWarning {
export interface SellOrder {
address: string
createdAt: number
endAt: number
endAt?: number
id: string
maker: string
marketplace: NftMarketplace
marketplaceUrl: string
orderHash: string
orderHash?: string
price: {
currency: string
currency?: string
value: number
}
quantity: number
startAt: number
status: OrderStatus
tokenId: string
tokenId?: string
type: OrderType
protocolParameters: Record<string, unknown>
}
......@@ -41,32 +41,31 @@ export interface Listing {
export interface WalletAsset {
id?: string
imageUrl: string
smallImageUrl: string
imageUrl?: string
smallImageUrl?: string
notForSale: boolean
animationUrl: string
susFlag: boolean
priceInfo: PriceInfo
name: string
tokenId: string
animationUrl?: string
susFlag?: boolean
priceInfo?: PriceInfo
name?: string
tokenId?: string
asset_contract: {
address: string
schema_name: 'ERC1155' | 'ERC721' | string
name: string
description: string
image_url: string
payout_address: string
tokenType: TokenType
address?: string
name?: string
description?: string
image_url?: string
payout_address?: string
tokenType?: NftStandard
}
collection: GenieCollection
collectionIsVerified: boolean
lastPrice: number
floorPrice: number
basisPoints: number
listing_date: string
date_acquired: string
sellOrders: SellOrder[]
floor_sell_order_price: number
collection?: GenieCollection
collectionIsVerified?: boolean
lastPrice?: number
floorPrice?: number
basisPoints?: number
listing_date?: string
date_acquired?: string
sellOrders?: SellOrder[]
floor_sell_order_price?: number
// Used for creating new listings
expirationTime?: number
marketAgnosticPrice?: number
......@@ -95,8 +94,8 @@ export enum ListingStatus {
}
export interface AssetRow {
images: string[]
name: string
images: (string | undefined)[]
name?: string
status: ListingStatus
callback?: () => Promise<void>
}
......@@ -108,7 +107,7 @@ export interface ListingRow extends AssetRow {
}
export interface CollectionRow extends AssetRow {
collectionAddress: string
collectionAddress?: string
marketplace: ListingMarket
}
......
......@@ -62,7 +62,7 @@ const getConsiderationItems = (
creatorFee?: ConsiderationInputItem
} => {
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 openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString()
......@@ -76,7 +76,9 @@ const getConsiderationItems = (
sellerFee: createConsiderationItem(sellerFee, signerAddress),
openseaFee: createConsiderationItem(openseaFee, OPENSEA_FEE_ADDRESS),
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(
const signerAddress = await signer.getAddress()
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) {
case 'OpenSea':
try {
......
......@@ -6,9 +6,6 @@ import TopLevelModals from 'components/TopLevelModals'
import { useFeatureFlagsIsLoaded } from 'featureFlags'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
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 { Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { useIsDarkMode } from 'state/user/hooks'
......@@ -251,7 +248,6 @@ export default function App() {
<Route
path="/nfts"
element={
// TODO: replace loading state during Apollo migration
<Suspense fallback={null}>
<NftExplore />
</Suspense>
......@@ -260,7 +256,7 @@ export default function App() {
<Route
path="/nfts/asset/:contractAddress/:tokenId"
element={
<Suspense fallback={<AssetDetailsLoading />}>
<Suspense fallback={null}>
<Asset />
</Suspense>
}
......@@ -268,7 +264,7 @@ export default function App() {
<Route
path="/nfts/profile"
element={
<Suspense fallback={<ProfilePageLoadingSkeleton />}>
<Suspense fallback={null}>
<Profile />
</Suspense>
}
......@@ -276,7 +272,7 @@ export default function App() {
<Route
path="/nfts/collection/:contractAddress"
element={
<Suspense fallback={<CollectionPageSkeleton />}>
<Suspense fallback={null}>
<Collection />
</Suspense>
}
......@@ -284,7 +280,7 @@ export default function App() {
<Route
path="/nfts/collection/:contractAddress/activity"
element={
<Suspense fallback={<CollectionPageSkeleton />}>
<Suspense fallback={null}>
<Collection />
</Suspense>
}
......
import TokenDetails from 'components/Tokens/TokenDetails'
import { TokenDetailsPageSkeleton } from 'components/Tokens/TokenDetails/Skeleton'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { TokenQuery, tokenQuery } from 'graphql/data/Token'
import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice'
import { useTokenPriceQuery, useTokenQuery } from 'graphql/data/__generated__/types-and-hooks'
import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod, toHistoryDuration, validateUrlChainParam } from 'graphql/data/util'
import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
import { Suspense, useCallback, useEffect, useMemo } from 'react'
import { useQueryLoader } from 'react-relay'
import { useMemo } from 'react'
import { useParams } from 'react-router-dom'
export const pageTimePeriodAtom = atomWithStorage<TimePeriod>('tokenDetailsTimePeriod', TimePeriod.DAY)
......@@ -26,35 +24,28 @@ export default function TokenDetailsPage() {
[chain, isNative, pageChainId, timePeriod, tokenAddress]
)
const [tokenQueryReference, loadTokenQuery] = useQueryLoader<TokenQuery>(tokenQuery)
const [priceQueryReference, loadPriceQuery] = useQueryLoader<TokenPriceQuery>(tokenPriceQuery)
useEffect(() => {
loadTokenQuery({ contract })
loadPriceQuery({ contract, duration })
}, [contract, duration, loadPriceQuery, loadTokenQuery, timePeriod])
const { data: tokenQuery, loading: tokenQueryLoading } = useTokenQuery({
variables: {
contract,
},
})
const refetchTokenPrices = useCallback(
(t: TimePeriod) => {
loadPriceQuery({ contract, duration: toHistoryDuration(t) })
setTimePeriod(t)
const { data: tokenPriceQuery } = useTokenPriceQuery({
variables: {
contract,
duration,
},
[contract, loadPriceQuery, setTimePeriod]
)
})
if (!tokenQueryReference) {
return <TokenDetailsPageSkeleton />
}
if (!tokenQuery || tokenQueryLoading) return <TokenDetailsPageSkeleton />
return (
<Suspense fallback={<TokenDetailsPageSkeleton />}>
<TokenDetails
urlAddress={tokenAddress}
chain={chain}
tokenQueryReference={tokenQueryReference}
priceQueryReference={priceQueryReference}
refetchTokenPrices={refetchTokenPrices}
/>
</Suspense>
<TokenDetails
urlAddress={tokenAddress}
chain={chain}
tokenQuery={tokenQuery}
tokenPriceQuery={tokenPriceQuery}
onChangeTimePeriod={setTimePeriod}
/>
)
}
......@@ -7,12 +7,11 @@ import { filterStringAtom } from 'components/Tokens/state'
import NetworkFilter from 'components/Tokens/TokenTable/NetworkFilter'
import SearchBar from 'components/Tokens/TokenTable/SearchBar'
import TimeSelector from 'components/Tokens/TokenTable/TimeSelector'
import TokenTable, { LoadingTokenTable } from 'components/Tokens/TokenTable/TokenTable'
import { PAGE_SIZE } from 'graphql/data/TopTokens'
import TokenTable from 'components/Tokens/TokenTable/TokenTable'
import { chainIdToBackendName, isValidBackendChainName } from 'graphql/data/util'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { useResetAtom } from 'jotai/utils'
import { Suspense, useEffect, useState } from 'react'
import { useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
......@@ -75,8 +74,6 @@ const Tokens = () => {
const { chainId: connectedChainId } = useWeb3React()
const connectedChainName = chainIdToBackendName(connectedChainId)
const [rowCount, setRowCount] = useState(PAGE_SIZE)
useEffect(() => {
resetFilterString()
}, [location, resetFilterString])
......@@ -110,9 +107,7 @@ const Tokens = () => {
<SearchBar />
</SearchContainer>
</FiltersWrapper>
<Suspense fallback={<LoadingTokenTable rowCount={rowCount} />}>
<TokenTable setRowCount={setRowCount} />
</Suspense>
<TokenTable />
</ExploreContainer>
</Trace>
)
......
......@@ -25,7 +25,3 @@ declare module 'multihashes' {
declare function decode(buff: Uint8Array): { code: number; name: string; length: number; digest: Uint8Array }
declare function toB58String(hash: Uint8Array): string
}
declare module 'babel-plugin-relay/macro' {
export { graphql as default } from 'react-relay'
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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