From ac7a326e4f64fbd2822f9b54e48316816845f418 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Tue, 15 Aug 2023 00:24:27 +1000 Subject: [PATCH] Add basic pools fetcher, refactor some util functions --- .../queries/useVotingPoolsQuery.ts | 66 +----- src/lib/utils/api.ts | 65 ++++++ .../api/graphql/generated/api-schema.graphql | 1 + .../api/graphql/generated/api-types.ts | 217 ++++++++++++++++++ src/services/api/graphql/pool.graphql | 109 +++++++++ .../balancer/api/entities/pools/index.ts | 127 +++++++--- 6 files changed, 492 insertions(+), 93 deletions(-) create mode 100644 src/lib/utils/api.ts create mode 100644 src/services/api/graphql/pool.graphql diff --git a/src/composables/queries/useVotingPoolsQuery.ts b/src/composables/queries/useVotingPoolsQuery.ts index 6532e1ba29..ad5a67977a 100644 --- a/src/composables/queries/useVotingPoolsQuery.ts +++ b/src/composables/queries/useVotingPoolsQuery.ts @@ -8,14 +8,11 @@ import { } from '@/services/balancer/gauges/gauge-controller.decorator'; import useWeb3 from '@/services/web3/useWeb3'; import { isTestnet } from '@/composables/useNetwork'; -import { - GqlChain, - GqlPoolMinimalType, - VeBalGetVotingListQuery, -} from '@/services/api/graphql/generated/api-types'; +import { VeBalGetVotingListQuery } from '@/services/api/graphql/generated/api-types'; import { Network } from '@/lib/config'; import { PoolType } from '@/services/pool/types'; import { testnetVotingPools } from '@/components/contextual/pages/vebal/LMVoting/testnet-voting-pools'; +import { mapApiChain, mapApiPoolType } from '@/lib/utils/api'; /** * TYPES @@ -94,62 +91,3 @@ export default function useVotingPoolsQuery( queryOptions as QueryOptions ); } - -export function mapApiChain( - apiChain: GqlChain | 'SEPOLIA' | 'GOERLI' -): Network { - switch (apiChain) { - case GqlChain.Arbitrum: - return Network.ARBITRUM; - case GqlChain.Avalanche: - return Network.AVALANCHE; - case GqlChain.Gnosis: - return Network.GNOSIS; - case GqlChain.Fantom: - return Network.FANTOM; - case GqlChain.Optimism: - return Network.OPTIMISM; - case GqlChain.Polygon: - return Network.POLYGON; - case GqlChain.Mainnet: - return Network.MAINNET; - case GqlChain.Zkevm: - return Network.ZKEVM; - case 'SEPOLIA': - return Network.SEPOLIA; - case 'GOERLI': - return Network.GOERLI; - - default: - throw new Error(`Unexpected API chain: ${apiChain}`); - } -} - -export function mapApiPoolType( - apiPoolType: GqlPoolMinimalType -): PoolType | null { - switch (apiPoolType) { - case GqlPoolMinimalType.Element: - return PoolType.Element; - case GqlPoolMinimalType.Gyro3: - return PoolType.Gyro3; - case GqlPoolMinimalType.Gyroe: - return PoolType.GyroE; - case GqlPoolMinimalType.Investment: - return PoolType.Investment; - case GqlPoolMinimalType.Linear: - return PoolType.Linear; - case GqlPoolMinimalType.LiquidityBootstrapping: - return PoolType.LiquidityBootstrapping; - case GqlPoolMinimalType.MetaStable: - return PoolType.MetaStable; - case GqlPoolMinimalType.PhantomStable: - return PoolType.StablePhantom; - case GqlPoolMinimalType.Stable: - return PoolType.Stable; - case GqlPoolMinimalType.Weighted: - return PoolType.Weighted; - default: - return null; - } -} diff --git a/src/lib/utils/api.ts b/src/lib/utils/api.ts new file mode 100644 index 0000000000..22f1e87ce6 --- /dev/null +++ b/src/lib/utils/api.ts @@ -0,0 +1,65 @@ +import { + GqlChain, + GqlPoolMinimalType, +} from '@/services/api/graphql/generated/api-types'; +import { Network } from '@/lib/config'; +import { PoolType } from '@/services/pool/types'; + +export function mapApiChain( + apiChain: GqlChain | 'SEPOLIA' | 'GOERLI' +): Network { + switch (apiChain) { + case GqlChain.Arbitrum: + return Network.ARBITRUM; + case GqlChain.Avalanche: + return Network.AVALANCHE; + case GqlChain.Gnosis: + return Network.GNOSIS; + case GqlChain.Fantom: + return Network.FANTOM; + case GqlChain.Optimism: + return Network.OPTIMISM; + case GqlChain.Polygon: + return Network.POLYGON; + case GqlChain.Mainnet: + return Network.MAINNET; + case GqlChain.Zkevm: + return Network.ZKEVM; + case 'SEPOLIA': + return Network.SEPOLIA; + case 'GOERLI': + return Network.GOERLI; + + default: + throw new Error(`Unexpected API chain: ${apiChain}`); + } +} + +export function mapApiPoolType( + apiPoolType: GqlPoolMinimalType +): PoolType | null { + switch (apiPoolType) { + case GqlPoolMinimalType.Element: + return PoolType.Element; + case GqlPoolMinimalType.Gyro3: + return PoolType.Gyro3; + case GqlPoolMinimalType.Gyroe: + return PoolType.GyroE; + case GqlPoolMinimalType.Investment: + return PoolType.Investment; + case GqlPoolMinimalType.Linear: + return PoolType.Linear; + case GqlPoolMinimalType.LiquidityBootstrapping: + return PoolType.LiquidityBootstrapping; + case GqlPoolMinimalType.MetaStable: + return PoolType.MetaStable; + case GqlPoolMinimalType.PhantomStable: + return PoolType.StablePhantom; + case GqlPoolMinimalType.Stable: + return PoolType.Stable; + case GqlPoolMinimalType.Weighted: + return PoolType.Weighted; + default: + return null; + } +} diff --git a/src/services/api/graphql/generated/api-schema.graphql b/src/services/api/graphql/generated/api-schema.graphql index 98d6caa9a6..950985f22e 100644 --- a/src/services/api/graphql/generated/api-schema.graphql +++ b/src/services/api/graphql/generated/api-schema.graphql @@ -980,6 +980,7 @@ type Mutation { poolBlackListAddPool(poolId: String!): String! poolBlackListRemovePool(poolId: String!): String! poolDeletePool(poolId: String!): String! + poolInitOnChainDataForAllPools: String! poolInitializeSnapshotsForPool(poolId: String!): String! poolLoadOnChainDataForAllPools: String! poolLoadOnChainDataForPoolsWithActiveUpdates: String! diff --git a/src/services/api/graphql/generated/api-types.ts b/src/services/api/graphql/generated/api-types.ts index 6c1f3a5d5e..331b5b4be1 100644 --- a/src/services/api/graphql/generated/api-types.ts +++ b/src/services/api/graphql/generated/api-types.ts @@ -1077,6 +1077,7 @@ export type Mutation = { poolBlackListAddPool: Scalars['String']; poolBlackListRemovePool: Scalars['String']; poolDeletePool: Scalars['String']; + poolInitOnChainDataForAllPools: Scalars['String']; poolInitializeSnapshotsForPool: Scalars['String']; poolLoadOnChainDataForAllPools: Scalars['String']; poolLoadOnChainDataForPoolsWithActiveUpdates: Scalars['String']; @@ -1355,6 +1356,97 @@ export type QueryUserGetSwapsArgs = { skip?: InputMaybe; }; +export type GetPoolsQueryVariables = Exact<{ [key: string]: never }>; + +export type GetPoolsQuery = { + __typename?: 'Query'; + pools: Array<{ + __typename?: 'GqlPoolMinimal'; + id: string; + chain: GqlChain; + address: string; + name: string; + symbol: string; + type: GqlPoolMinimalType; + version: number; + owner?: string | null; + factory?: string | null; + createTime: number; + dynamicData: { + __typename?: 'GqlPoolDynamicData'; + totalLiquidity: string; + totalShares: string; + fees24h: string; + swapFee: string; + lifetimeSwapFees: string; + swapEnabled: boolean; + volume24h: string; + lifetimeVolume: string; + apr: { + __typename?: 'GqlPoolApr'; + hasRewardApr: boolean; + swapApr: string; + thirdPartyApr: + | { __typename?: 'GqlPoolAprRange'; min: string; max: string } + | { __typename?: 'GqlPoolAprTotal'; total: string }; + nativeRewardApr: + | { __typename?: 'GqlPoolAprRange'; min: string; max: string } + | { __typename?: 'GqlPoolAprTotal'; total: string }; + apr: + | { __typename?: 'GqlPoolAprRange'; min: string; max: string } + | { __typename?: 'GqlPoolAprTotal'; total: string }; + items: Array<{ + __typename?: 'GqlBalancePoolAprItem'; + id: string; + title: string; + apr: + | { __typename?: 'GqlPoolAprRange'; min: string; max: string } + | { __typename?: 'GqlPoolAprTotal'; total: string }; + subItems?: Array<{ + __typename?: 'GqlBalancePoolAprSubItem'; + id: string; + title: string; + apr: + | { __typename?: 'GqlPoolAprRange'; min: string; max: string } + | { __typename?: 'GqlPoolAprTotal'; total: string }; + }> | null; + }>; + }; + }; + allTokens: Array<{ + __typename?: 'GqlPoolTokenExpanded'; + id: string; + address: string; + isNested: boolean; + isPhantomBpt: boolean; + weight?: string | null; + symbol: string; + }>; + displayTokens: Array<{ + __typename?: 'GqlPoolTokenDisplay'; + id: string; + address: string; + name: string; + weight?: string | null; + symbol: string; + nestedTokens?: Array<{ + __typename?: 'GqlPoolTokenDisplay'; + id: string; + address: string; + name: string; + weight?: string | null; + symbol: string; + }> | null; + }>; + staking?: { + __typename?: 'GqlPoolStaking'; + id: string; + type: GqlPoolStakingType; + address: string; + } | null; + }>; +}; + export type GetCurrentTokenPricesQueryVariables = Exact<{ [key: string]: never; }>; @@ -1396,6 +1488,117 @@ export type VeBalGetVotingListQuery = { }>; }; +export const GetPoolsDocument = gql` + query GetPools { + pools: poolGetPools { + id + chain + address + name + symbol + type + version + owner + factory + symbol + createTime + dynamicData { + totalLiquidity + totalShares + fees24h + swapFee + lifetimeSwapFees + swapEnabled + volume24h + lifetimeVolume + apr { + hasRewardApr + thirdPartyApr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + nativeRewardApr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + swapApr + apr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + items { + id + title + apr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + subItems { + id + title + apr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + } + } + } + } + allTokens { + id + address + isNested + isPhantomBpt + weight + symbol + } + displayTokens { + id + address + name + weight + symbol + nestedTokens { + id + address + name + weight + symbol + } + } + staking { + id + type + address + } + } + } +`; export const GetCurrentTokenPricesDocument = gql` query GetCurrentTokenPrices { prices: tokenGetCurrentPrices { @@ -1445,6 +1648,20 @@ export function getSdk( withWrapper: SdkFunctionWrapper = defaultWrapper ) { return { + GetPools( + variables?: GetPoolsQueryVariables, + requestHeaders?: Dom.RequestInit['headers'] + ): Promise { + return withWrapper( + wrappedRequestHeaders => + client.request(GetPoolsDocument, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'GetPools', + 'query' + ); + }, GetCurrentTokenPrices( variables?: GetCurrentTokenPricesQueryVariables, requestHeaders?: Dom.RequestInit['headers'] diff --git a/src/services/api/graphql/pool.graphql b/src/services/api/graphql/pool.graphql new file mode 100644 index 0000000000..d02fc47ed5 --- /dev/null +++ b/src/services/api/graphql/pool.graphql @@ -0,0 +1,109 @@ +query GetPools { + pools: poolGetPools { + id + chain + address + name + symbol + type + version + owner + factory + symbol + createTime + dynamicData { + totalLiquidity + totalShares + fees24h + swapFee + lifetimeSwapFees + swapEnabled + volume24h + lifetimeVolume + apr { + hasRewardApr + thirdPartyApr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + nativeRewardApr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + swapApr + apr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + items { + id + title + apr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + subItems { + id + title + apr { + ... on GqlPoolAprTotal { + total + } + ... on GqlPoolAprRange { + min + max + } + } + } + } + } + } + allTokens { + id + address + isNested + isPhantomBpt + weight + symbol + } + displayTokens { + id + address + name + weight + symbol + nestedTokens { + id + address + name + weight + symbol + } + } + staking { + id + type + address + } + } +} diff --git a/src/services/balancer/api/entities/pools/index.ts b/src/services/balancer/api/entities/pools/index.ts index 670340c13b..3bba348a7d 100644 --- a/src/services/balancer/api/entities/pools/index.ts +++ b/src/services/balancer/api/entities/pools/index.ts @@ -1,15 +1,23 @@ import { configService } from '@/services/config/config.service'; +import { getApi } from '@/dependencies/balancer-api'; import { Pool } from '@/services/pool/types'; +import { GetPoolsQuery } from '@/services/api/graphql/generated/api-types'; import { PoolsQueryBuilder } from '@/types/subgraph'; import { GraphQLArgs, GraphQLQuery, + PoolToken, + PoolType, PoolsBalancerAPIRepository, } from '@balancer-labs/sdk'; import _ from 'lodash'; import Service from '../../balancer-api.service'; import queryBuilder from './query'; +import { mapApiChain, mapApiPoolType } from '@/lib/utils/api'; + +export type ApiPools = GetPoolsQuery['pools']; +export type ApiPool = ApiPools[number]; export default class Pools { service: Service; @@ -26,39 +34,100 @@ export default class Pools { } public async get(args: GraphQLArgs = {}, attrs: any = {}): Promise { - const query = this.queryBuilder(args, attrs); - const skip = query.args.skip; - const first = query.args.first; - delete query.args.skip; // not allowed for Balancer API - delete query.args.first; + const api = getApi(); + const response = await api.GetPools(); + const pools: ApiPools = response.pools; - /* Some temporary hacks to make the API work with an empty in array (it should return no data) - * and if not_in is also set then delete it (because it's set by default) */ - if (query.args.where?.id?.in) { - if (query.args.where?.id?.in.length === 0) { - return []; - } - if (query.args.where?.id?.not_in) { - delete query.args.where?.id.not_in; - } - } + const convertedPools: Pool[] = pools.map((pool: ApiPool) => { + return this.mapPool(pool); + }); - if (!this.repository || !_.isEqual(query, this.lastQuery)) { - this.lastQuery = _.cloneDeep(query); - const graphQLUrl = `${configService.network.balancerApi}/graphql`; - this.repository = new PoolsBalancerAPIRepository({ - url: graphQLUrl, - apiKey: configService.network.keys.balancerApi || '', - query: query, - }); - } + return convertedPools; - const pools = await this.repository.fetch({ - first, - skip, - }); + // const query = this.queryBuilder(args, attrs); + // const skip = query.args.skip; + // const first = query.args.first; + // delete query.args.skip; // not allowed for Balancer API + // delete query.args.first; + + // /* Some temporary hacks to make the API work with an empty in array (it should return no data) + // * and if not_in is also set then delete it (because it's set by default) */ + // if (query.args.where?.id?.in) { + // if (query.args.where?.id?.in.length === 0) { + // return []; + // } + // if (query.args.where?.id?.not_in) { + // delete query.args.where?.id.not_in; + // } + // } + + // if (!this.repository || !_.isEqual(query, this.lastQuery)) { + // this.lastQuery = _.cloneDeep(query); + // const graphQLUrl = `${configService.network.balancerApi}/graphql`; + // this.repository = new PoolsBalancerAPIRepository({ + // url: graphQLUrl, + // apiKey: configService.network.keys.balancerApi || '', + // query: query, + // }); + // } + + // const pools = await this.repository.fetch({ + // first, + // skip, + // }); + + // return pools as Pool[]; + } + + // Converts a pool from the API subgraph to frontend pool type + private mapPool(apiPool: ApiPool): Pool { + return { + id: apiPool.id, + name: apiPool.name || '', + address: apiPool.address, + chainId: mapApiChain(apiPool.chain), + poolType: mapApiPoolType(apiPool.type) || PoolType.Weighted, + poolTypeVersion: apiPool.version || 1, + swapFee: apiPool.dynamicData.swapFee, + swapEnabled: apiPool.dynamicData.swapEnabled, + protocolYieldFeeCache: '0.5', // Default protocol yield fee + protocolSwapFeeCache: '0.5', // Default protocol swap fee + // amp: apiPool.amp ?? undefined, + owner: apiPool.owner ?? undefined, + factory: apiPool.factory ?? undefined, + symbol: apiPool.symbol ?? undefined, + tokens: (apiPool.allTokens || []).map(this.mapToken), + tokensList: (apiPool.allTokens || []).map(t => t.address), + tokenAddresses: (apiPool.allTokens || []).map(t => t.address), + totalLiquidity: apiPool.dynamicData.totalLiquidity, + totalShares: apiPool.dynamicData.totalShares, + totalSwapFee: apiPool.dynamicData.lifetimeSwapFees, + totalSwapVolume: apiPool.dynamicData.lifetimeVolume, + // priceRateProviders: apiPool.priceRateProviders ?? undefined, + // onchain: subgraphPool.onchain, + createTime: apiPool.createTime, + // mainIndex: apiPool.mainIndex ?? undefined, + // wrappedIndex: apiPool.wrappedIndex ?? undefined, + // mainTokens: subgraphPool.mainTokens, + // wrappedTokens: subgraphPool.wrappedTokens, + // unwrappedTokens: subgraphPool.unwrappedTokens, + // isNew: subgraphPool.isNew, + // volumeSnapshot: subgraphPool.volumeSnapshot, + // feesSnapshot: subgraphPool.???, // Approximated last 24h fees + // boost: subgraphPool.boost, + totalWeight: '1', + lowerTarget: '0', + upperTarget: '0', + // isInRecoveryMode: apiPool.isInRecoveryMode ?? false, + // isPaused: apiPool.isPaused ?? false, + }; + } - return pools as Pool[]; + private mapToken(apiToken: ApiPool['allTokens'][number]): PoolToken { + return { + address: apiToken.address, + balance: '0', + }; } get skip(): number {