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