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