diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a0e56c19..87484fad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,10 +14,17 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Prettier Check - uses: actionsx/prettier@v2 + - name: Setup Node.js + uses: actions/setup-node@v3 with: - args: --check . + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Prettier Check + run: npm run format - name: Prettier Result if: failure() diff --git a/packages/api/src/routes/metrics/metricController.ts b/packages/api/src/routes/metrics/metricController.ts index 7197b5c9..9cbe1fcf 100644 --- a/packages/api/src/routes/metrics/metricController.ts +++ b/packages/api/src/routes/metrics/metricController.ts @@ -102,6 +102,10 @@ type HistoricalAggregateRecord = { totalOperators?: number totalAvs?: number } + +type HistoricalAggregateRecordWithStrategies = HistoricalAggregateRecord & { + tvlStrategies: { [strategyName: string]: number } +} type AggregateModelMap = { metricAvsUnit: Prisma.MetricAvsUnit metricOperatorUnit: Prisma.MetricOperatorUnit @@ -568,8 +572,15 @@ export async function getHistoricalAvsAggregate(req: Request, res: Response) { try { const { address } = req.params - const { frequency, variant, startAt, endAt } = queryCheck.data - const data = await doGetHistoricalAvsAggregate(address, startAt, endAt, frequency, variant) + const { frequency, variant, startAt, endAt, withStrategyTvl } = queryCheck.data + const data = await doGetHistoricalAvsAggregate( + address, + startAt, + endAt, + frequency, + variant, + withStrategyTvl + ) res.status(200).send({ data }) } catch (error) { handleAndReturnErrorResponse(req, res, error) @@ -1541,7 +1552,8 @@ async function doGetHistoricalAvsAggregate( startAt: string, endAt: string, frequency: string, - variant: string + variant: string, + withStrategyTvl: boolean = false ) { const startTimestamp = resetTime(new Date(startAt)) const endTimestamp = resetTime(new Date(endAt)) @@ -1549,6 +1561,16 @@ async function doGetHistoricalAvsAggregate( const ethPrices = await fetchCurrentEthPrices() + // Create strategy address-to-symbol mapping for strategy breakdown + let strategyAddressToSymbol: Map | undefined + if (withStrategyTvl) { + const strategiesWithShares = await getStrategiesWithShareUnderlying() + strategyAddressToSymbol = new Map() + for (const strategy of strategiesWithShares) { + strategyAddressToSymbol.set(strategy.strategyAddress.toLowerCase(), strategy.symbol) + } + } + // Fetch initial data for metrics calculation const processMetricUnitData = async () => { // Fetch the timestamp of the first record on or before startTimestamp @@ -1670,7 +1692,7 @@ async function doGetHistoricalAvsAggregate( strategyData = [...strategyData, ...remainingStrategyData] strategyAddresses = [...new Set(strategyData.map((data) => data.strategyAddress))] - const results: HistoricalAggregateRecord[] = [] + const results: (HistoricalAggregateRecord | HistoricalAggregateRecordWithStrategies)[] = [] let currentTimestamp = startTimestamp const offset = getOffsetInMs(frequency) @@ -1720,12 +1742,60 @@ async function doGetHistoricalAvsAggregate( modelNameTvl, ethPrices ) - results.push({ + + // Prepare the base result + const baseResult = { timestamp: new Date(Number(currentTimestamp)).toISOString(), tvlEth, totalStakers, totalOperators - }) + } + + // Conditionally add strategy breakdown + if (withStrategyTvl && strategyAddressToSymbol) { + const tvlStrategies: { [strategyName: string]: number } = {} + + // Calculate strategy-wise TVL for the current timestamp + if (variant === 'cumulative') { + // For cumulative, get the latest TVL value for each strategy + const latestStrategyRecords = new Map() + for (const record of intervalStrategyData) { + const existing = latestStrategyRecords.get(record.strategyAddress) + if (!existing || record.timestamp > existing.timestamp) { + latestStrategyRecords.set(record.strategyAddress, record) + } + } + + for (const [strategyAddress, record] of latestStrategyRecords) { + const ethPrice = ethPrices.get(strategyAddress) || 0 + const strategySymbol = strategyAddressToSymbol.get(strategyAddress) || strategyAddress + tvlStrategies[strategySymbol] = Number(record.tvl) * ethPrice + } + } else { + // For discrete, sum the change values for each strategy + const strategyChanges = new Map() + for (const record of intervalStrategyData) { + const ethPrice = ethPrices.get(record.strategyAddress) || 0 + const changeEth = Number(record.changeTvl) * ethPrice + strategyChanges.set( + record.strategyAddress, + (strategyChanges.get(record.strategyAddress) || 0) + changeEth + ) + } + + for (const [strategyAddress, changeEth] of strategyChanges) { + const strategySymbol = strategyAddressToSymbol.get(strategyAddress) || strategyAddress + tvlStrategies[strategySymbol] = changeEth + } + } + + results.push({ + ...baseResult, + tvlStrategies + } as HistoricalAggregateRecordWithStrategies) + } else { + results.push(baseResult) + } currentTimestamp = nextTimestamp } diff --git a/packages/api/src/schema/zod/schemas/historicalCountQuery.ts b/packages/api/src/schema/zod/schemas/historicalCountQuery.ts index dc31c344..35f565a6 100644 --- a/packages/api/src/schema/zod/schemas/historicalCountQuery.ts +++ b/packages/api/src/schema/zod/schemas/historicalCountQuery.ts @@ -119,7 +119,14 @@ export const HistoricalCountSchema = z ) .default('') .describe('End date in ISO string format') - .openapi({ example: '2024-04-12T08:31:11.000' }) + .openapi({ example: '2024-04-12T08:31:11.000' }), + withStrategyTvl: z + .enum(['true', 'false']) + .optional() + .default('false') + .describe('Include strategy-wise TVL breakdown in response') + .transform((val) => val === 'true') + .openapi({ example: 'false' }) }) .refine( (data) => {