diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e98329c..ded949cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # backend +## 1.29.2 + +### Patch Changes + +- ed867e5: add generalized erc4626 price handler +- 9d1a4b3: v3 on base and arbitrum + ## 1.29.1 ### Patch Changes diff --git a/config/arbitrum.ts b/config/arbitrum.ts index 7724ef42..9ae096fb 100644 --- a/config/arbitrum.ts +++ b/config/arbitrum.ts @@ -13,6 +13,8 @@ export default { subgraphs: { startDate: '2021-08-23', balancer: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmPbjY6L1NhPjpBv7wDTfG9EPx5FpCuBqeg1XxByzBTLcs`, + balancerV3: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmS6suakkYyKgEtDmMjwpbUzzHNmju7Rh5hSteZbCtTfgV`, + balancerPoolsV3: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmcxLe2iK1KcizhcdDggYyjL8HoNqFMeHLhscH49n1hZBW`, cowAmm: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmUDGSJXdMzG4ezDzf1LvXVb2igwY6rnaNFLC62ZJZ3Pbv`, blocks: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/JBnWrv9pvBvSi2pUZzba3VweGBTde6s44QvsDABP47Gt`, gauge: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmT3h6pogdPkxfWsBxKNtpq7kR9fqKaQ9jGxe7fZx7MUVE`, diff --git a/config/base.ts b/config/base.ts index c9c3684f..45842a41 100644 --- a/config/base.ts +++ b/config/base.ts @@ -13,6 +13,8 @@ export default { subgraphs: { startDate: '2023-07-10', balancer: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmRKBwBwPKtFz4mQp5jvH44USVprM4C77Nr4m77UGCbGv9`, + balancerV3: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmWREXT4GScDzeudK7i7uRYc2D6mPaVyw863KtDUrNgZU1`, + balancerPoolsV3: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmVmh7GAdYcGbKsbbkL7j42ciFEDTxrdtijUxYbhLs7jkv`, cowAmm: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/deployments/id/QmVRCjhFz7XXJoeJ5t4FdysN2JaBVdUCvpTVoMzXRNjA87`, blocks: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/6f2Z8rTvsBQinEMwRSBxbyg3BP2LTFiEA1hjPZxmy3xs`, gauge: `https://gateway.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/CfBvJNYsbKZdxXzaCtNc6dUbHH6TjDupprjKKo9gnmwg`, diff --git a/modules/network/arbitrum.ts b/modules/network/arbitrum.ts index 60382ba1..38a0bd04 100644 --- a/modules/network/arbitrum.ts +++ b/modules/network/arbitrum.ts @@ -148,5 +148,34 @@ export const arbitrumNetworkConfig: NetworkConfig = { name: 'sync-erc4626-unwrap-rate', interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(60, 'minutes') : every(20, 'minutes'), }, + // V3 Jobs + { + name: 'add-pools-v3', + interval: every(2, 'minutes'), + }, + { + name: 'sync-pools-v3', + interval: every(30, 'seconds'), + }, + { + name: 'sync-join-exits-v3', + interval: every(1, 'minutes'), + }, + { + name: 'sync-swaps-v3', + interval: every(1, 'minutes'), + }, + { + name: 'sync-snapshots-v3', + interval: every(10, 'minutes'), + }, + { + name: 'sync-hook-data', + interval: every(1, 'hours'), + }, + { + name: 'sync-erc4626-unwrap-rate', + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(60, 'minutes') : every(20, 'minutes'), + }, ], }; diff --git a/modules/network/base.ts b/modules/network/base.ts index 754ac39a..51b01c15 100644 --- a/modules/network/base.ts +++ b/modules/network/base.ts @@ -148,5 +148,34 @@ export const baseNetworkConfig: NetworkConfig = { name: 'sync-erc4626-unwrap-rate', interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(60, 'minutes') : every(20, 'minutes'), }, + // V3 Jobs + { + name: 'add-pools-v3', + interval: every(2, 'minutes'), + }, + { + name: 'sync-pools-v3', + interval: every(30, 'seconds'), + }, + { + name: 'sync-join-exits-v3', + interval: every(1, 'minutes'), + }, + { + name: 'sync-swaps-v3', + interval: every(1, 'minutes'), + }, + { + name: 'sync-snapshots-v3', + interval: every(10, 'minutes'), + }, + { + name: 'sync-hook-data', + interval: every(1, 'hours'), + }, + { + name: 'sync-erc4626-unwrap-rate', + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(60, 'minutes') : every(20, 'minutes'), + }, ], }; diff --git a/modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts b/modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts index 606e27be..1553da8f 100644 --- a/modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts +++ b/modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts @@ -72,6 +72,9 @@ export class YbTokensAprService implements PoolAprService { }); for (const pool of poolsWithYbTokensExpanded) { + if (pool.address === '0x43026d483f42fb35efe03c20b251142d022783f2') { + console.log('found'); + } if (!pool.dynamicData) { continue; } diff --git a/modules/token/lib/token-price-handlers/erc4626-price-handler.service.ts b/modules/token/lib/token-price-handlers/erc4626-price-handler.service.ts new file mode 100644 index 00000000..c8f5b61b --- /dev/null +++ b/modules/token/lib/token-price-handlers/erc4626-price-handler.service.ts @@ -0,0 +1,82 @@ +import { TokenPriceHandler } from '../../token-types'; +import { PrismaTokenWithTypes } from '../../../../prisma/prisma-types'; +import { timestampRoundedUpToNearestHour } from '../../../common/time'; +import { prisma } from '../../../../prisma/prisma-client'; +import _ from 'lodash'; +import { tokenAndPrice, updatePrices } from './price-handler-helper'; +import { Chain } from '@prisma/client'; + +export class ERC4626PriceHandlerService implements TokenPriceHandler { + public readonly exitIfFails = false; + public readonly id = 'ERC4626PriceHandlerService'; + + private getAcceptedTokens(tokens: PrismaTokenWithTypes[]): PrismaTokenWithTypes[] { + return tokens.filter((token) => token.types.includes('ERC4626')); + } + + public async updatePricesForTokens(tokens: PrismaTokenWithTypes[]): Promise { + const acceptedTokens = this.getAcceptedTokens(tokens); + + const tokenAndPrices: tokenAndPrice[] = []; + const timestamp = timestampRoundedUpToNearestHour(); + + // Group tokens by chain + const tokensByChain = _.groupBy(acceptedTokens, 'chain'); + + const updatedTokens: PrismaTokenWithTypes[] = []; + for (const chain in tokensByChain) { + // Use existing rates for erc4626 tokens); + + const erc4626TokensForChain = tokensByChain[chain]; + if (!erc4626TokensForChain.length) { + continue; + } + + // Fetch rates for aave tokens + const underlying = erc4626TokensForChain + .map((token) => token.underlyingTokenAddress) + .filter((address) => address !== null); + + const underlyingPrices = await prisma.prismaTokenCurrentPrice.findMany({ + where: { tokenAddress: { in: _.uniq(underlying) }, chain: chain as Chain }, + }); + + const underlyingMap = _.zipObject( + underlyingPrices.map((p) => p.tokenAddress), + underlyingPrices, + ); + + const rateMap = _.zipObject( + erc4626TokensForChain.map((token) => token.address), + erc4626TokensForChain.map((token) => Number(token.unwrapRate)), + ); + + for (const erc4626Token of erc4626TokensForChain) { + const dbToken = acceptedTokens.find((t) => t.address === erc4626Token.address); + const underlying = erc4626Token.underlyingTokenAddress; + if (!dbToken || !underlying || !underlyingMap[underlying]) { + console.error( + `ERC4626PriceHandlerService: Underlying price for ERC4626 ${erc4626Token.symbol} (underlying address: ${erc4626Token.underlyingTokenAddress}) on ${chain} not found`, + ); + continue; + } + try { + const price = Number((rateMap[erc4626Token.address] * underlyingMap[underlying].price).toFixed(2)); + + updatedTokens.push(dbToken); + tokenAndPrices.push({ + address: erc4626Token.address, + chain: erc4626Token.chain, + price, + }); + } catch (e: any) { + console.error('ERC4626 price failed for', erc4626Token.address, chain, e.message); + } + } + } + + await updatePrices(this.id, tokenAndPrices, timestamp); + + return updatedTokens; + } +} diff --git a/modules/token/lib/token-price.service.ts b/modules/token/lib/token-price.service.ts index 70e94829..182f4dba 100644 --- a/modules/token/lib/token-price.service.ts +++ b/modules/token/lib/token-price.service.ts @@ -19,11 +19,13 @@ import { MorphoPriceHandlerService } from './token-price-handlers/morpho-price-h import { RektTokensHandlerService } from './token-price-handlers/rekt-tokens-handler.service'; import config from '../../../config'; import { BeetsPriceHandlerService } from './token-price-handlers/beets-price-handler.service'; +import { ERC4626PriceHandlerService } from './token-price-handlers/erc4626-price-handler.service'; export class TokenPriceService { cache: CacheClass = new Cache(); private readonly priceHandlers: TokenPriceHandler[] = [ new RektTokensHandlerService(), + new ERC4626PriceHandlerService(), new FbeetsPriceHandlerService(), new BeetsPriceHandlerService(), new ClqdrPriceHandlerService(), diff --git a/modules/token/token.service.test.ts b/modules/token/token.service.test.ts index 60834e7f..2fdd1d12 100644 --- a/modules/token/token.service.test.ts +++ b/modules/token/token.service.test.ts @@ -17,14 +17,14 @@ describe('Token service', () => { await tokenService.updateTokenPrices(['SONIC']); const prices = await tokenService.getCurrentTokenPrices(['SONIC']); - console.log(prices.find((price) => price.tokenAddress === '0x2d0e0814e62d80056181f5cd932274405966e4f0')); + console.log(prices.find((price) => price.tokenAddress === '0x541fd749419ca806a8bc7da8ac23d346f2df8b77')); }, 500000); test('sync tokens from pool tokens', async () => { initRequestScopedContext(); - setRequestScopedContextValue('chainId', '34443'); - await tokenService.syncTokenContentData(); - }); + setRequestScopedContextValue('chainId', '146'); + await tokenService.syncTokenContentData('SONIC'); + }, 1000000); test('get tokens', async () => { initRequestScopedContext(); diff --git a/package.json b/package.json index 3cca440c..e2c6f7a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "backend", - "version": "1.29.1", + "version": "1.29.2", "description": "Backend service for Beethoven X and Balancer", "repository": "https://github.com/balancer/backend", "author": "Beethoven X", diff --git a/tasks/index.ts b/tasks/index.ts index 4b7fe3fd..dc7ae013 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -18,6 +18,7 @@ import { chainIdToChain } from '../modules/network/chain-id-to-chain'; import { backsyncSwaps } from './subgraph-syncing/backsync-swaps'; import { poolService } from '../modules/pool/pool.service'; import { initRequestScopedContext, setRequestScopedContextValue } from '../modules/context/request-scoped-context'; +import { tokenService } from '../modules/token/token.service'; // TODO needed? const sftmxController = SftmxController(); @@ -140,6 +141,8 @@ async function run(job: string = process.argv[2], chainId: string = process.argv initRequestScopedContext(); setRequestScopedContextValue('chainId', chainId); return poolService.updatePoolAprs(chain); + } else if (job === 'update-prices') { + return tokenService.updateTokenPrices([chain]); } // Maintenance else if (job === 'sync-fx-quote-tokens') {