diff --git a/apps/evm/app/(landing)/api/stats/route.ts b/apps/evm/app/(landing)/api/stats/route.ts index 70991d41e3..fadf016f6b 100644 --- a/apps/evm/app/(landing)/api/stats/route.ts +++ b/apps/evm/app/(landing)/api/stats/route.ts @@ -1,30 +1,110 @@ +import { getAddress } from '@ethersproject/address' import { ChainId } from '@sushiswap/chain' import { SUSHI_ADDRESS } from '@sushiswap/currency' import { formatNumber, formatUSD } from '@sushiswap/format' -import getBentoTVL from 'functions/graph/fetchers/bentobox' -import { getLegacyExchangeData } from 'functions/graph/fetchers/exchange' -import { getTridentExchangeData } from 'functions/graph/queries/trident' +import { getBuiltGraphSDK } from '@sushiswap/graph-client' +import { BENTOBOX_ENABLED_NETWORKS } from '@sushiswap/graph-config' +import { SUSHISWAP_V2_SUPPORTED_CHAIN_IDS } from '@sushiswap/v2-sdk' +import { SUSHISWAP_V3_SUPPORTED_CHAIN_IDS } from '@sushiswap/v3-sdk' import { NextResponse } from 'next/server' const getSushiPriceUSD = async () => { - { - const prices = await fetch('https://token-price.sushi.com/v1/1').then((data) => data.json()) - return prices[SUSHI_ADDRESS[ChainId.ETHEREUM].toLowerCase()] + const prices = await fetch('https://token-price.sushi.com/v1/1').then((data) => data.json()) + return prices[SUSHI_ADDRESS[ChainId.ETHEREUM].toLowerCase()] +} + +interface ExchangeData { + tvlUSD: number + volumeUSD: number + pairCount: number +} + +const getV2Data = async () => { + const sdk = getBuiltGraphSDK() + const { factories } = await sdk.Factories({ chainIds: SUSHISWAP_V2_SUPPORTED_CHAIN_IDS }) + + return { + v2: factories + .filter(({ id }) => id !== 'ALL') + .reduce( + (acc, cur) => { + return { + tvlUSD: acc.tvlUSD + Number(cur.liquidityUSD), + volumeUSD: acc.volumeUSD + Number(cur.volumeUSD), + pairCount: acc.pairCount + Number(cur.pairCount), + } + }, + { + tvlUSD: 0, + volumeUSD: 0, + pairCount: 0, + } + ), + trident: factories + .filter(({ id }) => id === 'ALL') + .reduce( + (acc, cur) => { + return { + tvlUSD: acc.tvlUSD + Number(cur.liquidityUSD), + volumeUSD: acc.volumeUSD + Number(cur.volumeUSD), + pairCount: acc.pairCount + Number(cur.pairCount), + } + }, + { + tvlUSD: 0, + volumeUSD: 0, + pairCount: 0, + } + ), } } +const getBentoTvl = async () => { + const sdk = getBuiltGraphSDK() + const { rebases } = await sdk.RebasesByChainIds({ first: 1000, chainIds: BENTOBOX_ENABLED_NETWORKS }) + + const prices = await fetch('https://token-price.sushi.com/v1').then((data) => data.json()) + + return rebases.reduce((acc, cur) => { + const price = prices[cur.chainId][cur.id] || prices[cur.chainId][getAddress(cur.id)] + if (!price) return acc + + return acc + (Number(cur.elastic) / 10 ** Number(cur.token.decimals)) * Number(price) + }, 0) +} + +const getV3Data = async () => { + const sdk = getBuiltGraphSDK() + const { factories } = await sdk.V3Factories({ chainIds: SUSHISWAP_V3_SUPPORTED_CHAIN_IDS }) + + return factories.reduce( + (acc, cur) => { + return { + tvlUSD: acc.tvlUSD + Number(cur.totalValueLockedUSD), + volumeUSD: acc.volumeUSD + Number(cur.totalVolumeUSD), + pairCount: acc.pairCount + Number(cur.poolCount), + } + }, + { + tvlUSD: 0, + volumeUSD: 0, + pairCount: 0, + } + ) +} + export const fetchCache = 'auto' export async function GET() { - const [sushiPrice, bentoTVL, legacyExchangeData, tridentExchangeData] = await Promise.all([ + const [sushiPrice, bentoTVL, v2Data, v3Data] = await Promise.all([ getSushiPriceUSD(), - getBentoTVL(), - getLegacyExchangeData(), - getTridentExchangeData(), + getBentoTvl(), + getV2Data(), + getV3Data(), ]) - const totalTVL = bentoTVL + legacyExchangeData.tvlUSD - const totalVolume = legacyExchangeData.volumeUSD + tridentExchangeData.volumeUSD - const totalPoolCount = legacyExchangeData.pairCount + tridentExchangeData.poolCount + const totalTVL = bentoTVL + v2Data.v2.tvlUSD + v3Data.tvlUSD + const totalVolume = v2Data.v2.volumeUSD + v2Data.trident.volumeUSD + v3Data.volumeUSD + const totalPoolCount = v2Data.v2.pairCount + v2Data.trident.pairCount + v3Data.pairCount return NextResponse.json({ stats: { diff --git a/apps/evm/functions/graph/constants.ts b/apps/evm/functions/graph/constants.ts deleted file mode 100644 index a68434fa06..0000000000 --- a/apps/evm/functions/graph/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ChainId } from '@sushiswap/chain' - -const THE_GRAPH = 'https://api.thegraph.com' -const HYPER_GRAPH = 'https://q.hg.network' - -export const GRAPH_HOST = { - [ChainId.ETHEREUM]: THE_GRAPH, - [ChainId.GNOSIS]: THE_GRAPH, - [ChainId.POLYGON]: THE_GRAPH, - [ChainId.FANTOM]: THE_GRAPH, - [ChainId.BSC]: THE_GRAPH, - [ChainId.AVALANCHE]: THE_GRAPH, - [ChainId.CELO]: THE_GRAPH, - [ChainId.ARBITRUM]: THE_GRAPH, - [ChainId.HARMONY]: 'https://sushi.graph.t.hmny.io', - [ChainId.OKEX]: HYPER_GRAPH, - [ChainId.HECO]: HYPER_GRAPH, - [ChainId.MOONRIVER]: THE_GRAPH, - [ChainId.TELOS]: THE_GRAPH, - [ChainId.FUSE]: THE_GRAPH, - [ChainId.MOONBEAM]: THE_GRAPH, -} - -export const TRIDENT = { - [ChainId.POLYGON]: 'matthewlilley/trident-polygon', -} diff --git a/apps/evm/functions/graph/fetchers/bentobox.ts b/apps/evm/functions/graph/fetchers/bentobox.ts deleted file mode 100644 index d3e169f5df..0000000000 --- a/apps/evm/functions/graph/fetchers/bentobox.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { BENTOBOX_SUBGRAPH_NAME, BentoBoxChainId, SUBGRAPH_HOST } from '@sushiswap/graph-config' - -import { pager } from '../pager' -import { bentoTokensQuery } from '../queries/bentobox' -import { getNativePrice, getTokenPrices } from './exchange' - -export default async function getBentoTVL() { - const bentoTVLQueries: Promise[] = [] - - const chainIds = Object.keys(BENTOBOX_SUBGRAPH_NAME).map((chainId) => Number(chainId) as BentoBoxChainId) - - for (const chainId of chainIds) { - bentoTVLQueries.push( - (async function () { - try { - const nativePrice = await getNativePrice(chainId) - const { tokens: bentoTokens } = await pager( - `https://${SUBGRAPH_HOST[chainId]}/${BENTOBOX_SUBGRAPH_NAME[chainId]}`, - bentoTokensQuery - ) - const tokenPrices = await getTokenPrices( - bentoTokens.map((bentoToken: any) => bentoToken.id), - chainId - ) - - return bentoTokens - .map((bentoToken: any) => { - const tokenPriceUSD = - Number(tokenPrices.find((tokenPrice: any) => tokenPrice.id === bentoToken.id)?.derivedETH ?? 0) * - nativePrice - - return Number((bentoToken.rebase.elastic / 10 ** bentoToken.decimals) * tokenPriceUSD) - }) - .reduce((acc: any, cur: any) => Number(acc) + Number(cur), 0) - } catch { - return 0 - } - })() - ) - } - - return (await Promise.all(bentoTVLQueries)).reduce((acc, cur) => acc + cur, 0) -} diff --git a/apps/evm/functions/graph/fetchers/exchange.ts b/apps/evm/functions/graph/fetchers/exchange.ts deleted file mode 100644 index 1035b2468f..0000000000 --- a/apps/evm/functions/graph/fetchers/exchange.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { EXCHANGE_SUBGRAPH_NAME, SUBGRAPH_HOST } from '@sushiswap/graph-config' -import request from 'graphql-request' - -import { pager } from '../pager' -import { bundleQuery, factoryQuery, tokenPricesQuery } from '../queries/exchange' - -export async function getNativePrice(chainId: keyof typeof SUBGRAPH_HOST & keyof typeof EXCHANGE_SUBGRAPH_NAME) { - try { - // @ts-ignore - const { bundle } = await request( - `https://${SUBGRAPH_HOST[chainId]}/${EXCHANGE_SUBGRAPH_NAME[chainId]}`, - bundleQuery - ) - - return Number(bundle?.ethPrice) ?? 0 - } catch { - return 0 - } -} - -export async function getTokenPrices( - tokens: string[], - chainId: keyof typeof SUBGRAPH_HOST & keyof typeof EXCHANGE_SUBGRAPH_NAME -) { - try { - const { tokens: tokenPrices } = await pager( - `https://${SUBGRAPH_HOST[chainId]}/${EXCHANGE_SUBGRAPH_NAME[chainId]}`, - tokenPricesQuery, - { - where: { id_in: tokens }, - } - ) - - return tokenPrices - } catch { - return [] - } -} - -async function getFactory(chainId: keyof typeof SUBGRAPH_HOST & keyof typeof EXCHANGE_SUBGRAPH_NAME) { - try { - // @ts-ignore - const { factories } = await request( - `https://${SUBGRAPH_HOST[chainId]}/${EXCHANGE_SUBGRAPH_NAME[chainId]}`, - factoryQuery - ) - const factory = factories[0] - - return { - volumeUSD: Number(factory.volumeUSD), - tvlUSD: Number(factory.liquidityUSD), - pairCount: Number(factory.pairCount), - userCount: Number(factory.userCount), - } - } catch { - return { - volumeUSD: 0, - tvlUSD: 0, - pairCount: 0, - userCount: 0, - } - } -} - -export async function getLegacyExchangeData() { - const factoryQueries: ReturnType[] = [] - const chainIds = Object.keys(EXCHANGE_SUBGRAPH_NAME).map(Number) as (keyof typeof SUBGRAPH_HOST & - keyof typeof EXCHANGE_SUBGRAPH_NAME)[] - for (const chainId of chainIds) { - factoryQueries.push(getFactory(chainId)) - } - - return (await Promise.all(factoryQueries)).reduce((acc, cur) => { - return { - volumeUSD: acc.volumeUSD + cur.volumeUSD, - tvlUSD: acc.tvlUSD + cur.tvlUSD, - pairCount: acc.pairCount + cur.pairCount, - userCount: acc.userCount + cur.userCount, - } - }) -} diff --git a/apps/evm/functions/graph/fetchers/trident.ts b/apps/evm/functions/graph/fetchers/trident.ts deleted file mode 100644 index c7133df30e..0000000000 --- a/apps/evm/functions/graph/fetchers/trident.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from 'graphql-request' - -export const tridentFactoryQuery = gql` - query tridentFactoryQuery($block: Block_height) { - factories(first: 1, block: $block) { - volumeUSD - poolCount - } - } -` - -export const bentoBoxQuery = gql` - query bentoBoxQuery($block: Block_height, $where: BentoBox_filter) { - bentoBoxes(first: 1, block: $block, where: $where) { - id - protocolCount - userCount - tokenCount - masterContractCount - cloneCount - flashloanCount - transactionCount - } - } -` - -export const bentoTokensQuery = gql` - query bentoTokens($first: Int = 1000, $skip: Int = 0, $block: Block_height, $where: Token_filter) { - tokens(first: $first, skip: $skip, block: $block, where: $where) { - id - rebase { - elastic - } - } - } -` diff --git a/apps/evm/functions/graph/pager.ts b/apps/evm/functions/graph/pager.ts deleted file mode 100644 index e6fa794037..0000000000 --- a/apps/evm/functions/graph/pager.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {request, Variables} from 'graphql-request' - -// @ts-ignore TYPE NEEDS FIXING -export async function pager(endpoint: string, query, variables: Variables = {}) { - if (endpoint.includes('undefined')) return {} - - const data: any = {} - let skip = 0 - let flag = true - - while (flag) { - flag = false - const req = await request(endpoint, query, variables) - - // @ts-ignore TYPE NEEDS FIXING - Object.keys(req).forEach((key) => { - // @ts-ignore TYPE NEEDS FIXING - data[key] = data[key] ? [...data[key], ...req[key]] : req[key] - }) - - // @ts-ignore TYPE NEEDS FIXING - Object.values(req).forEach((entry: any) => { - if (entry.length === 1000) flag = true - }) - - // @ts-ignore TYPE NEEDS FIXING - if (Object.keys(variables).includes('first') && variables['first'] !== undefined) break - - skip += 1000 - variables = { ...variables, skip } - } - return data -} diff --git a/apps/evm/functions/graph/queries/bentobox.ts b/apps/evm/functions/graph/queries/bentobox.ts deleted file mode 100644 index e61328c03b..0000000000 --- a/apps/evm/functions/graph/queries/bentobox.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { gql } from 'graphql-request' - -export const bentoBoxQuery = gql` - query bentoBoxQuery($block: Block_height, $where: BentoBox_filter) { - bentoBoxes(first: 1, block: $block, where: $where) { - id - protocolCount - userCount - tokenCount - masterContractCount - cloneCount - flashloanCount - transactionCount - } - } -` - -export const bentoTokensQuery = gql` - query bentoTokens($first: Int = 1000, $skip: Int = 0, $block: Block_height, $where: Token_filter) { - tokens(first: $first, skip: $skip, block: $block, where: $where) { - id - decimals - rebase { - elastic - } - } - } -` diff --git a/apps/evm/functions/graph/queries/exchange.ts b/apps/evm/functions/graph/queries/exchange.ts deleted file mode 100644 index 9fe9b88eae..0000000000 --- a/apps/evm/functions/graph/queries/exchange.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { gql } from 'graphql-request' - -export const factoryQuery = gql` - query factoryQuery($block: Block_height) { - factories(first: 1, block: $block) { - id - pairCount - userCount - volumeUSD - liquidityUSD - } - } -` - -export const bundleFields = gql` - fragment bundleFields on Bundle { - id - ethPrice - } -` - -export const bundleQuery = gql` - query bundleQuery($id: Int! = 1, $block: Block_height) { - bundle(id: $id, block: $block) { - ...bundleFields - } - } - ${bundleFields} -` - -export const tokenPricesQuery = gql` - query tokenPricesQuery($first: Int = 1000, $skip: Int = 0, $block: Block_height, $where: Token_filter) { - tokens(first: $first, skip: $skip, block: $block, where: $where) { - id - derivedETH - } - } -` diff --git a/apps/evm/functions/graph/queries/trident.ts b/apps/evm/functions/graph/queries/trident.ts deleted file mode 100644 index 30532a3e9f..0000000000 --- a/apps/evm/functions/graph/queries/trident.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { SUBGRAPH_HOST, TRIDENT_SUBGRAPH_NAME, TridentChainId } from '@sushiswap/graph-config' -import request from 'graphql-request' - -import { tridentFactoryQuery } from '../fetchers/trident' - -async function getTridentFactory(chainId: TridentChainId) { - try { - // @ts-ignore FIX TYPE - const { factories } = await request( - `https://${SUBGRAPH_HOST[chainId]}/${TRIDENT_SUBGRAPH_NAME[chainId]}`, - tridentFactoryQuery - ) - const factory = factories[0] - - return { - volumeUSD: Number(factory.volumeUSD), - poolCount: Number(factory.poolCount), - } - } catch { - return { - volumeUSD: 0, - poolCount: 0, - } - } -} - -export async function getTridentExchangeData() { - const factoryQueries: ReturnType[] = [] - const chainIds = Object.keys(TRIDENT_SUBGRAPH_NAME).map((chainId) => Number(chainId) as TridentChainId) - - for (const chainId of chainIds) { - factoryQueries.push(getTridentFactory(chainId)) - } - - return (await Promise.all(factoryQueries)).reduce((acc, cur) => { - return { - volumeUSD: acc.volumeUSD + cur.volumeUSD, - poolCount: acc.poolCount + cur.poolCount, - } - }) -} diff --git a/packages/graph-client/.graphclientrc.yml b/packages/graph-client/.graphclientrc.yml index 74f8acb195..bf57c2dab2 100644 --- a/packages/graph-client/.graphclientrc.yml +++ b/packages/graph-client/.graphclientrc.yml @@ -302,6 +302,9 @@ additionalTypeDefs: | extend type Factory { chainId: Int! } + extend type SUSHISWAP_V3_Factory { + chainId: Int! + } extend type Rebase { chainId: Int! } @@ -409,8 +412,11 @@ additionalTypeDefs: | chainIds: [Int!]! ): [Block!]! factoriesByChainIds( - chainIds: [Int!]! + chainIds: [BigInt!]! ): [Factory!]! + v3factoriesByChainIds( + chainIds: [BigInt!]! + ): [SUSHISWAP_V3_Factory!]! liquidityPositionsByChainIds( skip: Int = 0 first: Int = 100 @@ -554,7 +560,7 @@ additionalTypeDefs: | orderBy: Pair_orderBy orderDirection: OrderDirection block: Block_height - chainIds: [Int!]! + chainIds: [BigInt!]! now: Int! ): [Pair!]! oneDayBlocks( diff --git a/packages/graph-client/queries/concetrated.graphql b/packages/graph-client/queries/concetrated.graphql index b15589875d..c7a9eae50b 100644 --- a/packages/graph-client/queries/concetrated.graphql +++ b/packages/graph-client/queries/concetrated.graphql @@ -52,6 +52,16 @@ query PoolsByTokenPair($tokenId0: String!, $tokenId1: String!) { } } +query V3Factories($chainIds: [BigInt!]!) { + factories: v3factoriesByChainIds(chainIds: $chainIds) { + id + chainId + totalValueLockedUSD + totalVolumeUSD + poolCount + } +} + query V3Transactions($first: Int = 100, $skip: Int = 0, $orderBy: CONCENTRATED_Transaction_orderBy = timestamp, $orderDir: CONCENTRATED_OrderDirection = desc, $where: CONCENTRATED_Transaction_filter) { transactions: CONCENTRATED_transactions(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDir, where: $where) { id diff --git a/packages/graph-client/queries/deprecated.graphql b/packages/graph-client/queries/deprecated.graphql index 34b2d7a83a..132e633372 100644 --- a/packages/graph-client/queries/deprecated.graphql +++ b/packages/graph-client/queries/deprecated.graphql @@ -178,7 +178,7 @@ query CrossChainStats( $orderBy: Pair_orderBy $orderDirection: OrderDirection $block: Block_height - $chainIds: [Int!]! + $chainIds: [BigInt!]! $now: Int! ) { crossChainStats( diff --git a/packages/graph-client/queries/factories.graphql b/packages/graph-client/queries/factories.graphql index 7542d67fc9..e841265d68 100644 --- a/packages/graph-client/queries/factories.graphql +++ b/packages/graph-client/queries/factories.graphql @@ -1,7 +1,9 @@ -query Factories($chainIds: [Int!]!) { +query Factories($chainIds: [BigInt!]!) { factories: factoriesByChainIds(chainIds: $chainIds) { id chainId + liquidityUSD + volumeUSD pairCount tokenCount } diff --git a/packages/graph-client/resolvers/bentobox/crossChainBentoBoxKpis.ts b/packages/graph-client/resolvers/bentobox/crossChainBentoBoxKpis.ts index f3f0e53dfe..1d5f814fac 100644 --- a/packages/graph-client/resolvers/bentobox/crossChainBentoBoxKpis.ts +++ b/packages/graph-client/resolvers/bentobox/crossChainBentoBoxKpis.ts @@ -1,6 +1,7 @@ import { BENTOBOX_SUBGRAPH_NAME, SUBGRAPH_HOST } from '@sushiswap/graph-config' import { BentoBoxKpi, Resolvers } from '../../.graphclient/index.js' +import { ChainId } from '@sushiswap/chain' export const crossChainBentoBoxKpis: Resolvers['Query']['crossChainBentoBoxKpis'] = async ( root, @@ -8,10 +9,13 @@ export const crossChainBentoBoxKpis: Resolvers['Query']['crossChainBentoBoxKpis' context, info ) => { - const supportedChainIds = args.chainIds.filter( - (chainId): chainId is keyof typeof BENTOBOX_SUBGRAPH_NAME & keyof typeof SUBGRAPH_HOST => - chainId in BENTOBOX_SUBGRAPH_NAME - ) + const supportedChainIds = args.chainIds + .filter( + (chainId): chainId is keyof typeof BENTOBOX_SUBGRAPH_NAME & keyof typeof SUBGRAPH_HOST => + chainId in BENTOBOX_SUBGRAPH_NAME + ) + // Kava subgraph doesn't have the bentoBoxKpis query + .filter((chainId) => chainId !== ChainId.KAVA) const kpis = await Promise.all( supportedChainIds.map((chainId) => @@ -25,13 +29,15 @@ export const crossChainBentoBoxKpis: Resolvers['Query']['crossChainBentoBoxKpis' subgraphHost: SUBGRAPH_HOST[chainId], }, info, - }).then((kpis: BentoBoxKpi[]) => + }).then((kpis: BentoBoxKpi[]) => { // We send chainId here so we can take it in the resolver above - kpis.map((kpi) => ({ + console.log(kpis, chainId) + + return kpis.map((kpi) => ({ ...kpi, chainId, })) - ) + }) ) ) diff --git a/packages/graph-client/resolvers/concentrated/index.ts b/packages/graph-client/resolvers/concentrated/index.ts index 8e8c2f95f9..35d82e28bf 100644 --- a/packages/graph-client/resolvers/concentrated/index.ts +++ b/packages/graph-client/resolvers/concentrated/index.ts @@ -1,9 +1,11 @@ import { Resolvers } from '../../.graphclient/index.js' import { poolsByTokenPair } from './poolsByTokenPair.js' +import { v3factoriesByChainIds } from './v3factoriesByChainIds.js' import { ticksById } from './ticksById.js' export const resolvers: Resolvers = { Query: { + v3factoriesByChainIds: v3factoriesByChainIds, ticksById: ticksById, poolsByTokenPair: poolsByTokenPair, },