From 18c0ec9efcc0fc7df72194018d403276aa4b841b Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:47:07 +0100 Subject: [PATCH] sync-latest-fx-prices --- modules/network/avalanche.ts | 4 ++ modules/network/mainnet.ts | 4 ++ modules/network/optimism.ts | 5 +- modules/network/polygon.ts | 4 ++ modules/pool/pool.prisma | 1 + .../balancer-subgraph-queries.graphql | 4 ++ modules/token/latest-fx-price.ts | 49 +++++++++++++++++++ modules/token/token.gql | 1 + modules/token/token.resolvers.ts | 10 ++++ .../migration.sql | 2 + prisma/schema.prisma | 1 + worker/job-handlers.ts | 16 ++++++ 12 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 modules/token/latest-fx-price.ts create mode 100644 prisma/migrations/20231215151625_latest_fx_price/migration.sql diff --git a/modules/network/avalanche.ts b/modules/network/avalanche.ts index 038f2788d..8a543c455 100644 --- a/modules/network/avalanche.ts +++ b/modules/network/avalanche.ts @@ -346,5 +346,9 @@ export const avalancheNetworkConfig: NetworkConfig = { name: 'feed-data-to-datastudio', interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(5, 'minutes') : every(1, 'minutes'), }, + { + name: 'sync-latest-fx-prices', + interval: every(10, 'minutes'), + }, ], }; diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index fe2fd1676..8592b1739 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -509,5 +509,9 @@ export const mainnetNetworkConfig: NetworkConfig = { name: 'feed-data-to-datastudio', interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(5, 'minutes') : every(1, 'minutes'), }, + { + name: 'sync-latest-fx-prices', + interval: every(10, 'minutes'), + }, ], }; diff --git a/modules/network/optimism.ts b/modules/network/optimism.ts index c0f3beccb..f104162b8 100644 --- a/modules/network/optimism.ts +++ b/modules/network/optimism.ts @@ -298,7 +298,10 @@ export const optimismNetworkConfig: NetworkConfig = { ], userStakedBalanceServices: [new UserSyncGaugeBalanceService()], services: { - balancerSubgraphService: new BalancerSubgraphService(optimismNetworkData.subgraphs.balancer, optimismNetworkData.chain.id), + balancerSubgraphService: new BalancerSubgraphService( + optimismNetworkData.subgraphs.balancer, + optimismNetworkData.chain.id, + ), }, /* For sub-minute jobs we set the alarmEvaluationPeriod and alarmDatapointsToAlarm to 1 instead of the default 3. diff --git a/modules/network/polygon.ts b/modules/network/polygon.ts index 2420ce629..357f35df8 100644 --- a/modules/network/polygon.ts +++ b/modules/network/polygon.ts @@ -391,5 +391,9 @@ export const polygonNetworkConfig: NetworkConfig = { name: 'feed-data-to-datastudio', interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(5, 'minutes') : every(1, 'minutes'), }, + { + name: 'sync-latest-fx-prices', + interval: every(10, 'minutes'), + }, ], }; diff --git a/modules/pool/pool.prisma b/modules/pool/pool.prisma index 6781f3727..a03ce9e54 100644 --- a/modules/pool/pool.prisma +++ b/modules/pool/pool.prisma @@ -229,6 +229,7 @@ model PrismaPoolTokenDynamicData { balanceUSD Float weight String? priceRate String + latestFxPrice Float? } model PrismaPoolSwap { diff --git a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql index f154888d1..bdf1a2171 100644 --- a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql +++ b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql @@ -141,6 +141,7 @@ fragment BalancerToken on Token { id symbol address + latestFXPrice latestUSDPrice totalVolumeNotional totalVolumeUSD @@ -213,6 +214,9 @@ fragment BalancerPoolToken on PoolToken { weight priceRate index + token { + latestFXPrice + } } query BalancerPools( diff --git a/modules/token/latest-fx-price.ts b/modules/token/latest-fx-price.ts new file mode 100644 index 000000000..2eb090661 --- /dev/null +++ b/modules/token/latest-fx-price.ts @@ -0,0 +1,49 @@ +import { GraphQLClient } from 'graphql-request'; +import { getSdk } from '../subgraphs/balancer-subgraph/generated/balancer-subgraph-types'; +import { prisma } from '../../prisma/prisma-client'; +import { Chain } from '@prisma/client'; + +/** + * 'Latest FX Price' is relevant only to FX pools. It is sourced from offchain platforms, like Chainlink. + * The subgraph actively indexes this price by listening to the 'Answer Updated' events emitted by Chainlink oracles. + * For reference and more details, see the code at: + * https://github.com/balancer/balancer-subgraph-v2/blob/master/src/mappings/pricing.ts#L373 + * + * Note: 'LatestFXPrice' is a dependency of SORv2. + */ +export const syncLatestFXPrices = async (subgraphUrl: string, chain: Chain) => { + const { pools } = await fetchFxPools(subgraphUrl); + + for (const pool of pools) { + const { tokens } = pool; + if (!tokens) continue; + + for (const token of tokens) { + try { + await prisma.prismaPoolTokenDynamicData.update({ + where: { + id_chain: { + id: token.id, + chain, + }, + }, + data: { + latestFxPrice: token.token.latestFXPrice ? parseFloat(token.token.latestFXPrice) : undefined, + }, + }); + } catch (e) { + console.error(`Error updating latest FX price for token ${token.id} on chain ${chain}: ${e}`); + } + } + } + + return true; +}; + +const fetchFxPools = (subgraphUrl: string) => { + const sdk = getSdk(new GraphQLClient(subgraphUrl)); + + return sdk.BalancerPools({ + where: { poolType: 'FX' }, + }); +}; diff --git a/modules/token/token.gql b/modules/token/token.gql index 5481e271b..3adbb8db1 100644 --- a/modules/token/token.gql +++ b/modules/token/token.gql @@ -20,6 +20,7 @@ extend type Mutation { tokenReloadTokenPrices: Boolean tokenSyncTokenDefinitions: String! tokenSyncTokenDynamicData: String! + tokenSyncLatestFxPrices(chain: GqlChain!): String! tokenInitChartData(tokenAddress: String!): String! tokenDeletePrice(tokenAddress: String!, timestamp: Int!): Boolean! tokenDeleteTokenType(tokenAddress: String!, type: GqlTokenType!): String! diff --git a/modules/token/token.resolvers.ts b/modules/token/token.resolvers.ts index 8bc79db4c..7554d5c9c 100644 --- a/modules/token/token.resolvers.ts +++ b/modules/token/token.resolvers.ts @@ -3,6 +3,8 @@ import _ from 'lodash'; import { isAdminRoute } from '../auth/auth-context'; import { tokenService } from './token.service'; import { headerChain } from '../context/header-chain'; +import { syncLatestFXPrices } from './latest-fx-price'; +import { AllNetworkConfigsKeyedOnChain } from '../network/network-config'; const resolvers: Resolvers = { Query: { @@ -137,6 +139,14 @@ const resolvers: Resolvers = { return 'success'; }, + tokenSyncLatestFxPrices: async (parent, { chain }, context) => { + isAdminRoute(context); + const subgraphUrl = AllNetworkConfigsKeyedOnChain[chain].data.subgraphs.balancer; + + await syncLatestFXPrices(subgraphUrl, chain); + + return 'success'; + }, tokenInitChartData: async (parent, { tokenAddress }, context) => { isAdminRoute(context); diff --git a/prisma/migrations/20231215151625_latest_fx_price/migration.sql b/prisma/migrations/20231215151625_latest_fx_price/migration.sql new file mode 100644 index 000000000..3f97ae378 --- /dev/null +++ b/prisma/migrations/20231215151625_latest_fx_price/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "PrismaPoolTokenDynamicData" ADD COLUMN "latestFxPrice" DOUBLE PRECISION; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6e3103627..3dc68f074 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -275,6 +275,7 @@ model PrismaPoolTokenDynamicData { balanceUSD Float weight String? priceRate String + latestFxPrice Float? } model PrismaPoolSwap { diff --git a/worker/job-handlers.ts b/worker/job-handlers.ts index 9b6102541..d2b3d2143 100644 --- a/worker/job-handlers.ts +++ b/worker/job-handlers.ts @@ -14,6 +14,8 @@ import { veBalVotingListService } from '../modules/vebal/vebal-voting-list.servi import { cronsMetricPublisher } from '../modules/metrics/metrics.client'; import moment from 'moment'; import { cronsDurationMetricPublisher } from '../modules/metrics/cron-duration-metrics.client'; +import { syncLatestFXPrices } from '../modules/token/latest-fx-price'; +import { AllNetworkConfigs } from '../modules/network/network-config'; const runningJobs: Set = new Set(); @@ -278,6 +280,20 @@ export function configureWorkerRoutes(app: Express) { next, ); break; + case 'sync-latest-fx-prices': + await runIfNotAlreadyRunning( + job.name, + chainId, + () => { + const config = AllNetworkConfigs[chainId].data; + const subgraphUrl = config.subgraphs.balancer; + const chain = config.chain.prismaId; + return syncLatestFXPrices(subgraphUrl, chain); + }, + res, + next, + ); + break; default: res.sendStatus(400); throw new Error(`Unhandled job type ${job.name}`);