@steerprotocol/sdk 1.29.3 → 1.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { createClient } from '@steerprotocol/api-sdk';
2
- import { chainIdToName, getBeaconNameByProtocol } from '../const';
2
+ import { chainIdToName, getBeaconNameByProtocol, getProtocolTypeByBeacon } from '../const';
3
+ import { getAmmConfig } from '../const/amm/configs/ammConfig.js';
3
4
  import { getProtocolsForChainId } from '../const/amm/utils/protocol';
4
5
  import { ChainId, MultiPositionManagers, Protocol } from '../const/chain';
5
6
  import { steerSubgraphConfig } from '../const/subgraph';
@@ -145,8 +146,95 @@ export class VaultClient extends SubgraphClient {
145
146
  this.subgraphVaultClient = new SubgraphVaultClient();
146
147
  this.subgraphStudioKey = subgraphStudioKey || '';
147
148
  }
149
+ normalizeProtocolValue(value) {
150
+ return value.toLowerCase().replace(/[^a-z0-9]/g, '');
151
+ }
152
+ resolveProtocolEnum(protocol) {
153
+ if (!protocol) {
154
+ return null;
155
+ }
156
+ const normalizedProtocol = this.normalizeProtocolValue(protocol);
157
+ const matchedProtocol = Object.values(Protocol).find(protocolValue => this.normalizeProtocolValue(protocolValue) === normalizedProtocol);
158
+ return matchedProtocol || null;
159
+ }
160
+ getProtocolBeaconNames(protocol) {
161
+ const resolvedProtocol = this.resolveProtocolEnum(protocol);
162
+ if (!resolvedProtocol) {
163
+ return [];
164
+ }
165
+ const protocolConfig = getAmmConfig(this.subgraphStudioKey)[resolvedProtocol];
166
+ const beaconNames = [];
167
+ if (protocolConfig?.beaconContract) {
168
+ beaconNames.push(protocolConfig.beaconContract);
169
+ }
170
+ if (protocolConfig?.beaconContractSushiManaged) {
171
+ beaconNames.push(protocolConfig.beaconContractSushiManaged);
172
+ }
173
+ if (resolvedProtocol === Protocol.Blackhole) {
174
+ beaconNames.push(MultiPositionManagers.MultiPositionBlackholeOld);
175
+ }
176
+ const beaconAlias = getBeaconNameByProtocol(resolvedProtocol);
177
+ if (beaconAlias) {
178
+ beaconNames.push(beaconAlias);
179
+ }
180
+ return [...new Set(beaconNames.filter(name => name.length > 0))];
181
+ }
182
+ getPrimaryProtocolBeaconName(protocol) {
183
+ const resolvedProtocol = this.resolveProtocolEnum(protocol);
184
+ if (!resolvedProtocol) {
185
+ return null;
186
+ }
187
+ const protocolConfig = getAmmConfig(this.subgraphStudioKey)[resolvedProtocol];
188
+ return protocolConfig?.beaconContract || null;
189
+ }
190
+ buildApiVaultFilter(filter) {
191
+ if (!filter) {
192
+ return undefined;
193
+ }
194
+ const { protocol, ...rest } = filter;
195
+ const apiFilter = { ...rest };
196
+ if (!apiFilter.beaconName && protocol) {
197
+ const primaryBeaconName = this.getPrimaryProtocolBeaconName(protocol);
198
+ if (primaryBeaconName) {
199
+ apiFilter.beaconName = primaryBeaconName;
200
+ }
201
+ }
202
+ return apiFilter;
203
+ }
204
+ vaultMatchesProtocolFilter(vault, protocolFilter, resolvedProtocol) {
205
+ const normalizedFilter = this.normalizeProtocolValue(protocolFilter);
206
+ const normalizedBeaconName = this.normalizeProtocolValue(vault.beaconName);
207
+ const resolvedVaultProtocol = getProtocolTypeByBeacon(vault.beaconName);
208
+ const directCandidates = [vault.protocol, vault.protocolBaseType, resolvedVaultProtocol || ''];
209
+ const hasDirectMatch = directCandidates.some(candidate => {
210
+ if (!candidate) {
211
+ return false;
212
+ }
213
+ return this.normalizeProtocolValue(candidate) === normalizedFilter;
214
+ });
215
+ if (hasDirectMatch) {
216
+ return true;
217
+ }
218
+ const expectedBeacon = resolvedProtocol
219
+ ? getBeaconNameByProtocol(resolvedProtocol)
220
+ : protocolFilter;
221
+ const normalizedExpectedBeacon = this.normalizeProtocolValue(expectedBeacon);
222
+ return normalizedExpectedBeacon.length > 0 && normalizedBeaconName.includes(normalizedExpectedBeacon);
223
+ }
224
+ applyProtocolFilter(vaultsConnection, protocol) {
225
+ if (!protocol) {
226
+ return vaultsConnection;
227
+ }
228
+ const resolvedProtocol = this.resolveProtocolEnum(protocol);
229
+ const filteredEdges = vaultsConnection.edges.filter(edge => this.vaultMatchesProtocolFilter(edge.node, protocol, resolvedProtocol));
230
+ return {
231
+ ...vaultsConnection,
232
+ edges: filteredEdges
233
+ };
234
+ }
148
235
  /**
149
236
  * Gets vaults with pagination support
237
+ * Fetches ALL data from both API (database) and subgraph in parallel, merges without duplicates, then paginates
150
238
  * @param filter - Optional filter criteria
151
239
  * @param first - Number of items to fetch (default: 50)
152
240
  * @param after - Cursor for pagination (null for first page)
@@ -181,90 +269,388 @@ export class VaultClient extends SubgraphClient {
181
269
  * ```
182
270
  */
183
271
  async getVaults(filter, first = 50, after) {
184
- if (filter?.chainId !== ChainId.Avalanche) {
185
- try {
186
- // First try the API client
187
- const response = await this.apiClient.vaults({
188
- filter,
189
- first,
190
- after
191
- });
192
- if (response.data?.vaults) {
193
- // Transform the response to match our interface
194
- const transformedData = {
195
- edges: response.data.vaults.edges.map(edge => ({
196
- cursor: edge.cursor,
197
- node: {
198
- id: edge.node.id,
199
- chainId: edge.node.chainId,
200
- vaultAddress: edge.node.vaultAddress,
201
- protocol: edge.node.protocol,
202
- beaconName: edge.node.beaconName,
203
- protocolBaseType: edge.node.protocolBaseType,
204
- name: edge.node.name || '',
205
- feeApr: edge.node.feeApr || undefined,
206
- stakingApr: edge.node.stakingApr || undefined,
207
- merklApr: edge.node.merklApr || undefined,
208
- pool: {
209
- id: edge.node.pool?.id || '',
210
- poolAddress: edge.node.pool?.poolAddress || '',
211
- feeTier: edge.node.pool?.feeTier || '',
212
- tick: undefined, // Not available in API response
213
- liquidity: undefined, // Not available in API response
214
- volumeUSD: undefined, // Not available in API response
215
- totalValueLockedUSD: undefined // Not available in API response
216
- },
217
- token0: {
218
- id: edge.node.token0?.id || '',
219
- symbol: edge.node.token0?.symbol || '',
220
- name: edge.node.token0?.name || '',
221
- decimals: edge.node.token0?.decimals || 0,
222
- address: edge.node.token0?.address || '',
223
- chainId: edge.node.token0?.chainId || 0
224
- },
225
- token1: {
226
- id: edge.node.token1?.id || '',
227
- symbol: edge.node.token1?.symbol || '',
228
- name: edge.node.token1?.name || '',
229
- decimals: edge.node.token1?.decimals || 0,
230
- address: edge.node.token1?.address || '',
231
- chainId: edge.node.token1?.chainId || 0
232
- }
233
- }
234
- })),
235
- pageInfo: {
236
- hasNextPage: response.data.vaults.pageInfo.hasNextPage,
237
- endCursor: response.data.vaults.pageInfo.endCursor ?? null
238
- },
239
- totalCount: response.data.vaults.totalCount
240
- };
272
+ const apiFilter = this.buildApiVaultFilter(filter);
273
+ // Fetch ALL vaults from both sources in parallel (no pagination at source level)
274
+ const [apiResult, subgraphResult] = await Promise.allSettled([
275
+ this.getAllVaultsFromApi(apiFilter),
276
+ filter?.chainId !== ChainId.Avalanche ? this.getAllVaultsFromSubgraph(filter) : Promise.reject(new Error('Avalanche not supported'))
277
+ ]);
278
+ // Extract successful results
279
+ const apiVaults = apiResult.status === 'fulfilled' && apiResult.value.success && apiResult.value.data
280
+ ? apiResult.value.data
281
+ : [];
282
+ const subgraphVaults = subgraphResult.status === 'fulfilled' && subgraphResult.value.success && subgraphResult.value.data
283
+ ? subgraphResult.value.data
284
+ : [];
285
+ // If both failed, return error
286
+ if (apiVaults.length === 0 && subgraphVaults.length === 0) {
287
+ const apiError = apiResult.status === 'rejected' ? apiResult.reason : null;
288
+ const subgraphError = subgraphResult.status === 'rejected' ? subgraphResult.reason : null;
289
+ return {
290
+ data: null,
291
+ status: 500,
292
+ success: false,
293
+ error: `Both API and subgraph failed. API: ${apiError?.message || 'Unknown error'}. Subgraph: ${subgraphError?.message || 'Unknown error'}`
294
+ };
295
+ }
296
+ // Merge results and remove duplicates based on vaultAddress
297
+ const mergedVaults = this.mergeVaultResults(apiVaults, subgraphVaults);
298
+ // Apply protocol filter to merged results
299
+ const filteredVaults = mergedVaults.filter(edge => !filter?.protocol || this.vaultMatchesProtocolFilter(edge.node, filter.protocol, this.resolveProtocolEnum(filter.protocol)));
300
+ // Apply pagination to the complete merged and filtered dataset
301
+ const paginatedVaults = this.paginateVaults(filteredVaults, first, after);
302
+ return {
303
+ data: paginatedVaults,
304
+ status: 200,
305
+ success: true
306
+ };
307
+ }
308
+ /**
309
+ * Fetches ALL vaults from API (database) by auto-paginating
310
+ * @private
311
+ */
312
+ async getAllVaultsFromApi(apiFilter) {
313
+ try {
314
+ const allVaults = [];
315
+ let hasNextPage = true;
316
+ let cursor = null;
317
+ const batchSize = 100; // Fetch in batches of 100
318
+ while (hasNextPage) {
319
+ const response = await this.getVaultsFromApi(apiFilter, batchSize, cursor);
320
+ if (!response.success || !response.data) {
321
+ // If we already have some vaults, return them; otherwise return error
322
+ if (allVaults.length > 0) {
323
+ break;
324
+ }
241
325
  return {
242
- data: transformedData,
326
+ data: null,
243
327
  status: response.status,
244
- success: true
328
+ success: false,
329
+ error: response.error || 'Failed to fetch vaults from API'
245
330
  };
246
331
  }
332
+ allVaults.push(...response.data.edges);
333
+ hasNextPage = response.data.pageInfo.hasNextPage;
334
+ cursor = response.data.pageInfo.endCursor;
335
+ }
336
+ return {
337
+ data: allVaults,
338
+ status: 200,
339
+ success: true
340
+ };
341
+ }
342
+ catch (error) {
343
+ console.warn('Failed to fetch all vaults from API:', error);
344
+ return {
345
+ data: null,
346
+ status: 500,
347
+ success: false,
348
+ error: error instanceof Error ? error.message : 'Failed to fetch all vaults from API'
349
+ };
350
+ }
351
+ }
352
+ /**
353
+ * Fetches vaults from API (database) with pagination
354
+ * @private
355
+ */
356
+ async getVaultsFromApi(apiFilter, first = 50, after) {
357
+ try {
358
+ const response = await this.apiClient.vaults({
359
+ filter: apiFilter,
360
+ first,
361
+ after
362
+ });
363
+ if (!response.data?.vaults) {
364
+ return {
365
+ data: null,
366
+ status: response.status,
367
+ success: false,
368
+ error: 'No data returned from API'
369
+ };
370
+ }
371
+ // Transform the response to match our interface
372
+ const transformedData = {
373
+ edges: response.data.vaults.edges.map(edge => ({
374
+ cursor: edge.cursor,
375
+ node: {
376
+ id: edge.node.id,
377
+ chainId: edge.node.chainId,
378
+ vaultAddress: edge.node.vaultAddress,
379
+ protocol: edge.node.protocol,
380
+ beaconName: edge.node.beaconName,
381
+ protocolBaseType: edge.node.protocolBaseType,
382
+ name: edge.node.name || '',
383
+ feeApr: edge.node.feeApr || undefined,
384
+ stakingApr: edge.node.stakingApr || undefined,
385
+ merklApr: edge.node.merklApr || undefined,
386
+ pool: {
387
+ id: edge.node.pool?.id || '',
388
+ poolAddress: edge.node.pool?.poolAddress || '',
389
+ feeTier: edge.node.pool?.feeTier || '',
390
+ tick: undefined, // Not available in API response
391
+ liquidity: undefined, // Not available in API response
392
+ volumeUSD: undefined, // Not available in API response
393
+ totalValueLockedUSD: undefined // Not available in API response
394
+ },
395
+ token0: {
396
+ id: edge.node.token0?.id || '',
397
+ symbol: edge.node.token0?.symbol || '',
398
+ name: edge.node.token0?.name || '',
399
+ decimals: edge.node.token0?.decimals || 0,
400
+ address: edge.node.token0?.address || '',
401
+ chainId: edge.node.token0?.chainId || 0
402
+ },
403
+ token1: {
404
+ id: edge.node.token1?.id || '',
405
+ symbol: edge.node.token1?.symbol || '',
406
+ name: edge.node.token1?.name || '',
407
+ decimals: edge.node.token1?.decimals || 0,
408
+ address: edge.node.token1?.address || '',
409
+ chainId: edge.node.token1?.chainId || 0
410
+ }
411
+ }
412
+ })),
413
+ pageInfo: {
414
+ hasNextPage: response.data.vaults.pageInfo.hasNextPage,
415
+ endCursor: response.data.vaults.pageInfo.endCursor ?? null
416
+ },
417
+ totalCount: response.data.vaults.totalCount
418
+ };
419
+ return {
420
+ data: transformedData,
421
+ status: response.status,
422
+ success: true
423
+ };
424
+ }
425
+ catch (error) {
426
+ console.warn('API client failed:', error);
427
+ return {
428
+ data: null,
429
+ status: 500,
430
+ success: false,
431
+ error: error instanceof Error ? error.message : 'API request failed'
432
+ };
433
+ }
434
+ }
435
+ /**
436
+ * Merges vault results from API and subgraph, removing duplicates
437
+ * Prioritizes API data when duplicates are found
438
+ * Generates consistent cursors for the merged dataset
439
+ * @private
440
+ */
441
+ mergeVaultResults(apiVaults, subgraphVaults) {
442
+ const vaultMap = new Map();
443
+ // Add API vaults first (they take priority)
444
+ apiVaults.forEach(edge => {
445
+ const key = edge.node.vaultAddress.toLowerCase();
446
+ vaultMap.set(key, edge);
447
+ });
448
+ // Add subgraph vaults only if not already present
449
+ subgraphVaults.forEach(edge => {
450
+ const key = edge.node.vaultAddress.toLowerCase();
451
+ if (!vaultMap.has(key)) {
452
+ vaultMap.set(key, edge);
453
+ }
454
+ else {
455
+ // Merge additional data from subgraph if available (like pool details)
456
+ const existing = vaultMap.get(key);
457
+ const merged = {
458
+ ...existing,
459
+ node: {
460
+ ...existing.node,
461
+ // Merge pool data - prefer subgraph data for pool details if API doesn't have it
462
+ pool: {
463
+ id: existing.node.pool.id || edge.node.pool.id,
464
+ poolAddress: existing.node.pool.poolAddress || edge.node.pool.poolAddress,
465
+ feeTier: existing.node.pool.feeTier || edge.node.pool.feeTier,
466
+ tick: existing.node.pool.tick || edge.node.pool.tick,
467
+ liquidity: existing.node.pool.liquidity || edge.node.pool.liquidity,
468
+ volumeUSD: existing.node.pool.volumeUSD || edge.node.pool.volumeUSD,
469
+ totalValueLockedUSD: existing.node.pool.totalValueLockedUSD || edge.node.pool.totalValueLockedUSD
470
+ },
471
+ // Prefer API APR data, but use subgraph if API doesn't have it
472
+ feeApr: existing.node.feeApr ?? edge.node.feeApr,
473
+ stakingApr: existing.node.stakingApr ?? edge.node.stakingApr,
474
+ merklApr: existing.node.merklApr ?? edge.node.merklApr,
475
+ // Subgraph-only fields: always take from subgraph when available
476
+ positions: existing.node.positions ?? edge.node.positions,
477
+ tickRange: existing.node.tickRange ?? edge.node.tickRange,
478
+ fees: existing.node.fees ?? edge.node.fees,
479
+ }
480
+ };
481
+ vaultMap.set(key, merged);
247
482
  }
248
- catch (apiError) {
249
- console.warn('API client failed, falling back to subgraph:', apiError);
483
+ });
484
+ // Convert to array and regenerate cursors for consistent pagination
485
+ const mergedArray = Array.from(vaultMap.values());
486
+ return mergedArray.map((edge, index) => ({
487
+ ...edge,
488
+ cursor: `merged_${edge.node.chainId}_${index}_${edge.node.vaultAddress.toLowerCase()}`
489
+ }));
490
+ }
491
+ /**
492
+ * Applies pagination to vault results
493
+ * @private
494
+ */
495
+ paginateVaults(vaults, first, after) {
496
+ let startIndex = 0;
497
+ // If cursor is provided, find the starting position
498
+ if (after) {
499
+ const cursorIndex = vaults.findIndex(edge => edge.cursor === after);
500
+ if (cursorIndex !== -1) {
501
+ startIndex = cursorIndex + 1;
250
502
  }
251
503
  }
252
- // Fallback to subgraph if API fails or returns no data
504
+ // Get the slice of vaults for this page
505
+ const paginatedEdges = vaults.slice(startIndex, startIndex + first);
506
+ const hasNextPage = startIndex + first < vaults.length;
507
+ const endCursor = paginatedEdges.length > 0 ? paginatedEdges[paginatedEdges.length - 1].cursor : null;
508
+ return {
509
+ edges: paginatedEdges,
510
+ pageInfo: {
511
+ hasNextPage,
512
+ endCursor
513
+ },
514
+ totalCount: vaults.length
515
+ };
516
+ }
517
+ /**
518
+ * Fetches ALL vaults from subgraph (no pagination)
519
+ * @param filter - Optional filter criteria
520
+ * @returns Promise resolving to all vaults data from subgraph
521
+ * @private
522
+ */
523
+ async getAllVaultsFromSubgraph(filter) {
253
524
  try {
254
- return await this.getVaultsFromSubgraph(filter, first, after);
525
+ // Extract chainId from filter
526
+ const chainId = filter?.chainId;
527
+ if (!chainId) {
528
+ throw new Error('ChainId is required for subgraph');
529
+ }
530
+ // Get chain enum from chainId
531
+ const chain = chainIdToName(chainId);
532
+ if (!chain) {
533
+ throw new Error(`Unsupported chainId: ${chainId}`);
534
+ }
535
+ // Get subgraph URL for this chain
536
+ const subgraphUrl = steerSubgraphConfig[chain];
537
+ if (!subgraphUrl) {
538
+ throw new Error(`No subgraph configured for chain: ${chain}`);
539
+ }
540
+ const beaconNames = this.getProtocolBeaconNames(filter?.protocol);
541
+ if (filter?.beaconName) {
542
+ beaconNames.push(filter.beaconName);
543
+ }
544
+ const uniqueBeaconNames = [...new Set(beaconNames.filter(name => name.length > 0))];
545
+ // Fetch all vaults from subgraph
546
+ const subgraphVaults = await this.subgraphVaultClient.getAllVaultsFromSubgraph({
547
+ subgraphUrl,
548
+ chainId,
549
+ showDeprecated: false,
550
+ showCurrentProtocol: uniqueBeaconNames.length > 0,
551
+ beaconNames: uniqueBeaconNames
552
+ });
553
+ // Get all supported protocols for this chain
554
+ const supportedProtocols = getProtocolsForChainId(chainId, this.subgraphStudioKey);
555
+ // Fetch APR data for all protocols in parallel
556
+ const aprPromises = supportedProtocols.map(protocol => this.getAprs({ chainId, protocol }).catch(error => {
557
+ console.warn(`Failed to fetch APR for protocol ${protocol}:`, error);
558
+ return { success: false, data: null };
559
+ }));
560
+ const aprResults = await Promise.all(aprPromises);
561
+ // Create a map of vault address to APR for quick lookup
562
+ const aprMap = new Map();
563
+ aprResults && aprResults.forEach(aprResult => {
564
+ if (aprResult.success && aprResult.data) {
565
+ aprResult?.data?.vaults && aprResult.data.vaults.forEach(vaultApr => {
566
+ aprMap.set(vaultApr.vaultAddress.toLowerCase(), vaultApr.apr.apr);
567
+ });
568
+ }
569
+ });
570
+ // Transform to VaultEdge array with APR data
571
+ const vaultEdges = subgraphVaults.map((vault, index) => {
572
+ // Parse positions and compute tick range
573
+ const positions = vault.positions?.map(pos => ({
574
+ id: pos.id,
575
+ upperTick: parseInt(pos.upperTick),
576
+ lowerTick: parseInt(pos.lowerTick),
577
+ relativeWeight: pos.relativeWeight
578
+ }));
579
+ // Compute tick range from positions
580
+ let tickRange;
581
+ if (positions && positions.length > 0) {
582
+ const lowerTicks = positions.map(p => p.lowerTick);
583
+ const upperTicks = positions.map(p => p.upperTick);
584
+ tickRange = {
585
+ minLowerTick: Math.min(...lowerTicks),
586
+ maxUpperTick: Math.max(...upperTicks)
587
+ };
588
+ }
589
+ return {
590
+ cursor: `subgraph_${chainId}_${index}`,
591
+ node: {
592
+ id: vault.id,
593
+ chainId: chainId,
594
+ vaultAddress: vault.id,
595
+ protocol: vault.beaconName || '',
596
+ beaconName: vault.beaconName || '',
597
+ protocolBaseType: vault.beaconName || '',
598
+ name: `${vault.token0Symbol}/${vault.token1Symbol}`,
599
+ feeApr: aprMap.get(vault.id.toLowerCase()),
600
+ stakingApr: undefined,
601
+ merklApr: undefined,
602
+ positions,
603
+ tickRange,
604
+ fees: vault.fees0 && vault.fees1 ? {
605
+ fees0: vault.fees0,
606
+ fees1: vault.fees1
607
+ } : undefined,
608
+ pool: {
609
+ id: vault.pool || '',
610
+ poolAddress: vault.pool || '',
611
+ feeTier: vault.feeTier || '',
612
+ tick: undefined,
613
+ liquidity: undefined,
614
+ volumeUSD: undefined,
615
+ totalValueLockedUSD: undefined
616
+ },
617
+ token0: {
618
+ id: vault.token0,
619
+ symbol: vault.token0Symbol,
620
+ name: vault.token0Symbol, // Use symbol as name since subgraph doesn't provide name
621
+ decimals: parseInt(vault.token0Decimals) || 18,
622
+ address: vault.token0,
623
+ chainId: chainId
624
+ },
625
+ token1: {
626
+ id: vault.token1,
627
+ symbol: vault.token1Symbol,
628
+ name: vault.token1Symbol, // Use symbol as name since subgraph doesn't provide name
629
+ decimals: parseInt(vault.token1Decimals) || 18,
630
+ address: vault.token1,
631
+ chainId: chainId
632
+ }
633
+ }
634
+ };
635
+ });
636
+ return {
637
+ data: vaultEdges,
638
+ status: 200,
639
+ success: true
640
+ };
255
641
  }
256
- catch (subgraphError) {
257
- console.error('Both API and subgraph failed:', subgraphError);
642
+ catch (error) {
643
+ console.error('Subgraph vault fetch failed:', error);
258
644
  return {
259
645
  data: null,
260
646
  status: 500,
261
647
  success: false,
262
- error: subgraphError instanceof Error ? subgraphError.message : 'Both API and subgraph requests failed'
648
+ error: error instanceof Error ? error.message : 'Failed to fetch vaults from subgraph'
263
649
  };
264
650
  }
265
651
  }
266
652
  /**
267
- * Fallback method to fetch vaults from subgraph
653
+ * Fallback method to fetch vaults from subgraph with pagination
268
654
  * @param filter - Optional filter criteria
269
655
  * @param first - Number of items to fetch (default: 50)
270
656
  * @param after - Cursor for pagination (null for first page)
@@ -288,24 +674,18 @@ export class VaultClient extends SubgraphClient {
288
674
  if (!subgraphUrl) {
289
675
  throw new Error(`No subgraph configured for chain: ${chain}`);
290
676
  }
291
- const beaconNames = [];
292
- if (filter?.protocol) {
293
- const beacon = getBeaconNameByProtocol(filter.protocol);
294
- beaconNames.push(beacon);
295
- if (filter?.protocol === Protocol.Blackhole) {
296
- beaconNames.push(MultiPositionManagers.MultiPositionBlackholeOld);
297
- }
298
- }
677
+ const beaconNames = this.getProtocolBeaconNames(filter?.protocol);
299
678
  if (filter?.beaconName) {
300
679
  beaconNames.push(filter.beaconName);
301
680
  }
681
+ const uniqueBeaconNames = [...new Set(beaconNames.filter(name => name.length > 0))];
302
682
  // Fetch all vaults from subgraph
303
683
  const subgraphVaults = await this.subgraphVaultClient.getAllVaultsFromSubgraph({
304
684
  subgraphUrl,
305
685
  chainId,
306
686
  showDeprecated: false,
307
- showCurrentProtocol: false,
308
- beaconNames: beaconNames
687
+ showCurrentProtocol: uniqueBeaconNames.length > 0,
688
+ beaconNames: uniqueBeaconNames
309
689
  });
310
690
  // Get all supported protocols for this chain
311
691
  const supportedProtocols = getProtocolsForChainId(chainId, this.subgraphStudioKey);
@@ -336,8 +716,9 @@ export class VaultClient extends SubgraphClient {
336
716
  }
337
717
  }));
338
718
  }
719
+ const filteredVaultsConnection = this.applyProtocolFilter(vaultsConnection, filter?.protocol);
339
720
  return {
340
- data: vaultsConnection,
721
+ data: filteredVaultsConnection,
341
722
  status: 200,
342
723
  success: true
343
724
  };