diff --git a/packages/api/src/routes/avs/avsController.ts b/packages/api/src/routes/avs/avsController.ts index 5af37d2f..d668abe4 100644 --- a/packages/api/src/routes/avs/avsController.ts +++ b/packages/api/src/routes/avs/avsController.ts @@ -35,11 +35,7 @@ export async function getAllAVS(req: Request, res: Response) { include: { operator: { include: { - stakers: { - include: { - shares: true - } - } + shares: true } } } @@ -47,26 +43,32 @@ export async function getAllAVS(req: Request, res: Response) { } }) - const data = avsRecords.map((avs) => { - let tvl = 0 - let totalStakers = 0 - const totalOperators = avs.operators.length + const data = await Promise.all( + avsRecords.map(async (avs) => { + let tvl = 0 - avs.operators.map((avsOperator) => { - const operator = withOperatorTvl(avsOperator.operator) + const totalOperators = avs.operators.length + const totalStakers = await prisma.staker.count({ + where: { + operatorAddress: { in: avs.operators.map((o) => o.operatorAddress) } + } + }) - tvl += operator.tvl - totalStakers += operator.totalStakers - }) + avs.operators.map((avsOperator) => { + const operator = withOperatorTvl(avsOperator.operator) - return { - ...avs, - operators: undefined, - tvl, - totalOperators, - totalStakers - } - }) + tvl += operator.tvl + }) + + return { + ...avs, + operators: undefined, + tvl, + totalOperators, + totalStakers + } + }) + ) res.send({ data, @@ -143,11 +145,7 @@ export async function getAVS(req: Request, res: Response) { include: { operator: { include: { - stakers: { - include: { - shares: true - } - } + shares: true } } } @@ -156,31 +154,33 @@ export async function getAVS(req: Request, res: Response) { }) let tvl = 0 - let totalStakers = 0 - const totalOperators = avs.operators.length const sharesMap: IMap = new Map() + const totalOperators = avs.operators.length + const totalStakers = await prisma.staker.count({ + where: { + operatorAddress: { in: avs.operators.map((o) => o.operatorAddress) } + } + }) - avs.operators - .map((avsOperator) => avsOperator.operator) - .map((operator) => withOperatorTvlAndShares(operator)) - .map((operator) => { - operator.shares.map((s) => { - if (!sharesMap.has(s.strategyAddress)) { - sharesMap.set(s.strategyAddress, '0') - } + avs.operators.map((avsOperator) => { + const operator = withOperatorTvlAndShares(avsOperator.operator) - sharesMap.set( - s.strategyAddress, - ( - BigInt(sharesMap.get(s.strategyAddress)) + BigInt(s.shares) - ).toString() - ) - }) + operator.shares.map((s) => { + if (!sharesMap.has(s.strategyAddress)) { + sharesMap.set(s.strategyAddress, '0') + } - tvl += operator.tvl - totalStakers += operator.totalStakers + sharesMap.set( + s.strategyAddress, + ( + BigInt(sharesMap.get(s.strategyAddress)) + BigInt(s.shares) + ).toString() + ) }) + tvl += operator.tvl + }) + res.send({ ...avs, shares: Array.from(sharesMap, ([strategyAddress, shares]) => ({ @@ -281,36 +281,35 @@ export async function getAVSOperators(req: Request, res: Response) { const { id } = req.params const avs = await prisma.avs.findUniqueOrThrow({ where: { address: id }, - include: { operators: true } - }) - - const operatorAddresses = avs.operators - .filter((o) => o.isActive) - .map((o) => o.operatorAddress) - - const operatorsCount = await prisma.operator.count({ - where: { address: { in: operatorAddresses } } + include: { + operators: { + where: { isActive: true } + } + } }) const operatorsRecords = await prisma.operator.findMany({ - where: { address: { in: operatorAddresses } }, + where: { address: { in: avs.operators.map((o) => o.operatorAddress) } }, skip, take, include: { - stakers: { - include: { - shares: true - } - } + shares: true, + stakers: true } }) - const data = operatorsRecords.map((operator) => withOperatorTvl(operator)) + const data = operatorsRecords + .map((operator) => ({ + ...operator, + stakers: undefined, + totalStakers: operator.stakers.length + })) + .map((operator) => withOperatorTvl(operator)) res.send({ data, meta: { - total: operatorsCount, + total: avs.operators.length, skip, take } diff --git a/packages/api/src/routes/operators/operatorController.ts b/packages/api/src/routes/operators/operatorController.ts index 1ad43397..27b430db 100644 --- a/packages/api/src/routes/operators/operatorController.ts +++ b/packages/api/src/routes/operators/operatorController.ts @@ -2,7 +2,7 @@ import type { Request, Response } from 'express' import prisma from '../../utils/prismaClient' import { PaginationQuerySchema } from '../../schema/zod/schemas/paginationQuery' import { handleAndReturnErrorResponse } from '../../schema/errors' -import { StakerStrategyShares } from '@prisma/client' +import { OperatorStrategyShares } from '@prisma/client' import { IMap } from '../../schema/generic' /** @@ -26,17 +26,18 @@ export async function getAllOperators(req: Request, res: Response) { skip, take, include: { - stakers: { - include: { - shares: true - } - } + shares: true, + stakers: true } }) - const operators = operatorRecords.map((operator) => - withOperatorTvl(operator) - ) + const operators = operatorRecords + .map((operator) => ({ + ...operator, + stakers: undefined, + totalStakers: operator.stakers.length + })) + .map((operator) => withOperatorTvl(operator)) res.send({ data: operators, @@ -64,23 +65,22 @@ export async function getOperator(req: Request, res: Response) { const operator = await prisma.operator.findUniqueOrThrow({ where: { address: id }, include: { - stakers: { - include: { - shares: true - } - } + shares: true, + stakers: true } }) let tvl = 0 - operator.stakers.map((staker) => { - staker.shares.map((s) => { - tvl += Number(s.shares) / 1e18 - }) + operator.shares.map((s) => { + tvl += Number(s.shares) / 1e18 }) - res.send(withOperatorTvlAndShares(operator)) + res.send({ + ...withOperatorTvlAndShares(operator), + stakers: undefined, + totalStakers: operator.stakers.length + }) } catch (error) { handleAndReturnErrorResponse(req, res, error) } @@ -88,43 +88,38 @@ export async function getOperator(req: Request, res: Response) { // Helper methods export function withOperatorTvl(operator: { - stakers: { shares: StakerStrategyShares[] }[] + shares: OperatorStrategyShares[] }) { let tvl = 0 - operator.stakers.map((staker) => { - staker.shares.map((s) => { - tvl += Number(s.shares) / 1e18 - }) + operator.shares.map((s) => { + tvl += Number(s.shares) / 1e18 }) return { ...operator, - tvl, - totalStakers: operator.stakers.length, - stakers: undefined + shares: undefined, + tvl } } export function withOperatorTvlAndShares(operator: { - stakers: { shares: StakerStrategyShares[] }[] + shares: OperatorStrategyShares[] }) { let tvl = 0 const sharesMap: IMap = new Map() - operator.stakers.map((staker) => { - staker.shares.map((s) => { - if (!sharesMap.has(s.strategyAddress)) { - sharesMap.set(s.strategyAddress, '0') - } + operator.shares.map((s) => { + if (!sharesMap.has(s.strategyAddress)) { + sharesMap.set(s.strategyAddress, '0') + } - sharesMap.set( - s.strategyAddress, - (BigInt(sharesMap.get(s.strategyAddress)) + BigInt(s.shares)).toString() - ) + sharesMap.set( + s.strategyAddress, + (BigInt(sharesMap.get(s.strategyAddress)) + BigInt(s.shares)).toString() + ) - tvl += Number(s.shares) / 1e18 - }) + tvl += Number(s.shares) / 1e18 }) return { @@ -134,7 +129,6 @@ export function withOperatorTvlAndShares(operator: { strategyAddress, shares })), - tvl, - totalStakers: operator.stakers.length + tvl } } diff --git a/packages/prisma/migrations/20240429123242_remove_operator_shares/migration.sql b/packages/prisma/migrations/20240429123242_remove_operator_shares/migration.sql deleted file mode 100644 index 54740d08..00000000 --- a/packages/prisma/migrations/20240429123242_remove_operator_shares/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - Warnings: - - - You are about to drop the `OperatorStrategyShares` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "OperatorStrategyShares" DROP CONSTRAINT "OperatorStrategyShares_operatorAddress_fkey"; - --- DropTable -DROP TABLE "OperatorStrategyShares"; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 721d37b0..29992a9f 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -54,9 +54,19 @@ model Operator { metadataX String? avs AvsOperator[] + shares OperatorStrategyShares[] stakers Staker[] } +model OperatorStrategyShares { + Operator Operator @relation(fields: [operatorAddress], references: [address]) + operatorAddress String + strategyAddress String + shares String + + @@id([operatorAddress, strategyAddress]) +} + model Staker { address String @id @unique diff --git a/packages/seeder/src/seedOperatorShares.ts b/packages/seeder/src/seedOperatorShares.ts new file mode 100644 index 00000000..9cff0372 --- /dev/null +++ b/packages/seeder/src/seedOperatorShares.ts @@ -0,0 +1,78 @@ +import { getPrismaClient } from './utils/prismaClient' +import { bulkUpdateDbTransactions, IMap } from './utils/seeder' + +export async function seedOperatorShares(operatorAddresses: string[]) { + const prismaClient = getPrismaClient() + + let currentIndex = 0 + let nextIndex = 0 + const totalOperators = await prismaClient.operator.count({ + where: { address: { in: operatorAddresses } } + }) + + // biome-ignore lint/suspicious/noExplicitAny: + const dbTransactions: any[] = [] + + while (nextIndex < totalOperators) { + nextIndex = currentIndex + 12 + + const operators = await prismaClient.operator.findMany({ + where: { address: { in: operatorAddresses } }, + skip: currentIndex, + take: 12, + include: { + stakers: { + include: { + shares: true + } + } + } + }) + + operators.map((operator) => { + const sharesMap: IMap = new Map() + + operator.stakers.map((staker) => { + staker.shares.map((s) => { + if (!sharesMap.has(s.strategyAddress)) { + sharesMap.set(s.strategyAddress, '0') + } + + sharesMap.set( + s.strategyAddress, + ( + BigInt(sharesMap.get(s.strategyAddress)) + BigInt(s.shares) + ).toString() + ) + }) + }) + + for (const [strategyAddress, shares] of sharesMap) { + dbTransactions.push( + prismaClient.operatorStrategyShares.upsert({ + where: { + operatorAddress_strategyAddress: { + operatorAddress: operator.address, + strategyAddress + } + }, + create: { + operatorAddress: operator.address, + strategyAddress, + shares: shares.toString() + }, + update: { + shares: shares.toString() + } + }) + ) + } + }) + + currentIndex = nextIndex + } + + await bulkUpdateDbTransactions(dbTransactions) + + console.log('Seeded operator shares: ', totalOperators) +} diff --git a/packages/seeder/src/seedStakers.ts b/packages/seeder/src/seedStakers.ts index 2c40404d..618e2ef1 100644 --- a/packages/seeder/src/seedStakers.ts +++ b/packages/seeder/src/seedStakers.ts @@ -6,17 +6,14 @@ import { baseBlock, bulkUpdateDbTransactions, fetchLastSyncBlock, + IMap, loopThroughBlocks, saveLastSyncBlock } from './utils/seeder' +import { seedOperatorShares } from './seedOperatorShares' const blockSyncKey = 'lastSyncedBlock_stakers' -// Fix for broken types -interface IMap extends Map { - get(key: K): V -} - export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { console.log('Seeding stakers ...') @@ -30,6 +27,8 @@ export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { } > = new Map() + const impactedOperators = new Set() + const firstBlock = fromBlock ? fromBlock : await fetchLastSyncBlock(blockSyncKey) @@ -75,6 +74,8 @@ export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { log.eventName === 'OperatorSharesIncreased' || log.eventName === 'OperatorSharesDecreased' ) { + impactedOperators.add(operatorAddress) + const strategyAddress = String(log.args.strategy).toLowerCase() const shares = log.args.shares if (!shares) continue @@ -197,6 +198,9 @@ export async function seedStakers(toBlock?: bigint, fromBlock?: bigint) { await bulkUpdateDbTransactions(dbTransactions) + // Update operator shares + await seedOperatorShares(Array.from(impactedOperators)) + // Storing last sycned block await saveLastSyncBlock(blockSyncKey, lastBlock) diff --git a/packages/seeder/src/utils/seeder.ts b/packages/seeder/src/utils/seeder.ts index bc64a3eb..e429b49a 100644 --- a/packages/seeder/src/utils/seeder.ts +++ b/packages/seeder/src/utils/seeder.ts @@ -1,6 +1,11 @@ import { getPrismaClient } from './prismaClient' import { chunkArray } from './array' +// Fix for broken types +export interface IMap extends Map { + get(key: K): V +} + // Base block export const baseBlock = process.env.NETWORK && process.env.NETWORK === 'holesky'