Commit 91f4892b authored by cartcrom's avatar cartcrom Committed by GitHub

feat: integrate relay (#4320)

* setup relay compiler
* refactored to use polling interval, fixed PR comments
* fixes, readded uninitialized state for liquidity chart
* updated cypress test
* reorganized graphql files into src/graphql
parent d6d0a98a
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
"src/abis/types", "src/abis/types",
"src/locales/**/*.js", "src/locales/**/*.js",
"src/locales/**/en-US.po", "src/locales/**/en-US.po",
"src/state/data/generated.ts",
"node_modules", "node_modules",
"coverage", "coverage",
"build", "build",
......
# See https://help.github.com/ignore-files/ for more about ignoring files. # See https://help.github.com/ignore-files/ for more about ignoring files.
# generated contract types # generated contract types
/src/types/v3 /src/types/v3
/src/abis/types /src/abis/types
/src/locales/**/*.js /src/locales/**/*.js
/src/locales/**/en-US.po /src/locales/**/en-US.po
/src/locales/**/pseudo.po /src/locales/**/pseudo.po
/src/state/data/generated.ts
# generated graphql types
/src/graphql/schema/
__generated__/
# dependencies # dependencies
/node_modules /node_modules
......
/src/state/data/generated.ts /src/schema/schema.graphql
\ No newline at end of file \ No newline at end of file
...@@ -2,10 +2,6 @@ overrideExisting: true ...@@ -2,10 +2,6 @@ overrideExisting: true
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3' schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
documents: 'src/**/!(*.d).{ts,tsx}' documents: 'src/**/!(*.d).{ts,tsx}'
generates: generates:
./src/state/data/generated.ts: ./src/graphql/schema/schema.graphql:
plugins: plugins:
- typescript - schema-ast
- typescript-operations
- typescript-rtk-query:
importBaseApiFrom: './slice'
exportHooks: true
...@@ -37,8 +37,8 @@ describe('Add Liquidity', () => { ...@@ -37,8 +37,8 @@ describe('Add Liquidity', () => {
it('loads fee tier distribution', () => { it('loads fee tier distribution', () => {
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => { cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => { cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
if (hasQuery(req, 'feeTierDistribution')) { if (hasQuery(req, 'FeeTierDistributionQuery')) {
req.alias = 'feeTierDistributionQuery' req.alias = 'FeeTierDistributionQuery'
req.reply({ req.reply({
body: { body: {
...@@ -55,7 +55,7 @@ describe('Add Liquidity', () => { ...@@ -55,7 +55,7 @@ describe('Add Liquidity', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab') cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.wait('@feeTierDistributionQuery') cy.wait('@FeeTierDistributionQuery')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier') cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40%') cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40%')
......
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
"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",
"graphql:generate": "graphql-codegen --config codegen.yml", "relay": "relay-compiler",
"graphql:generate": "graphql-codegen --config codegen.yml && yarn relay",
"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",
...@@ -22,6 +23,11 @@ ...@@ -22,6 +23,11 @@
"cypress:open": "cypress open --browser chrome --e2e", "cypress:open": "cypress open --browser chrome --e2e",
"cypress:run": "cypress run --browser chrome --e2e" "cypress:run": "cypress run --browser chrome --e2e"
}, },
"relay": {
"src": "./src",
"language": "typescript",
"schema": "./src/graphql/schema/schema.graphql"
},
"jest": { "jest": {
"collectCoverageFrom": [ "collectCoverageFrom": [
"src/components/**/*.ts*", "src/components/**/*.ts*",
...@@ -58,8 +64,7 @@ ...@@ -58,8 +64,7 @@
"@craco/craco": "6.4.3", "@craco/craco": "6.4.3",
"@ethersproject/experimental": "^5.4.0", "@ethersproject/experimental": "^5.4.0",
"@graphql-codegen/cli": "1.21.5", "@graphql-codegen/cli": "1.21.5",
"@graphql-codegen/typescript": "1.22.3", "@graphql-codegen/schema-ast": "^2.5.1",
"@graphql-codegen/typescript-operations": "^1.18.2",
"@graphql-codegen/typescript-rtk-query": "^1.1.1", "@graphql-codegen/typescript-rtk-query": "^1.1.1",
"@lingui/cli": "^3.9.0", "@lingui/cli": "^3.9.0",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
...@@ -90,6 +95,7 @@ ...@@ -90,6 +95,7 @@
"@types/wcag-contrast": "^3.0.0", "@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4", "@typescript-eslint/eslint-plugin": "^4",
"@typescript-eslint/parser": "^4", "@typescript-eslint/parser": "^4",
"babel-plugin-relay": "^14.1.0",
"@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",
"cypress": "^10.3.1", "cypress": "^10.3.1",
...@@ -106,6 +112,7 @@ ...@@ -106,6 +112,7 @@
"ms.macro": "^2.0.0", "ms.macro": "^2.0.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",
"typechain": "^5.0.0", "typechain": "^5.0.0",
"typescript": "^4.4.3" "typescript": "^4.4.3"
...@@ -126,6 +133,7 @@ ...@@ -126,6 +133,7 @@
"@reach/portal": "^0.10.3", "@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0", "@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.6.1", "@reduxjs/toolkit": "^1.6.1",
"@types/react-relay": "^13.0.2",
"@uniswap/governance": "^1.0.2", "@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2", "@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1", "@uniswap/merkle-distributor": "1.0.1",
...@@ -174,7 +182,7 @@ ...@@ -174,7 +182,7 @@
"firebase": "^9.1.3", "firebase": "^9.1.3",
"focus-visible": "^5.2.0", "focus-visible": "^5.2.0",
"fortmatic": "^2.4.0", "fortmatic": "^2.4.0",
"graphql": "^15.5.0", "graphql": "^16.5.0",
"graphql-request": "^3.4.0", "graphql-request": "^3.4.0",
"immer": "^9.0.6", "immer": "^9.0.6",
"inter-ui": "^3.13.1", "inter-ui": "^3.13.1",
...@@ -199,6 +207,7 @@ ...@@ -199,6 +207,7 @@
"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-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-table": "^7.8.0", "react-table": "^7.8.0",
...@@ -208,6 +217,7 @@ ...@@ -208,6 +217,7 @@
"rebass": "^4.0.7", "rebass": "^4.0.7",
"redux": "^4.1.2", "redux": "^4.1.2",
"redux-localstorage-simple": "^2.3.1", "redux-localstorage-simple": "^2.3.1",
"relay-hooks": "^7.1.0",
"setimmediate": "^1.0.5", "setimmediate": "^1.0.5",
"styled-components": "^5.3.5", "styled-components": "^5.3.5",
"tiny-invariant": "^1.2.0", "tiny-invariant": "^1.2.0",
......
...@@ -14,7 +14,7 @@ export function useDensityChartData({ ...@@ -14,7 +14,7 @@ export function useDensityChartData({
currencyB: Currency | undefined currencyB: Currency | undefined
feeAmount: FeeAmount | undefined feeAmount: FeeAmount | undefined
}) { }) {
const { isLoading, isUninitialized, isError, error, data } = usePoolActiveLiquidity(currencyA, currencyB, feeAmount) const { isLoading, error, data } = usePoolActiveLiquidity(currencyA, currencyB, feeAmount)
const formatData = useCallback(() => { const formatData = useCallback(() => {
if (!data?.length) { if (!data?.length) {
...@@ -42,10 +42,8 @@ export function useDensityChartData({ ...@@ -42,10 +42,8 @@ export function useDensityChartData({
return useMemo(() => { return useMemo(() => {
return { return {
isLoading, isLoading,
isUninitialized,
isError,
error, error,
formattedData: !isLoading && !isUninitialized ? formatData() : undefined, formattedData: !isLoading ? formatData() : undefined,
} }
}, [isLoading, isUninitialized, isError, error, formatData]) }, [isLoading, error, formatData])
} }
...@@ -96,7 +96,7 @@ export default function LiquidityChartRangeInput({ ...@@ -96,7 +96,7 @@ export default function LiquidityChartRangeInput({
const isSorted = currencyA && currencyB && currencyA?.wrapped.sortsBefore(currencyB?.wrapped) const isSorted = currencyA && currencyB && currencyA?.wrapped.sortsBefore(currencyB?.wrapped)
const { isLoading, isUninitialized, isError, error, formattedData } = useDensityChartData({ const { isLoading, error, formattedData } = useDensityChartData({
currencyA, currencyA,
currencyB, currencyB,
feeAmount, feeAmount,
...@@ -157,10 +157,12 @@ export default function LiquidityChartRangeInput({ ...@@ -157,10 +157,12 @@ export default function LiquidityChartRangeInput({
[isSorted, price, ticksAtLimit] [isSorted, price, ticksAtLimit]
) )
if (isError) { if (error) {
sendEvent('exception', { description: error.toString(), fatal: false }) sendEvent('exception', { description: error.toString(), fatal: false })
} }
const isUninitialized = !currencyA || !currencyB || (formattedData === undefined && !isLoading)
return ( return (
<AutoColumn gap="md" style={{ minHeight: '200px' }}> <AutoColumn gap="md" style={{ minHeight: '200px' }}>
{isUninitialized ? ( {isUninitialized ? (
...@@ -170,7 +172,7 @@ export default function LiquidityChartRangeInput({ ...@@ -170,7 +172,7 @@ export default function LiquidityChartRangeInput({
/> />
) : isLoading ? ( ) : isLoading ? (
<InfoBox icon={<Loader size="40px" stroke={theme.deprecated_text4} />} /> <InfoBox icon={<Loader size="40px" stroke={theme.deprecated_text4} />} />
) : isError ? ( ) : error ? (
<InfoBox <InfoBox
message={<Trans>Liquidity data not available.</Trans>} message={<Trans>Liquidity data not available.</Trans>}
icon={<CloudOff size={56} stroke={theme.deprecated_text4} />} icon={<CloudOff size={56} stroke={theme.deprecated_text4} />}
......
...@@ -44,7 +44,7 @@ export const ALL_SUPPORTED_CHAIN_IDS: SupportedChainId[] = Object.values(Support ...@@ -44,7 +44,7 @@ export const ALL_SUPPORTED_CHAIN_IDS: SupportedChainId[] = Object.values(Support
(id) => typeof id === 'number' (id) => typeof id === 'number'
) as SupportedChainId[] ) as SupportedChainId[]
export function isSupportedChain(chainId: number | undefined): chainId is SupportedChainId { export function isSupportedChain(chainId: number | null | undefined): chainId is SupportedChainId {
return !!chainId && !!SupportedChainId[chainId] return !!chainId && !!SupportedChainId[chainId]
} }
......
import graphql from 'babel-plugin-relay/macro'
import useInterval from 'lib/hooks/useInterval'
import { useCallback, useEffect, useState } from 'react'
import { fetchQuery, useRelayEnvironment } from 'relay-hooks'
import { useAppSelector } from 'state/hooks'
import type {
AllV3TicksQuery as AllV3TicksQueryType,
AllV3TicksQuery$data,
} from './__generated__/AllV3TicksQuery.graphql'
const query = graphql`
query AllV3TicksQuery($poolAddress: String!, $skip: Int!) {
ticks(first: 1000, skip: $skip, where: { poolAddress: $poolAddress }, orderBy: tickIdx) {
tick: tickIdx
liquidityNet
price0
price1
}
}
`
export type Ticks = AllV3TicksQuery$data['ticks']
export type TickData = Ticks[number]
export default function useAllV3TicksQuery(poolAddress: string | undefined, skip: number, interval: number) {
const [data, setData] = useState<AllV3TicksQuery$data | null>(null)
const [error, setError] = useState<any>(null)
const [isLoading, setIsLoading] = useState(true)
const chainId = useAppSelector((state) => state.application.chainId)
const environment = useRelayEnvironment()
const refreshData = useCallback(() => {
if (poolAddress && chainId) {
fetchQuery<AllV3TicksQueryType>(environment, query, {
poolAddress: poolAddress.toLowerCase(),
skip,
}).subscribe({
next: setData,
error: setError,
complete: () => setIsLoading(false),
})
} else {
setIsLoading(false)
}
}, [poolAddress, skip, chainId, environment])
// Trigger fetch on first load
useEffect(refreshData, [refreshData, poolAddress, skip])
useInterval(refreshData, interval, true)
return { error, isLoading, data }
}
import graphql from 'babel-plugin-relay/macro'
import useInterval from 'lib/hooks/useInterval'
import { useCallback, useEffect, useState } from 'react'
import { fetchQuery, useRelayEnvironment } from 'relay-hooks'
import { useAppSelector } from 'state/hooks'
import type {
FeeTierDistributionQuery as FeeTierDistributionQueryType,
FeeTierDistributionQuery$data,
} from './__generated__/FeeTierDistributionQuery.graphql'
const query = graphql`
query FeeTierDistributionQuery($token0: String!, $token1: String!) {
_meta {
block {
number
}
}
asToken0: pools(
orderBy: totalValueLockedToken0
orderDirection: desc
where: { token0: $token0, token1: $token1 }
) {
feeTier
totalValueLockedToken0
totalValueLockedToken1
}
asToken1: pools(
orderBy: totalValueLockedToken0
orderDirection: desc
where: { token0: $token1, token1: $token0 }
) {
feeTier
totalValueLockedToken0
totalValueLockedToken1
}
}
`
export default function useFeeTierDistributionQuery(
token0: string | undefined,
token1: string | undefined,
interval: number
) {
const [data, setData] = useState<FeeTierDistributionQuery$data | null>(null)
const [error, setError] = useState<any>(null)
const [isLoading, setIsLoading] = useState(true)
const environment = useRelayEnvironment()
const chainId = useAppSelector((state) => state.application.chainId)
const refreshData = useCallback(() => {
if (token0 && token1 && chainId) {
fetchQuery<FeeTierDistributionQueryType>(environment, query, {
token0: token0.toLowerCase(),
token1: token1.toLowerCase(),
}).subscribe({
next: setData,
error: setError,
complete: () => setIsLoading(false),
})
}
}, [token0, token1, chainId, environment])
// Trigger fetch on first load
useEffect(refreshData, [refreshData, token0, token1])
useInterval(refreshData, interval, true)
return { error, isLoading, data }
}
import { Environment, Network, RecordSource, Store } from 'relay-runtime'
import fetchGraphQL from './fetchGraphQL'
// Export a singleton instance of Relay Environment configured with our network function:
export default new Environment({
network: Network.create(fetchGraphQL),
store: new Store(new RecordSource()),
})
/**
* 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
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, Token } from '@uniswap/sdk-core' import { Currency, Token } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk' import { FeeAmount } from '@uniswap/v3-sdk'
import { sendEvent } from 'components/analytics' import { sendEvent } from 'components/analytics'
import useBlockNumber from 'lib/hooks/useBlockNumber' import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro' import ms from 'ms.macro'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useFeeTierDistributionQuery } from 'state/data/enhanced'
import { FeeTierDistributionQuery } from 'state/data/generated'
import useFeeTierDistributionQuery from '../graphql/FeeTierDistributionQuery'
import { PoolState, usePool } from './usePools' import { PoolState, usePool } from './usePools'
// maximum number of blocks past which we consider the data stale // maximum number of blocks past which we consider the data stale
...@@ -26,10 +24,7 @@ export function useFeeTierDistribution( ...@@ -26,10 +24,7 @@ export function useFeeTierDistribution(
currencyA: Currency | undefined, currencyA: Currency | undefined,
currencyB: Currency | undefined currencyB: Currency | undefined
): FeeTierDistribution { ): FeeTierDistribution {
const { isFetching, isLoading, isUninitialized, isError, distributions } = usePoolTVL( const { isLoading, error, distributions } = usePoolTVL(currencyA?.wrapped, currencyB?.wrapped)
currencyA?.wrapped,
currencyB?.wrapped
)
// fetch all pool states to determine pool state // fetch all pool states to determine pool state
const [poolStateVeryLow] = usePool(currencyA, currencyB, FeeAmount.LOWEST) const [poolStateVeryLow] = usePool(currencyA, currencyB, FeeAmount.LOWEST)
...@@ -38,10 +33,10 @@ export function useFeeTierDistribution( ...@@ -38,10 +33,10 @@ export function useFeeTierDistribution(
const [poolStateHigh] = usePool(currencyA, currencyB, FeeAmount.HIGH) const [poolStateHigh] = usePool(currencyA, currencyB, FeeAmount.HIGH)
return useMemo(() => { return useMemo(() => {
if (isLoading || isFetching || isUninitialized || isError || !distributions) { if (isLoading || error || !distributions) {
return { return {
isLoading: isLoading || isFetching || !isUninitialized, isLoading,
isError, isError: !!error,
distributions, distributions,
} }
} }
...@@ -53,7 +48,7 @@ export function useFeeTierDistribution( ...@@ -53,7 +48,7 @@ export function useFeeTierDistribution(
const percentages = const percentages =
!isLoading && !isLoading &&
!isError && !error &&
distributions && distributions &&
poolStateVeryLow !== PoolState.LOADING && poolStateVeryLow !== PoolState.LOADING &&
poolStateLow !== PoolState.LOADING && poolStateLow !== PoolState.LOADING &&
...@@ -72,42 +67,24 @@ export function useFeeTierDistribution( ...@@ -72,42 +67,24 @@ export function useFeeTierDistribution(
return { return {
isLoading, isLoading,
isError, isError: !!error,
distributions: percentages, distributions: percentages,
largestUsageFeeTier: largestUsageFeeTier === -1 ? undefined : largestUsageFeeTier, largestUsageFeeTier: largestUsageFeeTier === -1 ? undefined : largestUsageFeeTier,
} }
}, [ }, [isLoading, error, distributions, poolStateVeryLow, poolStateLow, poolStateMedium, poolStateHigh])
isLoading,
isFetching,
isUninitialized,
isError,
distributions,
poolStateVeryLow,
poolStateLow,
poolStateMedium,
poolStateHigh,
])
} }
function usePoolTVL(token0: Token | undefined, token1: Token | undefined) { function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
const latestBlock = useBlockNumber() const latestBlock = useBlockNumber()
const { isLoading, error, data } = useFeeTierDistributionQuery(token0?.address, token1?.address, ms`30s`)
const { isLoading, isFetching, isUninitialized, isError, data } = useFeeTierDistributionQuery( const { asToken0, asToken1, _meta } = data ?? {}
token0 && token1 ? { token0: token0.address.toLowerCase(), token1: token1.address.toLowerCase() } : skipToken,
{
pollingInterval: ms`30s`,
}
)
const { asToken0, asToken1, _meta } = (data as FeeTierDistributionQuery) ?? {}
return useMemo(() => { return useMemo(() => {
if (!latestBlock || !_meta || !asToken0 || !asToken1) { if (!latestBlock || !_meta || !asToken0 || !asToken1) {
return { return {
isLoading, isLoading,
isFetching, error,
isUninitialized,
isError,
} }
} }
...@@ -116,9 +93,7 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) { ...@@ -116,9 +93,7 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
return { return {
isLoading, isLoading,
isFetching, error,
isUninitialized,
isError,
} }
} }
...@@ -177,10 +152,8 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) { ...@@ -177,10 +152,8 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
return { return {
isLoading, isLoading,
isFetching, error,
isUninitialized,
isError,
distributions, distributions,
} }
}, [_meta, asToken0, asToken1, isLoading, isError, isFetching, isUninitialized, latestBlock]) }, [_meta, asToken0, asToken1, isLoading, error, latestBlock])
} }
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { FeeAmount, nearestUsableTick, Pool, TICK_SPACINGS, tickToPrice } from '@uniswap/v3-sdk' import { FeeAmount, nearestUsableTick, Pool, TICK_SPACINGS, tickToPrice } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { ZERO_ADDRESS } from 'constants/misc' import { ZERO_ADDRESS } from 'constants/misc'
import useAllV3TicksQuery, { TickData } from 'graphql/AllV3TicksQuery'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { useSingleContractMultipleData } from 'lib/hooks/multicall' import { useSingleContractMultipleData } from 'lib/hooks/multicall'
import ms from 'ms.macro' import ms from 'ms.macro'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useAllV3TicksQuery } from 'state/data/enhanced'
import computeSurroundingTicks from 'utils/computeSurroundingTicks' import computeSurroundingTicks from 'utils/computeSurroundingTicks'
import { V3_CORE_FACTORY_ADDRESSES } from '../constants/addresses' import { V3_CORE_FACTORY_ADDRESSES } from '../constants/addresses'
...@@ -18,12 +17,6 @@ import { PoolState, usePool } from './usePools' ...@@ -18,12 +17,6 @@ import { PoolState, usePool } from './usePools'
const PRICE_FIXED_DIGITS = 8 const PRICE_FIXED_DIGITS = 8
const CHAIN_IDS_MISSING_SUBGRAPH_DATA = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY] const CHAIN_IDS_MISSING_SUBGRAPH_DATA = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY]
export interface TickData {
tick: number
liquidityNet: JSBI
liquidityGross: JSBI
}
// Tick with fields parsed to JSBIs, and active liquidity computed. // Tick with fields parsed to JSBIs, and active liquidity computed.
export interface TickProcessed { export interface TickProcessed {
tick: number tick: number
...@@ -118,7 +111,6 @@ function useTicksFromTickLens( ...@@ -118,7 +111,6 @@ function useTicksFromTickLens(
return { return {
tick: tickData.tick, tick: tickData.tick,
liquidityNet: JSBI.BigInt(tickData.liquidityNet), liquidityNet: JSBI.BigInt(tickData.liquidityNet),
liquidityGross: JSBI.BigInt(tickData.liquidityGross),
} }
}) ?? []), }) ?? []),
], ],
...@@ -162,9 +154,7 @@ function useTicksFromSubgraph( ...@@ -162,9 +154,7 @@ function useTicksFromSubgraph(
) )
: undefined : undefined
return useAllV3TicksQuery(poolAddress ? { poolAddress: poolAddress?.toLowerCase(), skip: 0 } : skipToken, { return useAllV3TicksQuery(poolAddress, 0, ms`30s`)
pollingInterval: ms`30s`,
})
} }
// Fetches all ticks for a given pool // Fetches all ticks for a given pool
...@@ -174,10 +164,8 @@ function useAllV3Ticks( ...@@ -174,10 +164,8 @@ function useAllV3Ticks(
feeAmount: FeeAmount | undefined feeAmount: FeeAmount | undefined
): { ): {
isLoading: boolean isLoading: boolean
isUninitialized: boolean
isError: boolean
error: unknown error: unknown
ticks: TickData[] | undefined ticks: readonly 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
...@@ -186,8 +174,6 @@ function useAllV3Ticks( ...@@ -186,8 +174,6 @@ function useAllV3Ticks(
return { return {
isLoading: useSubgraph ? subgraphTickData.isLoading : tickLensTickData.isLoading, isLoading: useSubgraph ? subgraphTickData.isLoading : tickLensTickData.isLoading,
isUninitialized: useSubgraph ? subgraphTickData.isUninitialized : false,
isError: useSubgraph ? subgraphTickData.isError : tickLensTickData.isError,
error: useSubgraph ? subgraphTickData.error : tickLensTickData.isError, error: useSubgraph ? subgraphTickData.error : tickLensTickData.isError,
ticks: useSubgraph ? subgraphTickData.data?.ticks : tickLensTickData.tickData, ticks: useSubgraph ? subgraphTickData.data?.ticks : tickLensTickData.tickData,
} }
...@@ -199,8 +185,6 @@ export function usePoolActiveLiquidity( ...@@ -199,8 +185,6 @@ export function usePoolActiveLiquidity(
feeAmount: FeeAmount | undefined feeAmount: FeeAmount | undefined
): { ): {
isLoading: boolean isLoading: boolean
isUninitialized: boolean
isError: boolean
error: any error: any
activeTick: number | undefined activeTick: number | undefined
data: TickProcessed[] | undefined data: TickProcessed[] | undefined
...@@ -210,7 +194,7 @@ export function usePoolActiveLiquidity( ...@@ -210,7 +194,7 @@ export function usePoolActiveLiquidity(
// Find nearest valid tick for pool in case tick is not initialized. // Find nearest valid tick for pool in case tick is not initialized.
const activeTick = useMemo(() => getActiveTick(pool[1]?.tickCurrent, feeAmount), [pool, feeAmount]) const activeTick = useMemo(() => getActiveTick(pool[1]?.tickCurrent, feeAmount), [pool, feeAmount])
const { isLoading, isUninitialized, isError, error, ticks } = useAllV3Ticks(currencyA, currencyB, feeAmount) const { isLoading, error, ticks } = useAllV3Ticks(currencyA, currencyB, feeAmount)
return useMemo(() => { return useMemo(() => {
if ( if (
...@@ -220,13 +204,10 @@ export function usePoolActiveLiquidity( ...@@ -220,13 +204,10 @@ export function usePoolActiveLiquidity(
pool[0] !== PoolState.EXISTS || pool[0] !== PoolState.EXISTS ||
!ticks || !ticks ||
ticks.length === 0 || ticks.length === 0 ||
isLoading || isLoading
isUninitialized
) { ) {
return { return {
isLoading: isLoading || pool[0] === PoolState.LOADING, isLoading: isLoading || pool[0] === PoolState.LOADING,
isUninitialized,
isError,
error, error,
activeTick, activeTick,
data: undefined, data: undefined,
...@@ -246,8 +227,6 @@ export function usePoolActiveLiquidity( ...@@ -246,8 +227,6 @@ export function usePoolActiveLiquidity(
console.error('TickData pivot not found') console.error('TickData pivot not found')
return { return {
isLoading, isLoading,
isUninitialized,
isError,
error, error,
activeTick, activeTick,
data: undefined, data: undefined,
...@@ -269,11 +248,9 @@ export function usePoolActiveLiquidity( ...@@ -269,11 +248,9 @@ export function usePoolActiveLiquidity(
return { return {
isLoading, isLoading,
isUninitialized,
isError,
error, error,
activeTick, activeTick,
data: ticksProcessed, data: ticksProcessed,
} }
}, [currencyA, currencyB, activeTick, pool, ticks, isLoading, isUninitialized, isError, error]) }, [currencyA, currencyB, activeTick, pool, ticks, isLoading, error])
} }
...@@ -4,6 +4,7 @@ import 'polyfills' ...@@ -4,6 +4,7 @@ import 'polyfills'
import 'components/analytics' import 'components/analytics'
import { FeatureFlagsProvider } from 'featureFlags' import { FeatureFlagsProvider } from 'featureFlags'
import RelayEnvironment from 'graphql/RelayEnvironment'
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'
...@@ -11,6 +12,7 @@ import { createRoot } from 'react-dom/client' ...@@ -11,6 +12,7 @@ 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 { HashRouter } from 'react-router-dom' import { HashRouter } from 'react-router-dom'
import { RelayEnvironmentProvider } from 'relay-hooks'
import Blocklist from './components/Blocklist' import Blocklist from './components/Blocklist'
import Web3Provider from './components/Web3Provider' import Web3Provider from './components/Web3Provider'
...@@ -56,15 +58,17 @@ createRoot(container).render( ...@@ -56,15 +58,17 @@ createRoot(container).render(
<HashRouter> <HashRouter>
<LanguageProvider> <LanguageProvider>
<Web3Provider> <Web3Provider>
<Blocklist> <RelayEnvironmentProvider environment={RelayEnvironment}>
<BlockNumberProvider> <Blocklist>
<Updaters /> <BlockNumberProvider>
<ThemeProvider> <Updaters />
<ThemedGlobalStyle /> <ThemeProvider>
<App /> <ThemedGlobalStyle />
</ThemeProvider> <App />
</BlockNumberProvider> </ThemeProvider>
</Blocklist> </BlockNumberProvider>
</Blocklist>
</RelayEnvironmentProvider>
</Web3Provider> </Web3Provider>
</LanguageProvider> </LanguageProvider>
</HashRouter> </HashRouter>
......
...@@ -31,3 +31,7 @@ declare module 'multihashes' { ...@@ -31,3 +31,7 @@ declare module 'multihashes' {
declare module 'd3-curve-circlecorners' { declare module 'd3-curve-circlecorners' {
declare function radius(r: number): d3.CurveFactory declare function radius(r: number): d3.CurveFactory
} }
declare module 'babel-plugin-relay/macro' {
export { graphql as default } from 'react-relay'
}
...@@ -2,25 +2,11 @@ import { useWeb3React } from '@web3-react/core' ...@@ -2,25 +2,11 @@ import { useWeb3React } from '@web3-react/core'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import useIsWindowVisible from 'hooks/useIsWindowVisible' import useIsWindowVisible from 'hooks/useIsWindowVisible'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { api, CHAIN_TAG } from 'state/data/enhanced' import { useAppDispatch } from 'state/hooks'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { supportedChainId } from 'utils/supportedChainId' import { supportedChainId } from 'utils/supportedChainId'
import { updateChainId } from './reducer' import { updateChainId } from './reducer'
function useQueryCacheInvalidator() {
const dispatch = useAppDispatch()
// subscribe to `chainId` changes in the redux store rather than Web3
// this will ensure that when `invalidateTags` is called, the latest
// `chainId` is available in redux to build the subgraph url
const chainId = useAppSelector((state) => state.application.chainId)
useEffect(() => {
dispatch(api.util.invalidateTags([CHAIN_TAG]))
}, [chainId, dispatch])
}
export default function Updater(): null { export default function Updater(): null {
const { chainId, provider } = useWeb3React() const { chainId, provider } = useWeb3React()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
...@@ -28,8 +14,6 @@ export default function Updater(): null { ...@@ -28,8 +14,6 @@ export default function Updater(): null {
const [activeChainId, setActiveChainId] = useState(chainId) const [activeChainId, setActiveChainId] = useState(chainId)
useQueryCacheInvalidator()
useEffect(() => { useEffect(() => {
if (provider && chainId && windowVisible) { if (provider && chainId && windowVisible) {
setActiveChainId(chainId) setActiveChainId(chainId)
......
import { api as generatedApi } from './generated'
// tag that should be applied to queries that need to be invalidated when the chain changes
export const CHAIN_TAG = 'Chain'
// enhanced api to provide/invalidate tags
export const api = generatedApi.enhanceEndpoints({
addTagTypes: [CHAIN_TAG],
endpoints: {
allV3Ticks: {
providesTags: [CHAIN_TAG],
},
feeTierDistribution: {
providesTags: [CHAIN_TAG],
},
},
})
export const { useAllV3TicksQuery, useFeeTierDistributionQuery } = api
import { BaseQueryFn } from '@reduxjs/toolkit/query'
import { createApi } from '@reduxjs/toolkit/query/react'
import { SupportedChainId } from 'constants/chains'
import { DocumentNode } from 'graphql'
import { ClientError, gql, GraphQLClient } from 'graphql-request'
import { AppState } from 'state'
// List of supported subgraphs. Note that the app currently only support one active subgraph at a time
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',
}
export const api = createApi({
reducerPath: 'dataApi',
baseQuery: graphqlRequestBaseQuery(),
endpoints: (builder) => ({
allV3Ticks: builder.query({
query: ({ poolAddress, skip = 0 }) => ({
document: gql`
query allV3Ticks($poolAddress: String!, $skip: Int!) {
ticks(first: 1000, skip: $skip, where: { poolAddress: $poolAddress }, orderBy: tickIdx) {
tick: tickIdx
liquidityNet
price0
price1
}
}
`,
variables: {
poolAddress,
skip,
},
}),
}),
feeTierDistribution: builder.query({
query: ({ token0, token1 }) => ({
document: gql`
query feeTierDistribution($token0: String!, $token1: String!) {
_meta {
block {
number
}
}
asToken0: pools(
orderBy: totalValueLockedToken0
orderDirection: desc
where: { token0: $token0, token1: $token1 }
) {
feeTier
totalValueLockedToken0
totalValueLockedToken1
}
asToken1: pools(
orderBy: totalValueLockedToken0
orderDirection: desc
where: { token0: $token1, token1: $token0 }
) {
feeTier
totalValueLockedToken0
totalValueLockedToken1
}
}
`,
variables: {
token0,
token1,
},
}),
}),
}),
})
// Graphql query client wrapper that builds a dynamic url based on chain id
function graphqlRequestBaseQuery(): BaseQueryFn<
{ document: string | DocumentNode; variables?: any },
unknown,
Pick<ClientError, 'name' | 'message' | 'stack'>,
Partial<Pick<ClientError, 'request' | 'response'>>
> {
return async ({ document, variables }, { getState }) => {
try {
const chainId = (getState() as AppState).application.chainId
const subgraphUrl = chainId ? CHAIN_SUBGRAPH_URL[chainId] : undefined
if (!subgraphUrl) {
return {
error: {
name: 'UnsupportedChainId',
message: `Subgraph queries against ChainId ${chainId} are not supported.`,
stack: '',
},
}
}
return { data: await new GraphQLClient(subgraphUrl).request(document, variables), meta: {} }
} catch (error) {
if (error instanceof ClientError) {
const { name, message, stack, request, response } = error
return { error: { name, message, stack }, meta: { request, response } }
}
throw error
}
}
}
...@@ -8,7 +8,6 @@ import application from './application/reducer' ...@@ -8,7 +8,6 @@ import application from './application/reducer'
import burn from './burn/reducer' import burn from './burn/reducer'
import burnV3 from './burn/v3/reducer' import burnV3 from './burn/v3/reducer'
import connection from './connection/reducer' import connection from './connection/reducer'
import { api as dataApi } from './data/slice'
import { updateVersion } from './global/actions' import { updateVersion } from './global/actions'
import lists from './lists/reducer' import lists from './lists/reducer'
import logs from './logs/slice' import logs from './logs/slice'
...@@ -37,12 +36,10 @@ const store = configureStore({ ...@@ -37,12 +36,10 @@ const store = configureStore({
multicall: multicall.reducer, multicall: multicall.reducer,
lists, lists,
logs, logs,
[dataApi.reducerPath]: dataApi.reducer,
[routingApi.reducerPath]: routingApi.reducer, [routingApi.reducerPath]: routingApi.reducer,
}, },
middleware: (getDefaultMiddleware) => middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ thunk: true }) getDefaultMiddleware({ thunk: true })
.concat(dataApi.middleware)
.concat(routingApi.middleware) .concat(routingApi.middleware)
.concat(save({ states: PERSISTED_KEYS, debounce: 1000 })), .concat(save({ states: PERSISTED_KEYS, debounce: 1000 })),
preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: isTestEnv() }), preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: isTestEnv() }),
......
import { Token } from '@uniswap/sdk-core' import { Token } from '@uniswap/sdk-core'
import { tickToPrice } from '@uniswap/v3-sdk' import { tickToPrice } from '@uniswap/v3-sdk'
import { TickData, TickProcessed } from 'hooks/usePoolTickData' import { TickProcessed } from 'hooks/usePoolTickData'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { Ticks } from '../graphql/AllV3TicksQuery'
const PRICE_FIXED_DIGITS = 8 const PRICE_FIXED_DIGITS = 8
// Computes the numSurroundingTicks above or below the active tick. // Computes the numSurroundingTicks above or below the active tick.
...@@ -10,7 +12,7 @@ export default function computeSurroundingTicks( ...@@ -10,7 +12,7 @@ export default function computeSurroundingTicks(
token0: Token, token0: Token,
token1: Token, token1: Token,
activeTickProcessed: TickProcessed, activeTickProcessed: TickProcessed,
sortedTickData: TickData[], sortedTickData: Ticks,
pivot: number, pivot: number,
ascending: boolean ascending: boolean
): TickProcessed[] { ): TickProcessed[] {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment