aleph-indexer-sdk
Version:
TypeScript SDK for Aleph Indexer GraphQL API
1 lines • 736 kB
Source Map (JSON)
{"version":3,"sources":["../client.ts","../generated/index.ts","../types.ts","../constants.ts","../utils/parse-id.ts","../utils/vault-metrics.ts","../utils/parse.ts"],"sourcesContent":["import { GraphQLClient } from \"graphql-request\";\nimport { getSdk, GetVaultHistoricalFeesQuery } from \"./generated\";\nimport {\n Allocator,\n AllocatorPortfolioSnapshot,\n AllocatorPositionWithVaultData,\n EigenLayerDelegationEvent,\n EigenLayerOperatorStrategyShares,\n EigenLayerStakerDelegation,\n FeesDataPoint,\n NAVDataPoint,\n PositionsByClass,\n PricePerShareDataPoint,\n PricePerShareWithHighWatermarkDataPoint,\n SeriesPosition,\n SeriesPositionWithFeesDto,\n StakerPositionWithVaultData,\n Vault,\n VaultClass,\n VaultClassSeries,\n VaultDepositRequest,\n VaultDepositRequestSettled,\n VaultRedeemRequest,\n VaultRedeemRequestSettled,\n VaultSettledDepositBatch,\n VaultSettledRedeemBatch,\n} from \"./types\";\nimport {\n parseAllocator,\n parseDepositRequest,\n parseDepositRequestSettled,\n parseDepositSettlement,\n parseRedeemRequest,\n parseRedeemRequestSettled,\n parseRedeemSettlement,\n parseSyncDepositToSettled,\n parseSyncRedeemToSettled,\n parseVaultClass,\n parseVaultClassSeries,\n parseVaultData,\n} from \"./utils/parse\";\nimport { idsParser } from \"./utils/parse-id\";\nimport {\n aggregateAllocatorPortfolioSnapshotsByTimestamp,\n aggregateSnapshotsByTimestamp,\n calculateAllocatorPositionTotals,\n calculateAllocatorTotals,\n} from \"./utils/vault-metrics\";\n\ntype FeeSnapshot = GetVaultHistoricalFeesQuery[\"VaultFeesAccumulatedSnapshot\"][number];\nconst DEFAULT_CLASS_ID = 1;\nconst DEFAULT_SERIES_ID = 0;\n\nexport class AlephIndexerClient {\n private sdk: ReturnType<typeof getSdk>;\n\n constructor(url: string) {\n const client = new GraphQLClient(url);\n this.sdk = getSdk(client);\n }\n\n async getAllVaults(): Promise<Vault[]> {\n const result = await this.sdk.GetAllVaults();\n return result.VaultData.map((vault) =>\n parseVaultData(vault, result.VaultClassData, result.VaultClassSeriesData)\n );\n }\n\n async getVaultsByManager({ manager }: { manager: string }): Promise<Vault[]> {\n const result = await this.sdk.GetVaultsByManager({ manager: manager.toLowerCase() });\n return result.VaultData.map((vault) =>\n parseVaultData(vault, result.VaultClassData, result.VaultClassSeriesData)\n );\n }\n\n async getVaultData({ id }: { id: string }): Promise<Vault | null> {\n const normalizedId = idsParser.normalizeVaultId(id);\n const result = await this.sdk.GetVaultData({ id: normalizedId, classId: DEFAULT_CLASS_ID });\n const vault = result.VaultData_by_pk;\n return vault ? parseVaultData(vault, result.VaultClassData, result.VaultClassSeriesData) : null;\n }\n\n async getVaultDepositRequests(vaultId: string, classId?: number): Promise<VaultDepositRequest[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n let results;\n if (classId) {\n results = await this.sdk.GetVaultClassDepositRequests({ vaultId: normalizedVaultId, classId });\n } else {\n results = await this.sdk.GetVaultDepositRequests({ vaultId: normalizedVaultId });\n }\n return results.AlephVault_DepositRequest.map(parseDepositRequest);\n }\n\n async getVaultRedeemRequests(vaultId: string, classId?: number): Promise<VaultRedeemRequest[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n let results;\n if (classId) {\n results = await this.sdk.GetVaultClassRedeemRequests({ vaultId: normalizedVaultId, classId });\n } else {\n results = await this.sdk.GetVaultRedeemRequests({ vaultId: normalizedVaultId });\n }\n return results.AlephVault_RedeemRequest.map(parseRedeemRequest);\n }\n\n async getVaultSettledDepositBatch(\n vaultId: string,\n classId?: number,\n seriesId?: number\n ): Promise<VaultSettledDepositBatch[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n let results;\n if (seriesId && classId) {\n results = await this.sdk.GetVaultClassSeriesSettledDeposits({\n vaultId: normalizedVaultId,\n classId,\n seriesId,\n });\n } else if (classId) {\n results = await this.sdk.GetVaultClassSettledDeposits({\n vaultId: normalizedVaultId,\n classId,\n });\n } else {\n results = await this.sdk.GetVaultSettledDeposits({ vaultId: normalizedVaultId });\n }\n\n if (!results?.AlephVault_SettleDepositBatch) {\n return [];\n }\n return results.AlephVault_SettleDepositBatch.map(parseDepositSettlement);\n }\n\n async getVaultSettledRedeemBatch(vaultId: string, classId?: number): Promise<VaultSettledRedeemBatch[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n let results;\n if (classId) {\n results = await this.sdk.GetVaultClassSettledRedeems({ vaultId: normalizedVaultId, classId });\n } else {\n results = await this.sdk.GetVaultSettledRedeems({ vaultId: normalizedVaultId });\n }\n if (!results?.AlephVault_SettleRedeemBatch) {\n return [];\n }\n return results.AlephVault_SettleRedeemBatch.map(parseRedeemSettlement);\n }\n\n async getVaultRedeemRequestSettled(vaultId: string, classId?: number): Promise<VaultRedeemRequestSettled[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n\n const result =\n classId !== undefined\n ? await this.sdk.GetVaultClassRedeemRequestSettled({ vaultId: normalizedVaultId, classId })\n : await this.sdk.GetVaultRedeemRequestSettled({ vaultId: normalizedVaultId });\n\n const settledRequests = result.AlephVault_RedeemRequestSettled.map(parseRedeemRequestSettled);\n const syncRedeems = result.AlephVault_SyncRedeem.map(parseSyncRedeemToSettled);\n\n return [...settledRequests, ...syncRedeems];\n }\n\n async getVaultDepositRequestSettled(vaultId: string, classId?: number): Promise<VaultDepositRequestSettled[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n\n const result =\n classId !== undefined\n ? await this.sdk.GetVaultClassDepositRequestSettled({ vaultId: normalizedVaultId, classId })\n : await this.sdk.GetVaultDepositRequestSettled({ vaultId: normalizedVaultId });\n\n const settledRequests = result.AlephVault_DepositRequestSettled.map(parseDepositRequestSettled);\n const syncDeposits = result.AlephVault_SyncDeposit.map(parseSyncDepositToSettled);\n\n return [...settledRequests, ...syncDeposits];\n }\n\n async getVaultPricePerShareHistory({\n vaultId,\n classId,\n seriesId,\n }: {\n vaultId: string;\n classId?: number;\n seriesId?: number;\n }): Promise<PricePerShareDataPoint[]> {\n if ((classId !== undefined && classId < 1) || (seriesId !== undefined && seriesId < 0)) {\n return [];\n }\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const result = await this.sdk.GetVaultPricePerShareHistory({\n vaultId: normalizedVaultId,\n classId: classId ?? DEFAULT_CLASS_ID,\n seriesId: seriesId ?? DEFAULT_SERIES_ID,\n });\n return this.processPricePerShareSnapshots(result.VaultClassSeriesSnapshot);\n }\n\n async getVaultStartTimestamp(vaultId: string) {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const result = await this.sdk.GetVaultData({ id: normalizedVaultId, classId: DEFAULT_CLASS_ID });\n return result.VaultData_by_pk?.startTimestamp || 0;\n }\n\n getCurrentTimestamp() {\n return Math.floor(Date.now() / 1000);\n }\n\n getAllocatorStartTimestamp(allocatorAddress?: string) {\n return Math.floor(new Date(0).getTime() / 1000);\n }\n\n async getVaultPricePerShareHistoryRange({\n vaultId,\n startingTimestamp,\n endingTimestamp,\n classId,\n seriesId,\n }: {\n vaultId: string;\n startingTimestamp?: number;\n endingTimestamp?: number;\n classId?: number;\n seriesId?: number;\n }): Promise<PricePerShareDataPoint[]> {\n if ((classId !== undefined && classId < 1) || (seriesId !== undefined && seriesId < 0)) {\n return [];\n }\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const start = startingTimestamp\n ? Math.floor(startingTimestamp / 1000)\n : await this.getVaultStartTimestamp(normalizedVaultId);\n const end = endingTimestamp ? Math.floor(endingTimestamp / 1000) : this.getCurrentTimestamp();\n let result;\n if (seriesId !== undefined && seriesId >= 0 && classId !== undefined && classId >= 1) {\n result = await this.sdk.GetVaultPricePerShareHistoryRangeForClassSeries({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n seriesId: seriesId,\n });\n } else if (classId !== undefined && classId >= 1) {\n result = await this.sdk.GetVaultPricePerShareHistoryRange({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n });\n } else {\n result = await this.sdk.GetVaultPricePerShareHistoryRange({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: DEFAULT_CLASS_ID,\n });\n }\n return this.processPricePerShareSnapshots(result.VaultClassSeriesSnapshot);\n }\n\n async getVaultNAVHistoryRange({\n vaultId,\n startingTimestamp,\n endingTimestamp,\n classId,\n seriesId,\n }: {\n vaultId: string;\n startingTimestamp?: number;\n endingTimestamp?: number;\n classId?: number;\n seriesId?: number;\n }): Promise<NAVDataPoint[]> {\n if ((classId !== undefined && classId < 1) || (seriesId !== undefined && seriesId < 0)) {\n return [];\n }\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const start = startingTimestamp\n ? Math.floor(startingTimestamp / 1000)\n : await this.getVaultStartTimestamp(normalizedVaultId);\n const end = endingTimestamp ? Math.floor(endingTimestamp / 1000) : this.getCurrentTimestamp();\n let result;\n if (seriesId !== undefined && seriesId >= 0 && classId !== undefined && classId >= 1) {\n result = await this.sdk.GetVaultHistoricalNAVForClassSeries({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n seriesId: seriesId,\n });\n } else if (classId !== undefined && classId >= 1) {\n result = await this.sdk.GetVaultHistoricalNAVForClass({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n });\n } else {\n result = await this.sdk.GetVaultHistoricalNAV({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n });\n }\n return this.processNAVSnapshots(result.VaultClassSeriesSnapshot);\n }\n\n async getVaultHistoricalFees({\n vaultId,\n startingTimestamp,\n endingTimestamp,\n classId,\n seriesId,\n }: {\n vaultId: string;\n startingTimestamp?: number;\n endingTimestamp?: number;\n classId?: number;\n seriesId?: number;\n }): Promise<FeesDataPoint[]> {\n if ((classId !== undefined && classId < 1) || (seriesId !== undefined && seriesId < 0)) {\n return [];\n }\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const start = startingTimestamp\n ? Math.floor(startingTimestamp / 1000)\n : await this.getVaultStartTimestamp(normalizedVaultId);\n const end = endingTimestamp ? Math.floor(endingTimestamp / 1000) : this.getCurrentTimestamp();\n\n let result;\n if (seriesId !== undefined && seriesId >= 0 && classId !== undefined && classId >= 1) {\n result = await this.sdk.GetVaultHistoricalFeesForClassSeries({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n seriesId: seriesId,\n });\n } else if (classId !== undefined && classId >= 1) {\n result = await this.sdk.GetVaultHistoricalFeesForClass({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n });\n } else {\n result = await this.sdk.GetVaultHistoricalFees({\n vaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n });\n }\n return this.processFeesSnapshots(result.VaultFeesAccumulatedSnapshot);\n }\n\n private processFeesSnapshots(snapshots: FeeSnapshot[]): FeesDataPoint[] {\n const snapshotsByTime = this.groupSnapshotsByTimestamp(snapshots);\n const history: FeesDataPoint[] = [];\n snapshotsByTime.forEach((seriesSnapshots, timestamp) => {\n const aggregated = this.aggregateFeesSnapshotsByTimestamp(seriesSnapshots);\n const data = aggregated.get(timestamp)!;\n history.push({\n timestamp,\n managementFees: data.managementFees,\n performanceFees: data.performanceFees,\n totalManagementFees: data.totalManagementFees,\n totalPerformanceFees: data.totalPerformanceFees,\n totalFees: data.totalFees,\n seriesBreakdown: data.seriesBreakdown,\n });\n });\n return history.sort((a, b) => parseInt(a.timestamp) - parseInt(b.timestamp));\n }\n\n private aggregateFeesSnapshotsByTimestamp(snapshots: FeeSnapshot[]): Map<string, FeesDataPoint> {\n const aggregated = new Map<string, FeesDataPoint>();\n snapshots.forEach((snapshot) => {\n const timestamp = String(snapshot.timestamp);\n if (!aggregated.has(timestamp)) {\n aggregated.set(timestamp, {\n timestamp,\n managementFees: 0,\n performanceFees: 0,\n totalManagementFees: 0,\n totalPerformanceFees: 0,\n totalFees: 0,\n seriesBreakdown: [],\n });\n }\n const current = aggregated.get(timestamp)!;\n current.managementFees += parseFloat(snapshot.managementFees);\n current.performanceFees += parseFloat(snapshot.performanceFees);\n current.totalManagementFees += parseFloat(snapshot.pendingManagementFees);\n current.totalPerformanceFees += parseFloat(snapshot.pendingPerformanceFees);\n current.totalFees += parseFloat(snapshot.totalPendingFees);\n current.seriesBreakdown.push({\n seriesId: parseInt(String(snapshot.seriesId)),\n classId: parseInt(String(snapshot.classId)),\n managementFees: parseFloat(snapshot.pendingManagementFees),\n performanceFees: parseFloat(snapshot.pendingPerformanceFees),\n totalFees: parseFloat(snapshot.totalPendingFees),\n });\n });\n return aggregated;\n }\n\n async getAllocatorPositions({ allocator }: { allocator: string }): Promise<AllocatorPositionWithVaultData[]> {\n const result = await this.sdk.GetAllocatorPosition({ allocator: allocator.toLowerCase() });\n\n // Get all pending deposits for this allocator\n const allPendingDeposits = result.AlephVault_DepositRequest.filter(\n (request) => request.userWalletAddress === allocator.toLowerCase()\n );\n\n // Group pending deposits by vaultId to find first-time deposits\n const pendingDepositsByVault = allPendingDeposits.reduce(\n (acc, request) => {\n if (!acc[request.vaultId]) {\n acc[request.vaultId] = [];\n }\n acc[request.vaultId].push(request);\n return acc;\n },\n {} as Record<string, typeof allPendingDeposits>\n );\n\n const firstDepositTimestampByVault = Object.entries(pendingDepositsByVault).reduce(\n (acc, [vaultId, deposits]) => {\n const sortedDeposits = deposits.sort((a, b) => parseInt(a.timestamp) - parseInt(b.timestamp));\n acc[vaultId] = new Date(parseInt(sortedDeposits[0]?.timestamp) * 1000).toISOString();\n return acc;\n },\n {} as Record<string, string>\n );\n\n // Get vaultIds that have pending deposits but no position yet\n const existingVaultIds = new Set(result.AllocatorPosition.map((p) => p.vaultId));\n const firstTimeDepositVaultIds = Object.keys(pendingDepositsByVault).filter(\n (vaultId) => !existingVaultIds.has(vaultId)\n );\n\n // Create positions for existing allocator positions\n const existingPositions = result.AllocatorPosition.map((position) => {\n const normalizedPositionAddress = position.allocatorAddress.toLowerCase();\n const pendingDeposits = result.AlephVault_DepositRequest.filter(\n (request) =>\n request.userWalletAddress.toLowerCase() === normalizedPositionAddress &&\n request.vaultId === position.vaultId &&\n request.batchId > position.vaultData?.depositSettleId\n );\n const pendingDepositsAmount = pendingDeposits.reduce((sum, request) => sum + parseFloat(request.amount), 0);\n\n const pendingRedemptions = result.AlephVault_RedeemRequest.filter(\n (request) =>\n request.userWalletAddress.toLowerCase() === normalizedPositionAddress &&\n request.vaultId === position.vaultId &&\n request.batchId > position.vaultData?.redeemSettleId\n );\n const pendingRedemptionsAmount = pendingRedemptions.reduce(\n (sum, request) => sum + parseFloat(request.estimatedAmountToRedeem),\n 0\n );\n\n const vaultSpecificAllocatorShareSeries = result.AllocatorShareSeries.filter(\n (series) => series.vaultId === position.vaultId\n );\n\n const {\n pricePerShare,\n highWaterMark,\n lockInPeriod,\n totalAssets,\n totalShares,\n totalPendingManagementFees,\n totalPendingPerformanceFees,\n totalManagementFeesCollected,\n totalPerformanceFeesCollected,\n } = calculateAllocatorPositionTotals(vaultSpecificAllocatorShareSeries);\n\n // Map vaultSpecificAllocatorShareSeries to SeriesPositionWithFees\n const seriesPositions: SeriesPositionWithFeesDto[] = vaultSpecificAllocatorShareSeries\n .filter((series) => series.isActive)\n .map((series) => SeriesPositionWithFeesDto.create(series));\n\n // Get unique classIds\n const classIds = Array.from(new Set(seriesPositions.map((s) => s.classId))).sort((a, b) => a - b);\n\n return {\n ...position,\n netDeposits: parseFloat(position.netDeposits),\n netRedemptions: parseFloat(position.netRedemptions),\n totalAssets: totalAssets,\n totalShares: totalShares,\n pendingDepositsAmount: pendingDepositsAmount,\n pendingRedemptionsAmount: pendingRedemptionsAmount,\n pendingManagementFees: totalPendingManagementFees,\n pendingPerformanceFees: totalPendingPerformanceFees,\n managementFeesCollected: totalManagementFeesCollected,\n performanceFeesCollected: totalPerformanceFeesCollected,\n totalPendingFees: totalPendingManagementFees + totalPendingPerformanceFees,\n totalCollectedFees: totalManagementFeesCollected + totalPerformanceFeesCollected,\n firstDepositTimestamp: firstDepositTimestampByVault[position.vaultId],\n seriesPositions,\n classIds,\n vaultData: position.vaultData\n ? {\n id: position.vaultId,\n configId: position.vaultData?.configId ?? \"\",\n name: position.vaultData?.name ?? \"\",\n address: idsParser.parseVaultId(position.vaultId).address,\n pricePerShare: parseFloat(pricePerShare ?? \"1\"),\n highWaterMark: parseFloat(highWaterMark ?? \"1\"),\n vaultStartTimestamp: new Date(\n parseInt(position.vaultData?.startTimestamp) * 1000\n ).toISOString(),\n lockInPeriod: lockInPeriod ?? 0,\n underlyingToken: position.vaultData?.underlyingToken,\n underlyingSlashedToken: position.vaultData?.underlyingSlashedToken,\n }\n : undefined,\n };\n });\n\n // Create positions for first-time pending deposits\n const firstTimePositions = firstTimeDepositVaultIds.map((vaultId) => {\n const pendingDeposits = pendingDepositsByVault[vaultId];\n const pendingDepositsAmount = pendingDeposits.reduce((sum, request) => sum + parseFloat(request.amount), 0);\n\n const vaultClassData = pendingDeposits[0]?.vaultClassData;\n\n return {\n id: `${allocator}_${vaultId}`,\n allocatorAddress: allocator,\n vaultId: vaultId,\n netDeposits: 0,\n netRedemptions: 0,\n totalAssets: 0,\n totalShares: 0,\n pendingDepositsAmount,\n pendingRedemptionsAmount: 0,\n pendingManagementFees: 0,\n pendingPerformanceFees: 0,\n managementFeesCollected: 0,\n performanceFeesCollected: 0,\n totalPendingFees: 0,\n totalCollectedFees: 0,\n firstDepositTimestamp: firstDepositTimestampByVault[vaultId],\n seriesPositions: [],\n classIds: vaultClassData?.classId ? [vaultClassData.classId] : [DEFAULT_CLASS_ID],\n vaultData: vaultClassData\n ? {\n id: vaultClassData.id,\n configId: vaultClassData.vaultData?.configId ?? \"\",\n name: vaultClassData.vaultData?.name ?? \"\",\n address: idsParser.parseVaultId(vaultId).address,\n vaultStartTimestamp: new Date(\n parseInt(vaultClassData.vaultData?.startTimestamp) * 1000\n ).toISOString(),\n lockInPeriod: vaultClassData?.lockInPeriod ?? 0,\n pricePerShare: 1,\n highWaterMark: 1,\n underlyingToken: vaultClassData.vaultData?.underlyingToken ?? undefined,\n underlyingSlashedToken: vaultClassData.vaultData?.underlyingSlashedToken ?? undefined,\n }\n : undefined,\n };\n });\n\n return [...existingPositions, ...firstTimePositions];\n }\n\n async getAllocator({\n vaultId,\n allocatorAddress,\n classId,\n seriesId,\n }: {\n vaultId: string;\n allocatorAddress: string;\n classId?: number;\n seriesId?: number;\n }): Promise<Allocator | undefined> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const positionId = `${normalizedVaultId}_${allocatorAddress.toLowerCase()}`;\n const normalizedAllocator = allocatorAddress.toLowerCase();\n let result;\n if (seriesId !== undefined && classId !== undefined) {\n result = await this.sdk.GetAllocatorForClassSeries({\n positionId,\n vaultId: normalizedVaultId,\n allocatorAddress: normalizedAllocator,\n classId,\n seriesId,\n });\n } else if (classId !== undefined) {\n result = await this.sdk.GetAllocatorForClass({\n positionId,\n vaultId: normalizedVaultId,\n allocatorAddress: normalizedAllocator,\n classId,\n });\n } else {\n result = await this.sdk.GetAllocator({\n positionId,\n vaultId: normalizedVaultId,\n allocatorAddress: normalizedAllocator,\n });\n }\n\n if (!result?.AllocatorShareSeries || result?.AllocatorShareSeries.length === 0) return undefined;\n\n const position = result.AllocatorPosition_by_pk;\n const totalFeeShares = result.AlephVault_FeesAccumulated.reduce(\n (sum, fee) => sum + parseFloat(fee.managementFeeSharesToMint) + parseFloat(fee.performanceFeeSharesToMint),\n 0\n );\n\n if (!position) return undefined;\n const firstDepositTimestamp = parseInt(result.AlephVault_DepositRequest[0].timestamp);\n const totalVaultShares =\n result.VaultClassSeriesData.reduce((sum, series) => sum + parseFloat(series.totalShares), 0) -\n totalFeeShares;\n\n const {\n pricePerShare,\n highWaterMark,\n lockInPeriod,\n totalAssets,\n totalShares,\n totalManagementFeePaid,\n totalPerformanceFeePaid,\n positionValue,\n } = calculateAllocatorTotals(result.AllocatorShareSeries);\n\n // Build positionsByClass structure and collect unique IDs\n const positionsByClassMap = new Map<number, SeriesPosition[]>();\n const classIdsSet = new Set<number>();\n const seriesIdsSet = new Set<number>();\n\n result.AllocatorShareSeries.forEach((allocatorSeries) => {\n if (!allocatorSeries.isActive) return;\n\n const classId = parseInt(allocatorSeries.classId);\n const seriesId = parseInt(allocatorSeries.seriesId);\n const shares = parseFloat(allocatorSeries.shares);\n const assets = parseFloat(allocatorSeries.assets);\n const highWaterMark = parseFloat(allocatorSeries.vaultClassSeriesData?.highWaterMark || \"1\");\n const pps = parseFloat(allocatorSeries.vaultClassSeriesData?.pricePerShare || \"1\");\n const entryTimestamp = allocatorSeries.vaultClassSeriesData?.startTimestamp\n ? new Date(parseInt(allocatorSeries.vaultClassSeriesData.startTimestamp) * 1000).toISOString()\n : new Date(0).toISOString();\n\n // Collect unique IDs\n classIdsSet.add(classId);\n seriesIdsSet.add(seriesId);\n\n const seriesPosition: SeriesPosition = {\n seriesId,\n shares,\n assets,\n entryTimestamp,\n pricePerShare: pps,\n highWaterMark: highWaterMark,\n };\n\n if (!positionsByClassMap.has(classId)) {\n positionsByClassMap.set(classId, []);\n }\n positionsByClassMap.get(classId)!.push(seriesPosition);\n });\n\n const positionsByClass: PositionsByClass[] = Array.from(positionsByClassMap.entries()).map(\n ([classId, seriesPositions]) => ({\n classId,\n seriesPositions,\n })\n );\n\n const classIds = Array.from(classIdsSet).sort((a, b) => a - b);\n const seriesIds = Array.from(seriesIdsSet).sort((a, b) => a - b);\n\n return parseAllocator(\n {\n ...position,\n totalAssets: totalAssets.toString(),\n totalShares: totalShares.toString(),\n managementFeePaid: totalManagementFeePaid,\n performanceFeePaid: totalPerformanceFeePaid,\n firstDepositTimestamp,\n },\n {\n totalShares: totalVaultShares.toString(),\n lockInPeriod: lockInPeriod || 0,\n pricePerShare: pricePerShare || \"1\",\n highWaterMark: highWaterMark || \"1\",\n positionValue: positionValue || 0,\n },\n {\n classIds,\n seriesIds,\n positionsByClass,\n }\n );\n }\n\n async getVaultAllocators({\n vaultId,\n classId,\n seriesId,\n }: {\n vaultId: string;\n classId?: number;\n seriesId?: number;\n }): Promise<Allocator[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n let result;\n if (seriesId !== undefined && classId !== undefined) {\n result = await this.sdk.GetVaultAllocatorsForClassSeries({\n vaultId: normalizedVaultId,\n classId,\n seriesId,\n });\n } else if (classId !== undefined) {\n result = await this.sdk.GetVaultAllocatorsForClass({ vaultId: normalizedVaultId, classId });\n } else {\n result = await this.sdk.GetVaultAllocators({ vaultId: normalizedVaultId });\n }\n\n if (!result?.AllocatorPosition || result.AllocatorPosition.length === 0) return [];\n\n const totalFeeShares = result.AlephVault_FeesAccumulated.reduce(\n (sum, fee) => sum + parseFloat(fee.managementFeeSharesToMint) + parseFloat(fee.performanceFeeSharesToMint),\n 0\n );\n\n const totalVaultShares =\n result.VaultClassSeriesData.reduce((sum, series) => sum + parseFloat(series.totalShares), 0) -\n totalFeeShares;\n\n const depositsByAllocator = result.AlephVault_DepositRequest.reduce(\n (acc, deposit) => {\n const normalizedAddress = deposit.userWalletAddress.toLowerCase();\n if (!acc[normalizedAddress]) {\n acc[normalizedAddress] = [];\n }\n acc[normalizedAddress].push(deposit);\n return acc;\n },\n {} as Record<string, typeof result.AlephVault_DepositRequest>\n );\n\n const shareSeriesByAllocator = result.AllocatorShareSeries.reduce(\n (acc, series) => {\n const normalizedAddress = series.allocatorAddress.toLowerCase();\n if (!acc[normalizedAddress]) {\n acc[normalizedAddress] = [];\n }\n acc[normalizedAddress].push(series);\n return acc;\n },\n {} as Record<string, typeof result.AllocatorShareSeries>\n );\n\n const allocators: Allocator[] = result.AllocatorPosition.map((position) => {\n const allocatorAddress = position.allocatorAddress.toLowerCase();\n const allocatorShareSeries = shareSeriesByAllocator[allocatorAddress] || [];\n\n const allocatorDeposits = depositsByAllocator[allocatorAddress] || [];\n const firstDepositTimestamp = allocatorDeposits.length > 0 ? parseInt(allocatorDeposits[0].timestamp) : 0;\n\n const {\n pricePerShare,\n highWaterMark,\n lockInPeriod,\n totalAssets,\n totalShares,\n totalManagementFeePaid,\n totalPerformanceFeePaid,\n positionValue,\n } = calculateAllocatorTotals(allocatorShareSeries);\n\n // Build positionsByClass structure and collect unique IDs\n const positionsByClassMap = new Map<number, SeriesPosition[]>();\n const classIdsSet = new Set<number>();\n const seriesIdsSet = new Set<number>();\n\n allocatorShareSeries.forEach((allocatorSeries) => {\n if (!allocatorSeries.isActive) return;\n\n const classId = parseInt(allocatorSeries.classId);\n const seriesId = parseInt(allocatorSeries.seriesId);\n const shares = parseFloat(allocatorSeries.shares);\n const assets = parseFloat(allocatorSeries.assets);\n const highWaterMark = parseFloat(allocatorSeries.vaultClassSeriesData?.highWaterMark || \"1\");\n const pps = parseFloat(allocatorSeries.vaultClassSeriesData?.pricePerShare || \"1\");\n const entryTimestamp = allocatorSeries.vaultClassSeriesData?.startTimestamp\n ? new Date(parseInt(allocatorSeries.vaultClassSeriesData.startTimestamp) * 1000).toISOString()\n : new Date(0).toISOString();\n\n // Collect unique IDs\n classIdsSet.add(classId);\n seriesIdsSet.add(seriesId);\n\n const seriesPosition: SeriesPosition = {\n seriesId,\n shares,\n assets,\n entryTimestamp,\n pricePerShare: pps,\n highWaterMark: highWaterMark,\n };\n\n if (!positionsByClassMap.has(classId)) {\n positionsByClassMap.set(classId, []);\n }\n positionsByClassMap.get(classId)!.push(seriesPosition);\n });\n\n const positionsByClass: PositionsByClass[] = Array.from(positionsByClassMap.entries()).map(\n ([classId, seriesPositions]) => ({\n classId,\n seriesPositions,\n })\n );\n\n const classIds = Array.from(classIdsSet).sort((a, b) => a - b);\n const seriesIds = Array.from(seriesIdsSet).sort((a, b) => a - b);\n\n return parseAllocator(\n {\n ...position,\n totalAssets: totalAssets.toString(),\n totalShares: totalShares.toString(),\n managementFeePaid: totalManagementFeePaid,\n performanceFeePaid: totalPerformanceFeePaid,\n firstDepositTimestamp,\n },\n {\n totalShares: totalVaultShares.toString(),\n lockInPeriod: lockInPeriod || 0,\n pricePerShare: pricePerShare || \"1\",\n highWaterMark: highWaterMark || \"1\",\n positionValue: positionValue || 0,\n },\n {\n classIds,\n seriesIds,\n positionsByClass,\n }\n );\n });\n\n return allocators;\n }\n\n private processPricePerShareSnapshots(snapshots: any[]): PricePerShareDataPoint[] {\n const snapshotsByTime = this.groupSnapshotsByTimestamp(snapshots);\n\n const history: PricePerShareDataPoint[] = [];\n snapshotsByTime.forEach((seriesSnapshots, timestamp) => {\n const aggregated = aggregateSnapshotsByTimestamp(seriesSnapshots);\n const data = aggregated.get(timestamp)!;\n history.push({\n timestamp,\n pricePerShare: data.totalShares > 0 ? data.totalAssets / data.totalShares : 1.0,\n classIds: data.classIds,\n seriesBreakdown: data.seriesBreakdown,\n });\n });\n\n return history.sort((a, b) => parseInt(a.timestamp) - parseInt(b.timestamp));\n }\n\n private processNAVSnapshots(snapshots: any[]): NAVDataPoint[] {\n const snapshotsByTime = this.groupSnapshotsByTimestamp(snapshots);\n\n const history: NAVDataPoint[] = [];\n snapshotsByTime.forEach((seriesSnapshots, timestamp) => {\n const aggregated = aggregateSnapshotsByTimestamp(seriesSnapshots);\n const data = aggregated.get(timestamp)!;\n history.push({\n timestamp,\n nav: data.totalAssets,\n classIds: data.classIds,\n seriesBreakdown: data.seriesBreakdown,\n });\n });\n\n return history.sort((a, b) => parseInt(a.timestamp) - parseInt(b.timestamp));\n }\n\n private groupSnapshotsByTimestamp(snapshots: any[]) {\n const grouped = new Map<string, typeof snapshots>();\n snapshots.forEach((snapshot) => {\n const timestamp = snapshot.timestamp;\n if (!grouped.has(timestamp)) {\n grouped.set(timestamp, []);\n }\n grouped.get(timestamp)!.push(snapshot);\n });\n return grouped;\n }\n\n async getAllocatorHistoricalPortfolio({\n allocatorAddress,\n startingTimestamp,\n endingTimestamp,\n classId,\n seriesId,\n }: {\n allocatorAddress: string;\n startingTimestamp?: number;\n endingTimestamp?: number;\n classId?: number;\n seriesId?: number;\n }): Promise<AllocatorPortfolioSnapshot[]> {\n const normalizedAllocator = allocatorAddress.toLowerCase();\n const start = startingTimestamp\n ? Math.floor(startingTimestamp / 1000)\n : this.getAllocatorStartTimestamp(allocatorAddress);\n const end = endingTimestamp ? Math.floor(endingTimestamp / 1000) : this.getCurrentTimestamp();\n let result;\n if (seriesId !== undefined && seriesId >= 0 && classId !== undefined && classId >= 1) {\n result = await this.sdk.GetAllocatorPortfolioHistoryForClassSeries({\n allocatorAddress: normalizedAllocator,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n seriesId: seriesId,\n });\n } else if (classId !== undefined && classId >= 1) {\n result = await this.sdk.GetAllocatorPortfolioHistoryForClass({\n allocatorAddress: normalizedAllocator,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n classId: classId,\n });\n } else {\n result = await this.sdk.GetAllocatorPortfolioHistory({\n allocatorAddress: normalizedAllocator,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n });\n }\n\n return this.processAllocatorPortfolioSnapshots(result.AllocatorPositionSnapshot);\n }\n\n private processAllocatorPortfolioSnapshots(snapshots: any[]): AllocatorPortfolioSnapshot[] {\n const aggregatedSnapshots = this.groupSnapshotsByTimestamp(snapshots);\n const history: AllocatorPortfolioSnapshot[] = [];\n aggregatedSnapshots.forEach((seriesSnapshots, timestamp) => {\n const aggregated = aggregateAllocatorPortfolioSnapshotsByTimestamp(seriesSnapshots);\n const data = aggregated.get(timestamp)!;\n history.push({\n timestamp,\n vaultId: seriesSnapshots[0].vaultId,\n allocatorAddress: seriesSnapshots[0].allocatorAddress,\n shares: data.shares,\n assets: data.assets,\n managementFees: data.managementFees,\n performanceFees: data.performanceFees,\n totalFees: data.totalFees,\n netDeposits: parseFloat(seriesSnapshots[0].netDeposits),\n netRedemptions: parseFloat(seriesSnapshots[0].netRedemptions),\n seriesPositions: data.seriesPositions,\n classIds: data.classIds,\n });\n });\n return history;\n }\n\n async getVaultPricePerShareWithHighWatermarkHistoryRange({\n vaultId,\n startingTimestamp,\n endingTimestamp,\n }: {\n vaultId: string;\n startingTimestamp?: number;\n endingTimestamp?: number;\n }): Promise<PricePerShareWithHighWatermarkDataPoint[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const start = startingTimestamp\n ? Math.floor(startingTimestamp / 1000)\n : await this.getVaultStartTimestamp(normalizedVaultId);\n const end = endingTimestamp ? Math.floor(endingTimestamp / 1000) : this.getCurrentTimestamp();\n\n const result = await this.sdk.GetVaultPricePerShareWithHighWatermarkHistoryRange({\n vaultId: normalizedVaultId,\n startingTimestamp: start.toString(),\n endingTimestamp: end.toString(),\n });\n\n const hwmMap = new Map<string, typeof result.AlephVault_NewHighWaterMarkSet>();\n\n result.AlephVault_NewHighWaterMarkSet.forEach((hwm) => {\n const key = `${hwm.seriesId}:${hwm.classId}`;\n if (!hwmMap.has(key)) {\n hwmMap.set(key, []);\n }\n hwmMap.get(key)!.push(hwm);\n });\n\n const history: PricePerShareWithHighWatermarkDataPoint[] = result.VaultClassSeriesSnapshot.map((snapshot) => {\n const snapshotTimestamp = parseInt(snapshot.timestamp);\n const key = `${snapshot.seriesId}:${snapshot.classId}`;\n\n // Find the most recent HWM for this series/class before or at this timestamp\n let highWaterMark = 1.0; // Default starting value\n const relevantHWMs = hwmMap.get(key) || [];\n\n for (const hwm of relevantHWMs) {\n if (hwm.timestamp > snapshotTimestamp) {\n break;\n }\n highWaterMark = parseFloat(hwm.highWaterMark);\n }\n\n return {\n timestamp: snapshot.timestamp,\n pricePerShare: parseFloat(snapshot.pricePerShare),\n highWaterMark,\n classId: parseInt(snapshot.classId),\n seriesId: parseInt(snapshot.seriesId),\n };\n });\n\n return history.sort((a, b) => parseInt(a.timestamp) - parseInt(b.timestamp));\n }\n\n async getVaultClass({ vaultId, classId }: { vaultId: string; classId: number }): Promise<VaultClass | undefined> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const vaultClassId = idsParser.buildShareClassId(normalizedVaultId, classId);\n const result = await this.sdk.GetVaultClass({ vaultClassId, vaultId: normalizedVaultId, classId });\n const allocators: string[] =\n result.VaultClassData_by_pk?.allocatorIds.map((allocatorId) => {\n const parsed = idsParser.parseAllocatorPositionId(allocatorId);\n return parsed.allocatorAddress;\n }) || [];\n\n const { totalAssets, seriesIds } = result.VaultClassSeriesData.reduce(\n (acc, series) => {\n acc.totalAssets += parseFloat(series.totalAssets);\n acc.seriesIds.push(Number(series.seriesId));\n return acc;\n },\n { totalAssets: 0, seriesIds: [] as number[] }\n );\n\n const shareClass = result.VaultClassData_by_pk;\n if (!shareClass) return undefined;\n return parseVaultClass(shareClass, allocators, totalAssets, seriesIds);\n }\n\n async getVaultClasses({ vaultId }: { vaultId: string }): Promise<VaultClass[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const result = await this.sdk.GetVaultClasses({ vaultId: normalizedVaultId });\n\n const dataByClassId = result.VaultClassData.reduce(\n (acc, classData) => {\n const classId = classData.classId;\n acc[classId] = {\n allocators: classData.allocatorIds.map((allocatorId) => {\n const parsed = idsParser.parseAllocatorPositionId(allocatorId);\n return parsed.allocatorAddress;\n }),\n totalAssets: 0,\n seriesIds: [],\n };\n return acc;\n },\n {} as Record<number, { allocators: string[]; totalAssets: number; seriesIds: number[] }>\n );\n\n // Populate totalAssets and seriesIds from VaultClassSeriesData\n result.VaultClassSeriesData.forEach((series) => {\n if (dataByClassId[series.classId]) {\n dataByClassId[series.classId].totalAssets += parseFloat(series.totalAssets);\n dataByClassId[series.classId].seriesIds.push(Number(series.seriesId));\n }\n });\n\n return result.VaultClassData.map((vaultClassData) => {\n return parseVaultClass(\n vaultClassData,\n dataByClassId[vaultClassData.classId]?.allocators || [],\n dataByClassId[vaultClassData.classId]?.totalAssets || 0,\n dataByClassId[vaultClassData.classId]?.seriesIds || []\n );\n });\n }\n\n // Get a specific series by ID\n async getVaultClassSeries({\n vaultId,\n classId,\n seriesId,\n }: {\n vaultId: string;\n classId: number;\n seriesId: number;\n }): Promise<VaultClassSeries | undefined> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const vaultClassSeriesId = idsParser.buildVaultClassSeriesDataId(normalizedVaultId, classId, seriesId);\n const result = await this.sdk.GetVaultClassSeries({ vaultClassSeriesId });\n\n return result.VaultClassSeriesData_by_pk ? parseVaultClassSeries(result.VaultClassSeriesData_by_pk) : undefined;\n }\n\n // Get series for a specific class or all series for a vault\n async getVaultClassSeriesList({\n vaultId,\n classId,\n }: {\n vaultId: string;\n classId?: number;\n }): Promise<VaultClassSeries[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const result = classId\n ? await this.sdk.GetVaultClassAllSeries({ vaultId: normalizedVaultId, classId })\n : await this.sdk.GetVaultAllSeries({ vaultId: normalizedVaultId });\n\n return result?.VaultClassSeriesData?.map((series) => parseVaultClassSeries(series)) ?? [];\n }\n\n // Get series for allocator\n async getVaultAllocatorClassSeries({\n vaultId,\n classId,\n seriesId,\n allocatorAddress,\n }: {\n vaultId: string;\n classId: number;\n seriesId: number;\n allocatorAddress: string;\n }): Promise<VaultClassSeries | undefined> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const allocatorShareSeriesId = idsParser.buildAllocatorShareSeriesId(\n normalizedVaultId,\n allocatorAddress.toLowerCase(),\n classId,\n seriesId\n );\n const result = await this.sdk.GetVaultAllocatorClassSeries({ allocatorShareSeriesId });\n const { AllocatorShareSeries_by_pk: allocatorShareSeries } = result;\n\n if (!allocatorShareSeries || !allocatorShareSeries.vaultClassSeriesData) {\n return undefined;\n }\n\n return parseVaultClassSeries(allocatorShareSeries.vaultClassSeriesData);\n }\n\n // Get series for allocator for all classes or a specific class\n async getVaultAllocatorClassSeriesList({\n vaultId,\n classId,\n allocatorAddress,\n }: {\n vaultId: string;\n classId?: number;\n allocatorAddress: string;\n }): Promise<VaultClassSeries[]> {\n const normalizedVaultId = idsParser.normalizeVaultId(vaultId);\n const normalizedAllocator = allocatorAddress.toLowerCase();\n const result = classId\n ? await this.sdk.GetVaultAllocatorClassAllSeries({\n vaultId: normalizedVaultId,\n classId,\n allocatorAddress: normalizedAllocator,\n })\n