ci(release): publish latest release

parent 3582f6e3
IPFS hash of the deployment:
- CIDv0: `QmNryPgeKysHA94RrWHP8fND99CthjcCSUs93ejSbEMdC8`
- CIDv1: `bafybeiahysggu6xwtpveh7ux7hvrc34exseqwgidnl7lob4eogww3m56au`
- CIDv0: `QmPSP5cRUzNoTLLmK32mHARzj3nuNRvytqWsdDetCNMgDM`
- CIDv1: `bafybeiaqkn77amn3y4aj3h2sw2epgtsgothkkowwdpm6jxogowhmbxpv3a`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
......@@ -10,15 +10,15 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeiahysggu6xwtpveh7ux7hvrc34exseqwgidnl7lob4eogww3m56au.ipfs.dweb.link/
- https://bafybeiahysggu6xwtpveh7ux7hvrc34exseqwgidnl7lob4eogww3m56au.ipfs.cf-ipfs.com/
- [ipfs://QmNryPgeKysHA94RrWHP8fND99CthjcCSUs93ejSbEMdC8/](ipfs://QmNryPgeKysHA94RrWHP8fND99CthjcCSUs93ejSbEMdC8/)
- https://bafybeiaqkn77amn3y4aj3h2sw2epgtsgothkkowwdpm6jxogowhmbxpv3a.ipfs.dweb.link/
- https://bafybeiaqkn77amn3y4aj3h2sw2epgtsgothkkowwdpm6jxogowhmbxpv3a.ipfs.cf-ipfs.com/
- [ipfs://QmPSP5cRUzNoTLLmK32mHARzj3nuNRvytqWsdDetCNMgDM/](ipfs://QmPSP5cRUzNoTLLmK32mHARzj3nuNRvytqWsdDetCNMgDM/)
### 5.31.1 (2024-06-06)
## 5.32.0 (2024-06-10)
### Bug Fixes
### Features
* **web:** patch wagmi to fix MM bug - prod (#8842) 34c7bc6
* **web:** Remove last of thegraph usage -prod (#8870) 910cb06
web/5.31.1
\ No newline at end of file
web/5.32.0
\ No newline at end of file
......@@ -17,7 +17,6 @@ ignores: [
"esbuild-register",
## GraphQL
"@graphql-codegen/*",
"get-graphql-schema",
## React Scripts and subpackages used from within it
"case-sensitive-paths-webpack-plugin",
"react-dev-utils",
......
......@@ -15,5 +15,4 @@ REACT_APP_SENTRY_TRACES_SAMPLE_RATE=0.003
REACT_APP_STATSIG_PROXY_URL="https://interface.gateway.uniswap.org/v1/statsig-proxy"
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://ultra-blue-flower.quiknode.pro/770b22d5f362c537bc8fe19b034c45b22958f880"
REACT_APP_QUICKNODE_ARBITRUM_RPC_URL="https://tiniest-stylish-arrow.arbitrum-mainnet.quiknode.pro/d06833352b8de605914d9e24a390d8b4d3aff7ba"
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3?source=uniswap"
REACT_APP_IS_UNISWAP_INTERFACE=true
overrideExisting: true
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3?source=uniswap'
generates:
./src/graphql/thegraph/schema/schema.graphql:
plugins:
- schema-ast
import { CyHttpMessages } from 'cypress/types/net-stubbing'
import { getTestSelector, resetHardhatChain } from '../utils'
import { aliasQuery, hasQuery } from '../utils/graphql-test-utils'
describe('Add Liquidity', () => {
beforeEach(() => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3?source=uniswap', (req) => {
aliasQuery(req, 'feeTierDistribution')
})
})
it('loads the token pair', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH/500')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
......@@ -44,33 +35,11 @@ describe('Add Liquidity', () => {
})
it('loads fee tier distribution', () => {
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
cy.intercept(
'POST',
'/subgraphs/name/uniswap/uniswap-v3?source=uniswap',
(req: CyHttpMessages.IncomingHttpRequest) => {
if (hasQuery(req, 'FeeTierDistribution')) {
req.alias = 'FeeTierDistribution'
req.reply({
body: {
data: {
...feeTierDistribution,
},
},
headers: {
'access-control-allow-origin': '*',
},
})
}
}
)
cy.interceptGraphqlOperation('FeeTierDistribution', 'feeTierDistribution.json')
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH')
cy.wait('@FeeTierDistribution')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.30% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40% select')
})
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.30% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '77% select')
})
it('disables increment and decrement until initial prices are inputted', () => {
......
{
"_meta": {
"block": {
"number": 99999999
}
},
"asToken0": [
{
"feeTier": "100",
"totalValueLockedToken0": "0",
"totalValueLockedToken1": "3"
},
{
"feeTier": "500",
"totalValueLockedToken0": "0",
"totalValueLockedToken1": "1"
},
{
"feeTier": "3000",
"totalValueLockedToken0": "0",
"totalValueLockedToken1": "4"
},
{
"feeTier": "10000",
"totalValueLockedToken0": "0",
"totalValueLockedToken1": "2"
}
],
"asToken1": []
"data": {
"v3PoolsForTokenPair": [
{
"feeTier": 3000.0,
"token0Supply": 3084.78636993,
"token1Supply": 76658.86823272712,
"__typename": "V3Pool"
},
{
"feeTier": 500.0,
"token0Supply": 917.54087138,
"token1Supply": 22788.333363067817,
"__typename": "V3Pool"
},
{
"feeTier": 10000.0,
"token0Supply": 14.67505995,
"token1Supply": 276.9691974507059,
"__typename": "V3Pool"
},
{
"feeTier": 100.0,
"token0Supply": 0.04782301,
"token1Supply": 1.677630863841559,
"__typename": "V3Pool"
}
]
}
}
// Utility to match GraphQL mutation based on the query name
export const hasQuery = (req: any, queryName: string) => {
const { body } = req
return Object.prototype.hasOwnProperty.call(body, 'query') && body.query.includes(queryName)
}
// Alias query if queryName matches
export const aliasQuery = (req: any, queryName: string) => {
if (hasQuery(req, queryName)) {
req.alias = `${queryName}Query`
}
}
/* eslint-env node */
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/thegraph/**', '!./src/graphql/thegraph/__generated__/**'],
generates: {
'src/graphql/thegraph/__generated__/types-and-hooks.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-apollo'],
config: {
withHooks: true,
// This avoid all generated schemas being wrapped in Maybe https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#maybevalue-string-default-value-t--null
maybeValue: 'T',
},
},
},
}
// This is used in package.json when generating apollo schemas however the linter stills flags this as unused
// eslint-disable-next-line import/no-unused-modules
export default config
/* eslint-env node */
module.exports = {
src: './src',
language: 'typescript',
schema: './src/graphql/thegraph/schema.graphql',
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**', '**/data/**'],
}
......@@ -7,10 +7,6 @@
"ajv": "node scripts/compile-ajv-validators.js",
"check:deps:usage": "depcheck",
"check:circular": "concurrently \"../../scripts/check-circular-imports.sh ./src/pages/App.tsx 6\" \"../../scripts/check-circular-imports.sh ./src/setupTests.ts 0\"",
"graphql:schema": "node scripts/fetch-schema.js",
"graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts",
"graphql:generate": "yarn graphql:generate:thegraph",
"graphql": "yarn graphql:schema && yarn graphql:generate",
"sitemap:generate": "node scripts/generate-sitemap.js",
"i18n:upload": "./scripts/crowdin.sh upload",
"i18n:download": "./scripts/crowdin.sh download",
......@@ -230,7 +226,6 @@
"fancy-canvas": "2.1.0",
"focus-visible": "5.2.0",
"framer-motion": "10.17.6",
"get-graphql-schema": "^2.1.2",
"graphql": "16.6.0",
"i18next": "23.10.0",
"i18next-resources-to-backend": "^1.2.0",
......
......@@ -50,8 +50,6 @@
"https://api.avax.network/ext/bc/C/rpc",
"https://api.moonpay.com/",
"https://api.opensea.io",
"https://api.studio.thegraph.com/",
"https://api.thegraph.com/",
"https://bsc-dataseed1.binance.org/",
"https://bsc-dataseed1.bnbchain.org",
"https://buy.moonpay.com/",
......
/* eslint-env node */
require('dotenv').config({ path: '.env.production' })
const child_process = require('child_process')
const fs = require('fs/promises')
const { promisify } = require('util')
const thegraphConfig = require('../graphql.thegraph.config')
const exec = promisify(child_process.exec)
function fetchSchema(url, outputFile) {
exec(`npx --silent get-graphql-schema -h Origin=https://app.uniswap.org ${url}`)
.then(({ stderr, stdout }) => {
if (stderr) {
throw new Error(stderr)
} else {
fs.writeFile(outputFile, stdout)
}
})
.catch((err) => {
console.error(err)
console.error(`Failed to fetch schema from ${url}`)
})
}
fetchSchema(process.env.THE_GRAPH_SCHEMA_ENDPOINT, thegraphConfig.schema)
......@@ -22,7 +22,7 @@ export const useMenuContent = (): MenuSection[] => {
items: [
{ label: t('Pool'), href: '/pool', internal: true, overflow: true },
{ label: t('Vote'), href: 'https://vote.uniswapfoundation.org/' },
{ label: t('Analytics'), href: 'https://info.uniswap.org/' },
{ label: t('Analytics'), href: '/explore', internal: true },
],
},
{
......
......@@ -11,8 +11,7 @@ import {
PoolTableTransactionType,
usePoolTransactions,
} from 'graphql/data/pools/usePoolTransactions'
import { getSupportedGraphQlChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { OrderDirection, Transaction_OrderBy } from 'graphql/thegraph/__generated__/types-and-hooks'
import { OrderDirection, getSupportedGraphQlChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency'
import { Trans } from 'i18n'
import { useMemo, useReducer, useState } from 'react'
......@@ -32,11 +31,6 @@ const TableWrapper = styled.div`
min-height: 256px;
`
type PoolTxTableSortState = {
sortBy: Transaction_OrderBy
sortDirection: OrderDirection
}
enum PoolTransactionColumn {
Timestamp,
Type,
......@@ -85,10 +79,6 @@ export function PoolDetailsTransactionsTable({
PoolTableTransactionType.MINT,
])
const [sortState] = useState<PoolTxTableSortState>({
sortBy: Transaction_OrderBy.Timestamp,
sortDirection: OrderDirection.Desc,
})
const { transactions, loading, loadMore, error } = usePoolTransactions(
poolAddress,
chain.id,
......@@ -106,8 +96,8 @@ export function PoolDetailsTransactionsTable({
header: () => (
<Cell minWidth={PoolTransactionColumnWidth[PoolTransactionColumn.Timestamp]} justifyContent="flex-start">
<Row gap="4px">
{sortState.sortBy === Transaction_OrderBy.Timestamp && <HeaderArrow direction={OrderDirection.Desc} />}
<HeaderSortText $active={sortState.sortBy === Transaction_OrderBy.Timestamp}>
<HeaderArrow direction={OrderDirection.Desc} />
<HeaderSortText $active>
<Trans i18nKey="common.time" />
</HeaderSortText>
</Row>
......@@ -288,7 +278,6 @@ export function PoolDetailsTransactionsTable({
formatFiatPrice,
formatNumber,
showLoadingSkeleton,
sortState.sortBy,
token0,
token1?.symbol,
])
......
......@@ -71,10 +71,6 @@ const EXTERNAL_APIS = [
name: 'Google Analytics & Amplitude',
description: <Trans i18nKey="privacy.anonymizedLogs" />,
},
{
name: 'The Graph',
description: <Trans i18nKey="privacy.theGraph" />,
},
]
export function PrivacyPolicyModal() {
......
......@@ -8,7 +8,6 @@ import { useAbbreviatedTimeString } from 'components/Table/utils'
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { OrderDirection, getTokenDetailsURL, supportedChainIdFromGQLChain, unwrapToken } from 'graphql/data/util'
import { OrderDirection as TheGraphOrderDirection } from 'graphql/thegraph/__generated__/types-and-hooks'
import { useCurrency } from 'hooks/Tokens'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { Trans } from 'i18n'
......@@ -164,7 +163,7 @@ export const ClickableHeaderRow = styled(Row)<{ $justify?: string }>`
gap: 4px;
${ClickableStyle}
`
export const HeaderArrow = styled(ArrowDown)<{ direction: OrderDirection | TheGraphOrderDirection }>`
export const HeaderArrow = styled(ArrowDown)<{ direction: OrderDirection }>`
height: 16px;
width: 16px;
color: ${({ theme }) => theme.neutral1};
......
......@@ -6,7 +6,7 @@ import { TokenDetailsPoolsTable } from 'components/Tokens/TokenDetails/tables/To
import { usePoolsFromTokenAddress } from 'graphql/data/pools/usePoolsFromTokenAddress'
import Router from 'react-router-dom'
import { mocked } from 'test-utils/mocked'
import { validBEPoolToken0, validBEPoolToken1, validParams, validPoolToken0 } from 'test-utils/pools/fixtures'
import { validBEPoolToken0, validBEPoolToken1, validParams } from 'test-utils/pools/fixtures'
import { render, screen } from 'test-utils/render'
import { ProtocolVersion } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
......@@ -16,7 +16,7 @@ jest.mock('react-router-dom', () => ({
useParams: jest.fn(),
}))
const mockToken = new Token(ChainId.MAINNET, validPoolToken0.id, 18)
const mockToken = new Token(ChainId.MAINNET, validBEPoolToken0.id, 18)
describe('TDPPoolTable', () => {
beforeEach(() => {
......@@ -75,7 +75,7 @@ describe('TDPPoolTable', () => {
})
const { asFragment } = render(<TokenDetailsPoolsTable chainId={ChainId.MAINNET} referenceToken={mockToken} />)
expect(screen.getByTestId(`tdp-pools-table-${validPoolToken0.id}`)).not.toBeNull()
expect(screen.getByTestId(`tdp-pools-table-${validBEPoolToken0.id}`)).not.toBeNull()
expect(asFragment()).toMatchSnapshot()
})
})
......@@ -16,8 +16,7 @@ import {
import { SupportedInterfaceChainId } from 'constants/chains'
import { useUpdateManualOutage } from 'featureFlags/flags/outageBanner'
import { TokenTransactionType, useTokenTransactions } from 'graphql/data/useTokenTransactions'
import { unwrapToken } from 'graphql/data/util'
import { OrderDirection, Swap_OrderBy } from 'graphql/thegraph/__generated__/types-and-hooks'
import { OrderDirection, unwrapToken } from 'graphql/data/util'
import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency'
import { Trans } from 'i18n'
import { useMemo, useReducer, useState } from 'react'
......@@ -53,11 +52,6 @@ interface SwapLeg {
token: GQLToken
}
type TokenTxTableSortState = {
sortBy: Swap_OrderBy
sortDirection: OrderDirection
}
export function TransactionsTable({
chainId,
referenceToken,
......@@ -69,10 +63,6 @@ export function TransactionsTable({
const { formatNumber, formatFiatPrice } = useFormatter()
const [filterModalIsOpen, toggleFilterModal] = useReducer((s) => !s, false)
const [filter, setFilters] = useState<TokenTransactionType[]>([TokenTransactionType.BUY, TokenTransactionType.SELL])
const [sortState] = useState<TokenTxTableSortState>({
sortBy: Swap_OrderBy.Timestamp,
sortDirection: OrderDirection.Desc,
})
const { transactions, loading, loadMore, errorV2, errorV3 } = useTokenTransactions(
referenceToken.address,
chainId,
......@@ -126,8 +116,8 @@ export function TransactionsTable({
header: () => (
<Cell minWidth={120} justifyContent="flex-start" grow>
<Row gap="xs">
{sortState.sortBy === Swap_OrderBy.Timestamp && <HeaderArrow direction={sortState.sortDirection} />}
<HeaderSortText $active={sortState.sortBy === Swap_OrderBy.Timestamp}>
<HeaderArrow direction={OrderDirection.Desc} />
<HeaderSortText $active>
<Trans i18nKey="common.time" />
</HeaderSortText>
</Row>
......@@ -230,10 +220,7 @@ export function TransactionsTable({
header: () => (
<Cell minWidth={125} justifyContent="flex-end">
<Row gap="xs" justify="flex-end">
{sortState.sortBy === Swap_OrderBy.AmountUsd && <HeaderArrow direction={sortState.sortDirection} />}
<HeaderSortText $active={sortState.sortBy === Swap_OrderBy.AmountUsd}>
{activeLocalCurrency}
</HeaderSortText>
<HeaderSortText>{activeLocalCurrency}</HeaderSortText>
</Row>
</Cell>
),
......@@ -262,8 +249,6 @@ export function TransactionsTable({
}),
]
}, [
sortState.sortBy,
sortState.sortDirection,
showLoadingSkeleton,
chainId,
filterModalIsOpen,
......
......@@ -972,12 +972,6 @@ export const INFURA_PREFIX_TO_CHAIN_ID: { [prefix: string]: SupportedInterfaceCh
.map(([key, value]) => [value.infuraPrefix, parseInt(key) as SupportedInterfaceChainId])
)
export const CHAIN_SUBGRAPH_URL = Object.fromEntries(
Object.entries(CHAIN_INFO)
.filter(([, value]) => !!value.subgraphUrl)
.map(([key, value]) => [parseInt(key) as SupportedInterfaceChainId, value.subgraphUrl])
) as Record<SupportedInterfaceChainId, string>
/**
* Get the priority of a chainId based on its relevance to the user.
* @param {ChainId} chainId - The chainId to determine the priority for.
......
import { AllV3TicksQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
export type Ticks = NonNullable<NonNullable<AllV3TicksQuery['v3Pool']>['ticks']>
export type TickData = Ticks[number]
import { AllV3TicksQuery } from './__generated__/types-and-hooks'
export type Ticks = AllV3TicksQuery['ticks']
export type TickData = Ticks[number]
import { ApolloError, useQuery } from '@apollo/client'
import { useMemo } from 'react'
import { FeeTierDistributionDocument, FeeTierDistributionQuery } from './__generated__/types-and-hooks'
import { apolloClient } from './apollo'
export default function useFeeTierDistributionQuery(
token0: string | undefined,
token1: string | undefined,
interval: number
): { error?: ApolloError; isLoading: boolean; data: FeeTierDistributionQuery } {
const {
data,
loading: isLoading,
error,
} = useQuery(FeeTierDistributionDocument, {
variables: {
token0: token0?.toLowerCase(),
token1: token1?.toLowerCase(),
},
pollInterval: interval,
client: apolloClient,
})
return useMemo(
() => ({
error,
isLoading,
data,
}),
[data, error, isLoading]
)
}
import { ApolloClient, ApolloLink, concat, HttpLink, InMemoryCache, NormalizedCacheObject } from '@apollo/client'
import { ChainId } from '@uniswap/sdk-core'
import { CHAIN_SUBGRAPH_URL, isSupportedChainId } from 'constants/chains'
import store from '../../state/index'
const httpLink = new HttpLink({ uri: CHAIN_SUBGRAPH_URL[ChainId.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().application.chainId
operation.setContext(() => ({
uri:
isSupportedChainId(chainId) && CHAIN_SUBGRAPH_URL[chainId]
? CHAIN_SUBGRAPH_URL[chainId]
: CHAIN_SUBGRAPH_URL[ChainId.MAINNET],
}))
return forward(operation)
})
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: concat(authMiddleware, httpLink),
})
export const chainToApolloClient: Record<number, ApolloClient<NormalizedCacheObject>> = Object.fromEntries(
Object.entries(CHAIN_SUBGRAPH_URL).map(([chainId, url]) => [
chainId,
new ApolloClient({
cache: new InMemoryCache(),
uri: url,
}),
])
)
query AllV3Ticks($poolAddress: String, $skip: Int!) {
ticks(
first: 1000
skip: $skip
where: { poolAddress: $poolAddress }
orderBy: tickIdx
) {
tick: tickIdx
liquidityNet
price0
price1
}
}
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
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
import { Currency, Token } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { chainIdToBackendChain } from 'constants/chains'
import ms from 'ms'
import { useMemo } from 'react'
import useFeeTierDistributionQuery from '../graphql/thegraph/FeeTierDistributionQuery'
import {
useFeeTierDistributionQuery,
useIsV3SubgraphStaleQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { logger } from 'utilities/src/logger/logger'
import { PoolState, usePool } from './usePools'
// maximum number of blocks past which we consider the data stale
const MAX_DATA_BLOCK_AGE = 20
interface FeeTierDistribution {
isLoading: boolean
isError: boolean
......@@ -74,34 +74,47 @@ export function useFeeTierDistribution(
}
function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
const latestBlock = useBlockNumber()
const { isLoading, error, data } = useFeeTierDistributionQuery(token0?.address, token1?.address, ms(`30s`))
const { asToken0, asToken1, _meta } = data ?? {}
const chain = chainIdToBackendChain({ chainId: token0?.chainId, withFallback: true })
const { loading, error, data } = useFeeTierDistributionQuery({
variables: {
chain,
token0: token0?.address ?? '',
token1: token1?.address ?? '',
},
pollInterval: ms(`30s`),
})
const { data: isSubgraphStaleData, error: isSubgraphStaleError } = useIsV3SubgraphStaleQuery({
variables: { chain },
pollInterval: ms(`30s`),
})
const { v3PoolsForTokenPair } = data ?? {}
return useMemo(() => {
if (!latestBlock || !_meta || !asToken0 || !asToken1) {
if (isSubgraphStaleError || !v3PoolsForTokenPair) {
return {
isLoading,
error,
isLoading: loading,
error: error ?? isSubgraphStaleError,
}
}
if (latestBlock - (_meta?.block?.number ?? 0) > MAX_DATA_BLOCK_AGE) {
console.log(`Graph stale (latest block: ${latestBlock})`)
if (isSubgraphStaleData?.isV3SubgraphStale) {
logger.info('useFeeTierDistribution', 'usePoolTVL', `Subgraph stale`)
return {
isLoading,
isLoading: loading,
error,
}
}
const all = asToken0.concat(asToken1)
// sum tvl for token0 and token1 by fee tier
const tvlByFeeTier = all.reduce<{ [feeAmount: number]: [number | undefined, number | undefined] }>(
const tvlByFeeTier = v3PoolsForTokenPair.reduce<{ [feeAmount: number]: [number | undefined, number | undefined] }>(
(acc, value) => {
acc[value.feeTier][0] = (acc[value.feeTier][0] ?? 0) + Number(value.totalValueLockedToken0)
acc[value.feeTier][1] = (acc[value.feeTier][1] ?? 0) + Number(value.totalValueLockedToken1)
if (!value.feeTier) {
return acc
}
acc[value.feeTier][0] = (acc[value.feeTier][0] ?? 0) + Number(value.token0Supply)
acc[value.feeTier][1] = (acc[value.feeTier][1] ?? 0) + Number(value.token1Supply)
return acc
},
{
......@@ -149,9 +162,9 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
}
return {
isLoading,
isLoading: loading,
error,
distributions,
}
}, [_meta, asToken0, asToken1, isLoading, error, latestBlock])
}, [isSubgraphStaleError, v3PoolsForTokenPair, isSubgraphStaleData?.isV3SubgraphStale, loading, error])
}
import { ChainId, Currency, Price, Token, V3_CORE_FACTORY_ADDRESSES } from '@uniswap/sdk-core'
import { FeeAmount, Pool, TICK_SPACINGS, tickToPrice } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
import { TickData, Ticks } from 'graphql/thegraph/AllV3TicksQuery'
import { useAllV3TicksQuery } from 'graphql/thegraph/__generated__/types-and-hooks'
import { chainIdToBackendChain, useSupportedChainId } from 'constants/chains'
import { TickData, Ticks } from 'graphql/data/AllV3TicksQuery'
import { useAccount } from 'hooks/useAccount'
import JSBI from 'jsbi'
import ms from 'ms'
import { useEffect, useMemo, useState } from 'react'
import { useAllV3TicksQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import computeSurroundingTicks from 'utils/computeSurroundingTicks'
import { chainToApolloClient } from 'graphql/thegraph/apollo'
import { PoolState, usePoolMultichain } from './usePools'
const PRICE_FIXED_DIGITS = 8
......@@ -25,6 +25,7 @@ export interface TickProcessed {
const getActiveTick = (tickCurrent: number | undefined, feeAmount: FeeAmount | undefined) =>
tickCurrent && feeAmount ? Math.floor(tickCurrent / TICK_SPACINGS[feeAmount]) * TICK_SPACINGS[feeAmount] : undefined
const MAX_TICK_FETCH_VALUE = 1000
function useTicksFromSubgraph(
currencyA: Currency | undefined,
currencyB: Currency | undefined,
......@@ -32,7 +33,6 @@ function useTicksFromSubgraph(
skip = 0,
chainId: ChainId
) {
const apolloClient = chainToApolloClient[chainId]
const poolAddress =
currencyA && currencyB && feeAmount
? Pool.getAddress(
......@@ -43,16 +43,20 @@ function useTicksFromSubgraph(
chainId ? V3_CORE_FACTORY_ADDRESSES[chainId] : undefined
)
: undefined
const supportedChainId = useSupportedChainId(chainId)
return useAllV3TicksQuery({
variables: { poolAddress: poolAddress?.toLowerCase(), skip },
variables: {
address: poolAddress?.toLowerCase() ?? '',
chain: chainIdToBackendChain({ chainId: supportedChainId, withFallback: true }),
skip,
first: MAX_TICK_FETCH_VALUE,
},
skip: !poolAddress,
pollInterval: ms(`30s`),
client: apolloClient,
})
}
const MAX_THE_GRAPH_TICK_FETCH_VALUE = 1000
// Fetches all ticks for a given pool
function useAllV3Ticks(
currencyA: Currency | undefined,
......@@ -67,18 +71,19 @@ function useAllV3Ticks(
const [skipNumber, setSkipNumber] = useState(0)
const [subgraphTickData, setSubgraphTickData] = useState<Ticks>([])
const { data, error, loading: isLoading } = useTicksFromSubgraph(currencyA, currencyB, feeAmount, skipNumber, chainId)
const ticks: Ticks = data?.v3Pool?.ticks as Ticks
useEffect(() => {
if (data?.ticks.length) {
setSubgraphTickData((tickData) => [...tickData, ...data.ticks])
if (data.ticks.length === MAX_THE_GRAPH_TICK_FETCH_VALUE) {
setSkipNumber((skipNumber) => skipNumber + MAX_THE_GRAPH_TICK_FETCH_VALUE)
if (ticks?.length) {
setSubgraphTickData((tickData) => [...tickData, ...ticks])
if (ticks?.length === MAX_TICK_FETCH_VALUE) {
setSkipNumber((skipNumber) => skipNumber + MAX_TICK_FETCH_VALUE)
}
}
}, [data?.ticks])
}, [ticks])
return {
isLoading: isLoading || data?.ticks.length === MAX_THE_GRAPH_TICK_FETCH_VALUE,
isLoading: isLoading || ticks?.length === MAX_TICK_FETCH_VALUE,
error,
ticks: subgraphTickData,
}
......@@ -98,7 +103,7 @@ export function usePoolActiveLiquidity(
sqrtPriceX96?: JSBI
data?: TickProcessed[]
} {
const defaultChainId = useWeb3React().chainId ?? ChainId.MAINNET
const defaultChainId = useAccount().chainId ?? ChainId.MAINNET
const pool = usePoolMultichain(currencyA?.wrapped, currencyB?.wrapped, feeAmount, chainId ?? defaultChainId)
const liquidity = pool[1]?.liquidity
const sqrtPriceX96 = pool[1]?.sqrtRatioX96
......@@ -133,7 +138,7 @@ export function usePoolActiveLiquidity(
// find where the active tick would be to partition the array
// if the active tick is initialized, the pivot will be an element
// if not, take the previous tick as pivot
const pivot = ticks.findIndex(({ tick }) => tick > activeTick) - 1
const pivot = ticks.findIndex((tickData) => tickData?.tick && tickData.tick > activeTick) - 1
if (pivot < 0) {
// consider setting a local error
......@@ -150,7 +155,8 @@ export function usePoolActiveLiquidity(
const activeTickProcessed: TickProcessed = {
liquidityActive: JSBI.BigInt(pool[1]?.liquidity ?? 0),
tick: activeTick,
liquidityNet: Number(ticks[pivot].tick) === activeTick ? JSBI.BigInt(ticks[pivot].liquidityNet) : JSBI.BigInt(0),
liquidityNet:
Number(ticks[pivot]?.tick) === activeTick ? JSBI.BigInt(ticks[pivot]?.liquidityNet ?? 0) : JSBI.BigInt(0),
price0: sdkPrice.toFixed(PRICE_FIXED_DIGITS),
sdkPrice,
}
......
......@@ -798,7 +798,6 @@
"privacy.anonymizedLogs": "The app logs anonymized usage statistics in order to improve over time.",
"privacy.autoRouter": "The app fetches the optimal trade route from a Uniswap Labs server.",
"privacy.infura": "The app fetches on-chain data and constructs contract calls with an Infura API.",
"privacy.theGraph": "The app fetches blockchain data from The Graph’s hosted service.",
"privacy.thirdPartyApis": "This app uses the following third-party APIs:",
"privacy.trm": "The app securely collects your wallet address and shares it with TRM Labs Inc. for risk and compliance reasons.",
"privacy.uniswaptos": "Uniswap Labs’ Terms of Service",
......
......@@ -15,8 +15,7 @@ import {
import { useChainFromUrlParam } from 'constants/chains'
import { useUpdateManualOutage } from 'featureFlags/flags/outageBanner'
import { BETypeToTransactionType, TransactionType, useAllTransactions } from 'graphql/data/useAllTransactions'
import { getSupportedGraphQlChain } from 'graphql/data/util'
import { OrderDirection, Transaction_OrderBy } from 'graphql/thegraph/__generated__/types-and-hooks'
import { OrderDirection, getSupportedGraphQlChain } from 'graphql/data/util'
import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency'
import { Trans } from 'i18n'
import { useMemo, useReducer, useState } from 'react'
......@@ -29,11 +28,6 @@ import { shortenAddress } from 'utilities/src/addresses'
import { useFormatter } from 'utils/formatNumbers'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
type ExploreTxTableSortState = {
sortBy: Transaction_OrderBy
sortDirection: OrderDirection
}
export default function RecentTransactions() {
const activeLocalCurrency = useActiveLocalCurrency()
const { formatNumber, formatFiatPrice } = useFormatter()
......@@ -45,10 +39,6 @@ export default function RecentTransactions() {
])
const chain = getSupportedGraphQlChain(useChainFromUrlParam(), { fallbackToEthereum: true })
const [sortState] = useState<ExploreTxTableSortState>({
sortBy: Transaction_OrderBy.Timestamp,
sortDirection: OrderDirection.Desc,
})
const { transactions, loading, loadMore, errorV2, errorV3 } = useAllTransactions(chain.backendChain.chain, filter)
const combinedError =
errorV2 && errorV3
......@@ -66,10 +56,8 @@ export default function RecentTransactions() {
header: () => (
<Cell minWidth={120} justifyContent="flex-start" grow>
<Row gap="4px">
{sortState.sortBy === Transaction_OrderBy.Timestamp && (
<HeaderArrow direction={sortState.sortDirection} />
)}
<HeaderSortText $active={sortState.sortBy === Transaction_OrderBy.Timestamp}>
<HeaderArrow direction={OrderDirection.Desc} />
<HeaderSortText $active>
<Trans i18nKey="common.time" />
</HeaderSortText>
</Row>
......@@ -197,17 +185,7 @@ export default function RecentTransactions() {
),
}),
]
}, [
activeLocalCurrency,
chain.id,
filter,
filterModalIsOpen,
formatFiatPrice,
formatNumber,
showLoadingSkeleton,
sortState.sortBy,
sortState.sortDirection,
])
}, [activeLocalCurrency, chain.id, filter, filterModalIsOpen, formatFiatPrice, formatNumber, showLoadingSkeleton])
return (
<Table
......
......@@ -144,7 +144,7 @@ const LearnMoreArrow = styled(ArrowRightCircle)`
const ProtocolDescription = () => <Trans i18nKey="landing.protocolDescription" />
function LearnMore() {
return (
<LearnMoreButton href="https://info.uniswap.org">
<LearnMoreButton href="/explore">
<Row gap="sm" align="center">
<Trans i18nKey="common.learnMore.link" />
<LearnMoreArrow />
......
......@@ -95,7 +95,7 @@ describe('PoolDetailsPage', () => {
})
})
it('not found page displayed when no data is received from thegraph', () => {
it('not found page displayed when no data is received from backend', () => {
mocked(usePoolData).mockReturnValue({
data: undefined,
loading: false,
......
......@@ -3,22 +3,17 @@ import { ChainId, Currency, WETH9 } from '@uniswap/sdk-core'
import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk'
import { USDC_MAINNET } from 'constants/tokens'
import { PoolData } from 'graphql/data/pools/usePoolData'
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
import { Token as BEToken } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { Token } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
export const validParams = { poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', chainName: 'ethereum' }
export const validPoolToken0 = {
export const validBEPoolToken0 = {
id: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
name: 'USD Coin',
decimals: '6',
derivedETH: '0.0006240873011635544626425964678706127',
__typename: 'Token',
} as Token
export const validBEPoolToken0 = {
...validPoolToken0,
address: validPoolToken0.id,
chain: 'ETHEREUM',
decimals: 6,
project: {
......@@ -29,7 +24,7 @@ export const validBEPoolToken0 = {
url: 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
},
},
} as BEToken
} as Token
export const validUSDCCurrency = {
isNative: false,
......@@ -43,20 +38,16 @@ export const validUSDCCurrency = {
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
_checksummedAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
_tags: null,
wrapped: validPoolToken0,
wrapped: validBEPoolToken0,
} as unknown as Currency
const validPoolToken1 = {
id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
export const validBEPoolToken1 = {
symbol: 'WETH',
name: 'Wrapped Ether',
decimals: '18',
derivedETH: '1',
__typename: 'Token',
} as Token
export const validBEPoolToken1 = {
...validPoolToken1,
address: validPoolToken1.id,
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
chain: 'ETHEREUM',
decimals: 18,
project: {
......@@ -67,7 +58,7 @@ export const validBEPoolToken1 = {
url: 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
},
},
} as BEToken
} as Token
export const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c'
......
import { Price, Token } from '@uniswap/sdk-core'
import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { TickData } from 'graphql/thegraph/AllV3TicksQuery'
import { TickData } from 'graphql/data/AllV3TicksQuery'
import { TickProcessed } from 'hooks/usePoolTickData'
import JSBI from 'jsbi'
......@@ -8,7 +8,7 @@ import computeSurroundingTicks from './computeSurroundingTicks'
const getV3Tick = (tick: number, liquidityNet: number): TickData => ({
tick,
liquidityNet: JSBI.BigInt(liquidityNet),
liquidityNet: JSBI.BigInt(liquidityNet).toString(),
price0: undefined,
price1: undefined,
})
......
......@@ -3,7 +3,7 @@ import { tickToPrice } from '@uniswap/v3-sdk'
import { TickProcessed } from 'hooks/usePoolTickData'
import JSBI from 'jsbi'
import { Ticks } from '../graphql/thegraph/AllV3TicksQuery'
import { Ticks } from '../graphql/data/AllV3TicksQuery'
const PRICE_FIXED_DIGITS = 8
......@@ -23,12 +23,12 @@ export default function computeSurroundingTicks(
// building active liquidity for every tick.
let processedTicks: TickProcessed[] = []
for (let i = pivot + (ascending ? 1 : -1); ascending ? i < sortedTickData.length : i >= 0; ascending ? i++ : i--) {
const tick = Number(sortedTickData[i].tick)
const tick = Number(sortedTickData[i]?.tick)
const sdkPrice = tickToPrice(token0, token1, tick)
const currentTickProcessed: TickProcessed = {
liquidityActive: previousTickProcessed.liquidityActive,
tick,
liquidityNet: JSBI.BigInt(sortedTickData[i].liquidityNet),
liquidityNet: JSBI.BigInt(sortedTickData[i]?.liquidityNet ?? ''),
price0: sdkPrice.toFixed(PRICE_FIXED_DIGITS),
sdkPrice,
}
......@@ -40,7 +40,7 @@ export default function computeSurroundingTicks(
if (ascending) {
currentTickProcessed.liquidityActive = JSBI.add(
previousTickProcessed.liquidityActive,
JSBI.BigInt(sortedTickData[i].liquidityNet)
JSBI.BigInt(sortedTickData[i]?.liquidityNet ?? 0)
)
} else if (!ascending && JSBI.notEqual(previousTickProcessed.liquidityNet, JSBI.BigInt(0))) {
// We are iterating descending, so look at the previous tick and apply any net liquidity.
......
......@@ -11,7 +11,6 @@ ignores: [
"tsconfig",
## GraphQL
"@graphql-codegen/*",
"get-graphql-schema",
# i18n
"i18next-resources-for-ts",
]
......@@ -880,12 +880,14 @@ type PushNotification {
}
type Query {
isV3SubgraphStale(chain: Chain!): Boolean
historicalProtocolVolume(chain: Chain!, version: ProtocolVersion!, duration: HistoryDuration!): [TimestampedAmount!]
dailyProtocolTvl(chain: Chain!, version: ProtocolVersion!): [TimestampedAmount!]
""" returns top v3 pools sorted by total value locked in desc order"""
topV3Pools(chain: Chain!, first: Int!, tvlCursor: Float, tokenFilter: String): [V3Pool!]
v3Pool(chain: Chain!, address: String!): V3Pool
v3PoolsForTokenPair(chain: Chain!, token0: String!, token1: String!): [V3Pool!]
v3Transactions(chain: Chain!, first: Int!, timestampCursor: Int): [PoolTransaction!]
""" returns top v2 pairs sorted by total value locked in desc order"""
......@@ -1296,7 +1298,7 @@ type V3Pool implements IPool {
historicalVolume(duration: HistoryDuration!): [TimestampedAmount]
priceHistory(duration: HistoryDuration!): [TimestampedPoolPrice]
transactions(first: Int!, timestampCursor: Int): [PoolTransaction]
ticks(first: Int!, tickIdxCursor: Int): [V3PoolTick]
ticks(skip: Int, first: Int): [V3PoolTick]
}
type V3PoolTick {
......@@ -1304,5 +1306,7 @@ type V3PoolTick {
tickIdx: Int
liquidityGross: String
liquidityNet: String
price0: String
price1: String
}
query AllV3Ticks ($chain: Chain!, $address: String!, $skip: Int, $first: Int){
v3Pool(chain: $chain, address: $address) {
ticks(skip: $skip, first: $first) {
tick: tickIdx
liquidityNet
price0
price1
}
}
}
query FeeTierDistribution($chain: Chain!, $token0: String!, $token1: String!) {
v3PoolsForTokenPair(chain: $chain, token0: $token0, token1: $token1) {
feeTier
token0Supply
token1Supply
}
}
query isV3SubgraphStale($chain: Chain!) {
isV3SubgraphStale(chain: $chain)
}
......@@ -72,18 +72,18 @@ export function queryResolvers<T extends QueryResolvers>(
): Promise<ResolverReturnType<R>> => {
const [parent, args, context, info] = params
const resolvedObj = isResolverWithResolve(resolver)
const resolvedValue = isResolverWithResolve(resolver)
? resolver.resolve(parent, args, context, info)
: isResolverFunction(resolver)
? resolver(parent, args, context, info)
: null
const filteredObj = await filterObjectFields(
const updatedValue = await filterObjectFields(
info.fieldNodes[0]?.selectionSet,
resolvedObj ?? null
resolvedValue
)
// cloneDeepWith returns any type so we need to cast it manually
const resultObj = cloneDeepWith(filteredObj, undefinedToNull) as ResolverReturnType<R>
const resultObj = cloneDeepWith(updatedValue, undefinedToNull) as ResolverReturnType<R>
// Resolve the corresponding promise
if (promiseResolvers[key]) {
......@@ -100,12 +100,18 @@ export function queryResolvers<T extends QueryResolvers>(
}
}
async function filterObjectFields<T extends object>(
type Scalar = number | string | boolean | bigint | symbol | undefined
function isObject<T extends object | Scalar>(value: T): value is Exclude<T, Scalar> {
return typeof value === 'object'
}
async function filterObjectFields<T extends object | Scalar>(
selectionSet: SelectionSetNode | undefined,
sourceObject: T | Promise<Maybe<ResolverTypeWrapper<T>>> | null
sourceValue: T | Promise<Maybe<ResolverTypeWrapper<T>>> | null
): Promise<T | null> {
// resolved source object can be a Promise or a regular object
const source = await sourceObject
// resolved source value can be a Promise or a plain value
const source = await sourceValue
if (!source || !selectionSet) {
return source ?? null
......@@ -115,12 +121,17 @@ async function filterObjectFields<T extends object>(
return Promise.all(source.map((obj) => filterObjectFields(selectionSet, obj))) as T
}
if (!isObject(source)) {
return source
}
const result: Record<string, any> = {}
for (const selection of selectionSet.selections) {
if (selection.kind !== 'Field') {
continue
}
const key = selection.name.value
const value = source[key as keyof typeof source]
......
......@@ -72,12 +72,10 @@
},
"graphql:schema": {
"env": [
"THE_GRAPH_SCHEMA_ENDPOINT",
"REACT_APP_AWS_API_ENDPOINT",
"scripts/**"
],
"inputs": [
"scripts/fetch-schema.js",
".env.production",
"graphql.*.ts"
],
......@@ -90,7 +88,6 @@
"inputs": [
"codegen.ts",
"src/**/schema.graphql",
"scripts/fetch-schema.js",
"src/graphql/**",
"src/data/**/*.graphql"
],
......
......@@ -13635,7 +13635,6 @@ __metadata:
fancy-canvas: 2.1.0
focus-visible: 5.2.0
framer-motion: 10.17.6
get-graphql-schema: ^2.1.2
graphql: 16.6.0
hardhat: 2.14.0
husky: ^8.0.3
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