@steerprotocol/sdk 1.29.3 → 1.30.0

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,7 +1,8 @@
1
1
  import { createClient } from '@steerprotocol/api-sdk';
2
2
  import { Pool as PoolV3 } from "@uniswap/v3-sdk";
3
3
  import type { Address, Hash, PublicClient, WalletClient } from 'viem';
4
- import { chainIdToName, getBeaconNameByProtocol } from '../const';
4
+ import { chainIdToName, getBeaconNameByProtocol, getProtocolTypeByBeacon } from '../const';
5
+ import { getAmmConfig } from '../const/amm/configs/ammConfig.js';
5
6
  import { getProtocolsForChainId } from '../const/amm/utils/protocol';
6
7
  import { ChainId, MultiPositionManagers, Protocol } from '../const/chain';
7
8
  import { steerSubgraphConfig } from '../const/subgraph';
@@ -202,6 +203,13 @@ export interface VaultFilter {
202
203
  beaconName?: string;
203
204
  }
204
205
 
206
+ // API-specific filter that excludes protocol field
207
+ export interface ApiVaultFilter {
208
+ chainId?: number;
209
+ isActive?: boolean;
210
+ beaconName?: string;
211
+ }
212
+
205
213
  export interface TokenFilter {
206
214
  chainId?: number;
207
215
  symbol?: string;
@@ -378,8 +386,128 @@ export class VaultClient extends SubgraphClient {
378
386
  this.subgraphStudioKey = subgraphStudioKey || '';
379
387
  }
380
388
 
389
+ private normalizeProtocolValue(value: string): string {
390
+ return value.toLowerCase().replace(/[^a-z0-9]/g, '');
391
+ }
392
+
393
+ private resolveProtocolEnum(protocol?: string): Protocol | null {
394
+ if (!protocol) {
395
+ return null;
396
+ }
397
+
398
+ const normalizedProtocol = this.normalizeProtocolValue(protocol);
399
+ const matchedProtocol = Object.values(Protocol).find(
400
+ protocolValue => this.normalizeProtocolValue(protocolValue) === normalizedProtocol
401
+ );
402
+
403
+ return matchedProtocol || null;
404
+ }
405
+
406
+ private getProtocolBeaconNames(protocol?: string): string[] {
407
+ const resolvedProtocol = this.resolveProtocolEnum(protocol);
408
+ if (!resolvedProtocol) {
409
+ return [];
410
+ }
411
+
412
+ const protocolConfig = getAmmConfig(this.subgraphStudioKey)[resolvedProtocol];
413
+ const beaconNames: string[] = [];
414
+
415
+ if (protocolConfig?.beaconContract) {
416
+ beaconNames.push(protocolConfig.beaconContract);
417
+ }
418
+
419
+ if (protocolConfig?.beaconContractSushiManaged) {
420
+ beaconNames.push(protocolConfig.beaconContractSushiManaged);
421
+ }
422
+
423
+ if (resolvedProtocol === Protocol.Blackhole) {
424
+ beaconNames.push(MultiPositionManagers.MultiPositionBlackholeOld);
425
+ }
426
+
427
+ const beaconAlias = getBeaconNameByProtocol(resolvedProtocol);
428
+ if (beaconAlias) {
429
+ beaconNames.push(beaconAlias);
430
+ }
431
+
432
+ return [...new Set(beaconNames.filter(name => name.length > 0))];
433
+ }
434
+
435
+ private getPrimaryProtocolBeaconName(protocol?: string): string | null {
436
+ const resolvedProtocol = this.resolveProtocolEnum(protocol);
437
+ if (!resolvedProtocol) {
438
+ return null;
439
+ }
440
+
441
+ const protocolConfig = getAmmConfig(this.subgraphStudioKey)[resolvedProtocol];
442
+ return protocolConfig?.beaconContract || null;
443
+ }
444
+
445
+ private buildApiVaultFilter(filter?: VaultFilter): ApiVaultFilter | undefined {
446
+ if (!filter) {
447
+ return undefined;
448
+ }
449
+
450
+ const { protocol, ...rest } = filter;
451
+ const apiFilter: ApiVaultFilter = { ...rest };
452
+
453
+ if (!apiFilter.beaconName && protocol) {
454
+ const primaryBeaconName = this.getPrimaryProtocolBeaconName(protocol);
455
+ if (primaryBeaconName) {
456
+ apiFilter.beaconName = primaryBeaconName;
457
+ }
458
+ }
459
+
460
+ return apiFilter;
461
+ }
462
+
463
+ private vaultMatchesProtocolFilter(
464
+ vault: VaultNode,
465
+ protocolFilter: string,
466
+ resolvedProtocol: Protocol | null
467
+ ): boolean {
468
+ const normalizedFilter = this.normalizeProtocolValue(protocolFilter);
469
+ const normalizedBeaconName = this.normalizeProtocolValue(vault.beaconName);
470
+ const resolvedVaultProtocol = getProtocolTypeByBeacon(vault.beaconName);
471
+
472
+ const directCandidates = [vault.protocol, vault.protocolBaseType, resolvedVaultProtocol || ''];
473
+ const hasDirectMatch = directCandidates.some(candidate => {
474
+ if (!candidate) {
475
+ return false;
476
+ }
477
+ return this.normalizeProtocolValue(candidate) === normalizedFilter;
478
+ });
479
+
480
+ if (hasDirectMatch) {
481
+ return true;
482
+ }
483
+
484
+ const expectedBeacon = resolvedProtocol
485
+ ? getBeaconNameByProtocol(resolvedProtocol)
486
+ : protocolFilter;
487
+ const normalizedExpectedBeacon = this.normalizeProtocolValue(expectedBeacon);
488
+
489
+ return normalizedExpectedBeacon.length > 0 && normalizedBeaconName.includes(normalizedExpectedBeacon);
490
+ }
491
+
492
+ private applyProtocolFilter(vaultsConnection: VaultsConnection, protocol?: string): VaultsConnection {
493
+ if (!protocol) {
494
+ return vaultsConnection;
495
+ }
496
+
497
+ const resolvedProtocol = this.resolveProtocolEnum(protocol);
498
+ const filteredEdges = vaultsConnection.edges.filter(edge =>
499
+ this.vaultMatchesProtocolFilter(edge.node, protocol, resolvedProtocol)
500
+ );
501
+
502
+ return {
503
+ ...vaultsConnection,
504
+ edges: filteredEdges
505
+ };
506
+ }
507
+
381
508
  /**
382
509
  * Gets vaults with pagination support
510
+ * Fetches ALL data from both API (database) and subgraph in parallel, merges without duplicates, then paginates
383
511
  * @param filter - Optional filter criteria
384
512
  * @param first - Number of items to fetch (default: 50)
385
513
  * @param after - Cursor for pagination (null for first page)
@@ -418,95 +546,409 @@ export class VaultClient extends SubgraphClient {
418
546
  first: number = 50,
419
547
  after?: string | null
420
548
  ): Promise<SteerResponse<VaultsConnection>> {
421
-
549
+ const apiFilter = this.buildApiVaultFilter(filter);
550
+
551
+ // Fetch ALL vaults from both sources in parallel (no pagination at source level)
552
+ const [apiResult, subgraphResult] = await Promise.allSettled([
553
+ this.getAllVaultsFromApi(apiFilter),
554
+ filter?.chainId !== ChainId.Avalanche ? this.getAllVaultsFromSubgraph(filter) : Promise.reject(new Error('Avalanche not supported'))
555
+ ]);
556
+
557
+ // Extract successful results
558
+ const apiVaults: VaultEdge[] = apiResult.status === 'fulfilled' && apiResult.value.success && apiResult.value.data
559
+ ? apiResult.value.data
560
+ : [];
422
561
 
562
+ const subgraphVaults: VaultEdge[] = subgraphResult.status === 'fulfilled' && subgraphResult.value.success && subgraphResult.value.data
563
+ ? subgraphResult.value.data
564
+ : [];
565
+
566
+ // If both failed, return error
567
+ if (apiVaults.length === 0 && subgraphVaults.length === 0) {
568
+ const apiError = apiResult.status === 'rejected' ? apiResult.reason : null;
569
+ const subgraphError = subgraphResult.status === 'rejected' ? subgraphResult.reason : null;
570
+
571
+ return {
572
+ data: null,
573
+ status: 500,
574
+ success: false,
575
+ error: `Both API and subgraph failed. API: ${apiError?.message || 'Unknown error'}. Subgraph: ${subgraphError?.message || 'Unknown error'}`
576
+ };
577
+ }
423
578
 
424
- if (filter?.chainId !== ChainId.Avalanche) {
425
- try {
426
- // First try the API client
427
- const response = await this.apiClient.vaults({
428
- filter,
429
- first,
430
- after
431
- });
432
-
433
- if (response.data?.vaults) {
434
- // Transform the response to match our interface
435
- const transformedData: VaultsConnection = {
436
- edges: response.data.vaults.edges.map(edge => ({
437
- cursor: edge.cursor,
438
- node: {
439
- id: edge.node.id,
440
- chainId: edge.node.chainId,
441
- vaultAddress: edge.node.vaultAddress,
442
- protocol: edge.node.protocol,
443
- beaconName: edge.node.beaconName,
444
- protocolBaseType: edge.node.protocolBaseType,
445
- name: edge.node.name || '',
446
- feeApr: edge.node.feeApr || undefined,
447
- stakingApr: edge.node.stakingApr || undefined,
448
- merklApr: edge.node.merklApr || undefined,
449
- pool: {
450
- id: edge.node.pool?.id || '',
451
- poolAddress: edge.node.pool?.poolAddress || '',
452
- feeTier: edge.node.pool?.feeTier || '',
453
- tick: undefined, // Not available in API response
454
- liquidity: undefined, // Not available in API response
455
- volumeUSD: undefined, // Not available in API response
456
- totalValueLockedUSD: undefined // Not available in API response
457
- },
458
- token0: {
459
- id: edge.node.token0?.id || '',
460
- symbol: edge.node.token0?.symbol || '',
461
- name: edge.node.token0?.name || '',
462
- decimals: edge.node.token0?.decimals || 0,
463
- address: edge.node.token0?.address || '',
464
- chainId: edge.node.token0?.chainId || 0
465
- },
466
- token1: {
467
- id: edge.node.token1?.id || '',
468
- symbol: edge.node.token1?.symbol || '',
469
- name: edge.node.token1?.name || '',
470
- decimals: edge.node.token1?.decimals || 0,
471
- address: edge.node.token1?.address || '',
472
- chainId: edge.node.token1?.chainId || 0
473
- }
474
- }
475
- })),
476
- pageInfo: {
477
- hasNextPage: response.data.vaults.pageInfo.hasNextPage,
478
- endCursor: response.data.vaults.pageInfo.endCursor ?? null
479
- },
480
- totalCount: response.data.vaults.totalCount
481
- };
579
+ // Merge results and remove duplicates based on vaultAddress
580
+ const mergedVaults = this.mergeVaultResults(apiVaults, subgraphVaults);
581
+
582
+ // Apply protocol filter to merged results
583
+ const filteredVaults = mergedVaults.filter(edge =>
584
+ !filter?.protocol || this.vaultMatchesProtocolFilter(edge.node, filter.protocol, this.resolveProtocolEnum(filter.protocol))
585
+ );
586
+
587
+ // Apply pagination to the complete merged and filtered dataset
588
+ const paginatedVaults = this.paginateVaults(filteredVaults, first, after);
482
589
 
590
+ return {
591
+ data: paginatedVaults,
592
+ status: 200,
593
+ success: true
594
+ };
595
+ }
596
+
597
+ /**
598
+ * Fetches ALL vaults from API (database) by auto-paginating
599
+ * @private
600
+ */
601
+ private async getAllVaultsFromApi(
602
+ apiFilter?: ApiVaultFilter
603
+ ): Promise<SteerResponse<VaultEdge[]>> {
604
+ try {
605
+ const allVaults: VaultEdge[] = [];
606
+ let hasNextPage = true;
607
+ let cursor: string | null = null;
608
+ const batchSize = 100; // Fetch in batches of 100
609
+
610
+ while (hasNextPage) {
611
+ const response = await this.getVaultsFromApi(apiFilter, batchSize, cursor);
612
+
613
+ if (!response.success || !response.data) {
614
+ // If we already have some vaults, return them; otherwise return error
615
+ if (allVaults.length > 0) {
616
+ break;
617
+ }
483
618
  return {
484
- data: transformedData,
619
+ data: null,
485
620
  status: response.status,
486
- success: true
621
+ success: false,
622
+ error: response.error || 'Failed to fetch vaults from API'
487
623
  };
488
624
  }
489
- } catch (apiError) {
490
- console.warn('API client failed, falling back to subgraph:', apiError);
625
+
626
+ allVaults.push(...response.data.edges);
627
+ hasNextPage = response.data.pageInfo.hasNextPage;
628
+ cursor = response.data.pageInfo.endCursor;
491
629
  }
630
+
631
+ return {
632
+ data: allVaults,
633
+ status: 200,
634
+ success: true
635
+ };
636
+ } catch (error) {
637
+ console.warn('Failed to fetch all vaults from API:', error);
638
+ return {
639
+ data: null,
640
+ status: 500,
641
+ success: false,
642
+ error: error instanceof Error ? error.message : 'Failed to fetch all vaults from API'
643
+ };
644
+ }
492
645
  }
493
646
 
494
- // Fallback to subgraph if API fails or returns no data
647
+ /**
648
+ * Fetches vaults from API (database) with pagination
649
+ * @private
650
+ */
651
+ private async getVaultsFromApi(
652
+ apiFilter?: ApiVaultFilter,
653
+ first: number = 50,
654
+ after?: string | null
655
+ ): Promise<SteerResponse<VaultsConnection>> {
495
656
  try {
496
- return await this.getVaultsFromSubgraph(filter, first, after);
497
- } catch (subgraphError) {
498
- console.error('Both API and subgraph failed:', subgraphError);
657
+ const response = await this.apiClient.vaults({
658
+ filter: apiFilter,
659
+ first,
660
+ after
661
+ });
662
+
663
+ if (!response.data?.vaults) {
664
+ return {
665
+ data: null,
666
+ status: response.status,
667
+ success: false,
668
+ error: 'No data returned from API'
669
+ };
670
+ }
671
+
672
+ // Transform the response to match our interface
673
+ const transformedData: VaultsConnection = {
674
+ edges: response.data.vaults.edges.map(edge => ({
675
+ cursor: edge.cursor,
676
+ node: {
677
+ id: edge.node.id,
678
+ chainId: edge.node.chainId,
679
+ vaultAddress: edge.node.vaultAddress,
680
+ protocol: edge.node.protocol,
681
+ beaconName: edge.node.beaconName,
682
+ protocolBaseType: edge.node.protocolBaseType,
683
+ name: edge.node.name || '',
684
+ feeApr: edge.node.feeApr || undefined,
685
+ stakingApr: edge.node.stakingApr || undefined,
686
+ merklApr: edge.node.merklApr || undefined,
687
+ pool: {
688
+ id: edge.node.pool?.id || '',
689
+ poolAddress: edge.node.pool?.poolAddress || '',
690
+ feeTier: edge.node.pool?.feeTier || '',
691
+ tick: undefined, // Not available in API response
692
+ liquidity: undefined, // Not available in API response
693
+ volumeUSD: undefined, // Not available in API response
694
+ totalValueLockedUSD: undefined // Not available in API response
695
+ },
696
+ token0: {
697
+ id: edge.node.token0?.id || '',
698
+ symbol: edge.node.token0?.symbol || '',
699
+ name: edge.node.token0?.name || '',
700
+ decimals: edge.node.token0?.decimals || 0,
701
+ address: edge.node.token0?.address || '',
702
+ chainId: edge.node.token0?.chainId || 0
703
+ },
704
+ token1: {
705
+ id: edge.node.token1?.id || '',
706
+ symbol: edge.node.token1?.symbol || '',
707
+ name: edge.node.token1?.name || '',
708
+ decimals: edge.node.token1?.decimals || 0,
709
+ address: edge.node.token1?.address || '',
710
+ chainId: edge.node.token1?.chainId || 0
711
+ }
712
+ }
713
+ })),
714
+ pageInfo: {
715
+ hasNextPage: response.data.vaults.pageInfo.hasNextPage,
716
+ endCursor: response.data.vaults.pageInfo.endCursor ?? null
717
+ },
718
+ totalCount: response.data.vaults.totalCount
719
+ };
720
+
721
+ return {
722
+ data: transformedData,
723
+ status: response.status,
724
+ success: true
725
+ };
726
+ } catch (error) {
727
+ console.warn('API client failed:', error);
499
728
  return {
500
729
  data: null,
501
730
  status: 500,
502
731
  success: false,
503
- error: subgraphError instanceof Error ? subgraphError.message : 'Both API and subgraph requests failed'
732
+ error: error instanceof Error ? error.message : 'API request failed'
504
733
  };
505
734
  }
506
735
  }
507
736
 
508
737
  /**
509
- * Fallback method to fetch vaults from subgraph
738
+ * Merges vault results from API and subgraph, removing duplicates
739
+ * Prioritizes API data when duplicates are found
740
+ * Generates consistent cursors for the merged dataset
741
+ * @private
742
+ */
743
+ private mergeVaultResults(apiVaults: VaultEdge[], subgraphVaults: VaultEdge[]): VaultEdge[] {
744
+ const vaultMap = new Map<string, VaultEdge>();
745
+
746
+ // Add API vaults first (they take priority)
747
+ apiVaults.forEach(edge => {
748
+ const key = edge.node.vaultAddress.toLowerCase();
749
+ vaultMap.set(key, edge);
750
+ });
751
+
752
+ // Add subgraph vaults only if not already present
753
+ subgraphVaults.forEach(edge => {
754
+ const key = edge.node.vaultAddress.toLowerCase();
755
+ if (!vaultMap.has(key)) {
756
+ vaultMap.set(key, edge);
757
+ } else {
758
+ // Merge additional data from subgraph if available (like pool details)
759
+ const existing = vaultMap.get(key)!;
760
+ const merged: VaultEdge = {
761
+ ...existing,
762
+ node: {
763
+ ...existing.node,
764
+ // Merge pool data - prefer subgraph data for pool details if API doesn't have it
765
+ pool: {
766
+ id: existing.node.pool.id || edge.node.pool.id,
767
+ poolAddress: existing.node.pool.poolAddress || edge.node.pool.poolAddress,
768
+ feeTier: existing.node.pool.feeTier || edge.node.pool.feeTier,
769
+ tick: existing.node.pool.tick || edge.node.pool.tick,
770
+ liquidity: existing.node.pool.liquidity || edge.node.pool.liquidity,
771
+ volumeUSD: existing.node.pool.volumeUSD || edge.node.pool.volumeUSD,
772
+ totalValueLockedUSD: existing.node.pool.totalValueLockedUSD || edge.node.pool.totalValueLockedUSD
773
+ },
774
+ // Prefer API APR data, but use subgraph if API doesn't have it
775
+ feeApr: existing.node.feeApr ?? edge.node.feeApr,
776
+ stakingApr: existing.node.stakingApr ?? edge.node.stakingApr,
777
+ merklApr: existing.node.merklApr ?? edge.node.merklApr
778
+ }
779
+ };
780
+ vaultMap.set(key, merged);
781
+ }
782
+ });
783
+
784
+ // Convert to array and regenerate cursors for consistent pagination
785
+ const mergedArray = Array.from(vaultMap.values());
786
+ return mergedArray.map((edge, index) => ({
787
+ ...edge,
788
+ cursor: `merged_${edge.node.chainId}_${index}_${edge.node.vaultAddress.toLowerCase()}`
789
+ }));
790
+ }
791
+
792
+ /**
793
+ * Applies pagination to vault results
794
+ * @private
795
+ */
796
+ private paginateVaults(vaults: VaultEdge[], first: number, after?: string | null): VaultsConnection {
797
+ let startIndex = 0;
798
+
799
+ // If cursor is provided, find the starting position
800
+ if (after) {
801
+ const cursorIndex = vaults.findIndex(edge => edge.cursor === after);
802
+ if (cursorIndex !== -1) {
803
+ startIndex = cursorIndex + 1;
804
+ }
805
+ }
806
+
807
+ // Get the slice of vaults for this page
808
+ const paginatedEdges = vaults.slice(startIndex, startIndex + first);
809
+ const hasNextPage = startIndex + first < vaults.length;
810
+ const endCursor = paginatedEdges.length > 0 ? paginatedEdges[paginatedEdges.length - 1].cursor : null;
811
+
812
+ return {
813
+ edges: paginatedEdges,
814
+ pageInfo: {
815
+ hasNextPage,
816
+ endCursor
817
+ },
818
+ totalCount: vaults.length
819
+ };
820
+ }
821
+
822
+ /**
823
+ * Fetches ALL vaults from subgraph (no pagination)
824
+ * @param filter - Optional filter criteria
825
+ * @returns Promise resolving to all vaults data from subgraph
826
+ * @private
827
+ */
828
+ private async getAllVaultsFromSubgraph(
829
+ filter?: VaultFilter
830
+ ): Promise<SteerResponse<VaultEdge[]>> {
831
+ try {
832
+ // Extract chainId from filter
833
+ const chainId = filter?.chainId;
834
+ if (!chainId) {
835
+ throw new Error('ChainId is required for subgraph');
836
+ }
837
+
838
+ // Get chain enum from chainId
839
+ const chain = chainIdToName(chainId);
840
+
841
+ if (!chain) {
842
+ throw new Error(`Unsupported chainId: ${chainId}`);
843
+ }
844
+
845
+ // Get subgraph URL for this chain
846
+ const subgraphUrl = steerSubgraphConfig[chain];
847
+ if (!subgraphUrl) {
848
+ throw new Error(`No subgraph configured for chain: ${chain}`);
849
+ }
850
+
851
+ const beaconNames = this.getProtocolBeaconNames(filter?.protocol);
852
+
853
+ if (filter?.beaconName) {
854
+ beaconNames.push(filter.beaconName);
855
+ }
856
+
857
+ const uniqueBeaconNames = [...new Set(beaconNames.filter(name => name.length > 0))];
858
+
859
+ // Fetch all vaults from subgraph
860
+ const subgraphVaults = await this.subgraphVaultClient.getAllVaultsFromSubgraph({
861
+ subgraphUrl,
862
+ chainId,
863
+ showDeprecated: false,
864
+ showCurrentProtocol: uniqueBeaconNames.length > 0,
865
+ beaconNames: uniqueBeaconNames
866
+ });
867
+
868
+ // Get all supported protocols for this chain
869
+ const supportedProtocols = getProtocolsForChainId(chainId, this.subgraphStudioKey);
870
+
871
+ // Fetch APR data for all protocols in parallel
872
+ const aprPromises = supportedProtocols.map(protocol =>
873
+ this.getAprs({ chainId, protocol }).catch(error => {
874
+ console.warn(`Failed to fetch APR for protocol ${protocol}:`, error);
875
+ return { success: false, data: null };
876
+ })
877
+ );
878
+
879
+ const aprResults = await Promise.all(aprPromises);
880
+
881
+ // Create a map of vault address to APR for quick lookup
882
+ const aprMap = new Map<string, number>();
883
+ aprResults && aprResults.forEach(aprResult => {
884
+ if (aprResult.success && aprResult.data) {
885
+ aprResult?.data?.vaults && aprResult.data.vaults.forEach(vaultApr => {
886
+ aprMap.set(vaultApr.vaultAddress.toLowerCase(), vaultApr.apr.apr);
887
+ });
888
+ }
889
+ });
890
+
891
+ // Transform to VaultEdge array with APR data
892
+ const vaultEdges: VaultEdge[] = subgraphVaults.map((vault, index) => ({
893
+ cursor: `subgraph_${chainId}_${index}`,
894
+ node: {
895
+ id: vault.id,
896
+ chainId: chainId,
897
+ vaultAddress: vault.id,
898
+ protocol: vault.beaconName || '',
899
+ beaconName: vault.beaconName || '',
900
+ protocolBaseType: vault.beaconName || '',
901
+ name: `${vault.token0Symbol}/${vault.token1Symbol}`,
902
+ feeApr: aprMap.get(vault.id.toLowerCase()),
903
+ stakingApr: undefined,
904
+ merklApr: undefined,
905
+ pool: {
906
+ id: vault.pool || '',
907
+ poolAddress: vault.pool || '',
908
+ feeTier: vault.feeTier || '',
909
+ tick: undefined,
910
+ liquidity: undefined,
911
+ volumeUSD: undefined,
912
+ totalValueLockedUSD: undefined
913
+ },
914
+ token0: {
915
+ id: vault.token0,
916
+ symbol: vault.token0Symbol,
917
+ name: vault.token0Symbol, // Use symbol as name since subgraph doesn't provide name
918
+ decimals: parseInt(vault.token0Decimals) || 18,
919
+ address: vault.token0,
920
+ chainId: chainId
921
+ },
922
+ token1: {
923
+ id: vault.token1,
924
+ symbol: vault.token1Symbol,
925
+ name: vault.token1Symbol, // Use symbol as name since subgraph doesn't provide name
926
+ decimals: parseInt(vault.token1Decimals) || 18,
927
+ address: vault.token1,
928
+ chainId: chainId
929
+ }
930
+ }
931
+ }));
932
+
933
+ return {
934
+ data: vaultEdges,
935
+ status: 200,
936
+ success: true
937
+ };
938
+
939
+ } catch (error) {
940
+ console.error('Subgraph vault fetch failed:', error);
941
+ return {
942
+ data: null,
943
+ status: 500,
944
+ success: false,
945
+ error: error instanceof Error ? error.message : 'Failed to fetch vaults from subgraph'
946
+ };
947
+ }
948
+ }
949
+
950
+ /**
951
+ * Fallback method to fetch vaults from subgraph with pagination
510
952
  * @param filter - Optional filter criteria
511
953
  * @param first - Number of items to fetch (default: 50)
512
954
  * @param after - Cursor for pagination (null for first page)
@@ -539,28 +981,21 @@ export class VaultClient extends SubgraphClient {
539
981
  throw new Error(`No subgraph configured for chain: ${chain}`);
540
982
  }
541
983
 
542
- const beaconNames = [];
543
-
544
- if (filter?.protocol) {
545
- const beacon = getBeaconNameByProtocol(filter.protocol as Protocol);
546
- beaconNames.push(beacon);
547
-
548
- if (filter?.protocol === Protocol.Blackhole) {
549
- beaconNames.push(MultiPositionManagers.MultiPositionBlackholeOld);
550
- }
551
- }
984
+ const beaconNames = this.getProtocolBeaconNames(filter?.protocol);
552
985
 
553
986
  if (filter?.beaconName) {
554
987
  beaconNames.push(filter.beaconName);
555
988
  }
556
989
 
990
+ const uniqueBeaconNames = [...new Set(beaconNames.filter(name => name.length > 0))];
991
+
557
992
  // Fetch all vaults from subgraph
558
993
  const subgraphVaults = await this.subgraphVaultClient.getAllVaultsFromSubgraph({
559
994
  subgraphUrl,
560
995
  chainId,
561
996
  showDeprecated: false,
562
- showCurrentProtocol: false,
563
- beaconNames: beaconNames
997
+ showCurrentProtocol: uniqueBeaconNames.length > 0,
998
+ beaconNames: uniqueBeaconNames
564
999
  });
565
1000
 
566
1001
  // Get all supported protocols for this chain
@@ -606,8 +1041,10 @@ export class VaultClient extends SubgraphClient {
606
1041
  }));
607
1042
  }
608
1043
 
1044
+ const filteredVaultsConnection = this.applyProtocolFilter(vaultsConnection, filter?.protocol);
1045
+
609
1046
  return {
610
- data: vaultsConnection,
1047
+ data: filteredVaultsConnection,
611
1048
  status: 200,
612
1049
  success: true
613
1050
  };