@kehtus/proportion-sdk 0.1.2 → 0.1.4

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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/dist/abi/factory.d.ts +1 -0
  3. package/dist/abi/factory.d.ts.map +1 -0
  4. package/dist/abi/factory.js +1 -0
  5. package/dist/abi/factory.js.map +1 -0
  6. package/dist/abi/forwarder.d.ts +1 -0
  7. package/dist/abi/forwarder.d.ts.map +1 -0
  8. package/dist/abi/forwarder.js +1 -0
  9. package/dist/abi/forwarder.js.map +1 -0
  10. package/dist/abi/fundedAccount.d.ts +1 -0
  11. package/dist/abi/fundedAccount.d.ts.map +1 -0
  12. package/dist/abi/fundedAccount.js +1 -0
  13. package/dist/abi/fundedAccount.js.map +1 -0
  14. package/dist/abi/mainVault.d.ts +198 -16
  15. package/dist/abi/mainVault.d.ts.map +1 -0
  16. package/dist/abi/mainVault.js +253 -19
  17. package/dist/abi/mainVault.js.map +1 -0
  18. package/dist/account.d.ts +3 -2
  19. package/dist/account.d.ts.map +1 -0
  20. package/dist/account.js +46 -43
  21. package/dist/account.js.map +1 -0
  22. package/dist/base.d.ts +9 -0
  23. package/dist/base.d.ts.map +1 -0
  24. package/dist/base.js +19 -0
  25. package/dist/base.js.map +1 -0
  26. package/dist/constants.d.ts +5 -4
  27. package/dist/constants.d.ts.map +1 -0
  28. package/dist/constants.js +5 -4
  29. package/dist/constants.js.map +1 -0
  30. package/dist/factory.d.ts +1 -0
  31. package/dist/factory.d.ts.map +1 -0
  32. package/dist/factory.js +56 -10
  33. package/dist/factory.js.map +1 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/indexer.d.ts +9 -3
  39. package/dist/indexer.d.ts.map +1 -0
  40. package/dist/indexer.js +84 -136
  41. package/dist/indexer.js.map +1 -0
  42. package/dist/sdk.d.ts +1 -0
  43. package/dist/sdk.d.ts.map +1 -0
  44. package/dist/sdk.js +1 -0
  45. package/dist/sdk.js.map +1 -0
  46. package/dist/types.d.ts +5 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +1 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/utils.d.ts +8 -0
  51. package/dist/utils.d.ts.map +1 -0
  52. package/dist/utils.js +10 -0
  53. package/dist/utils.js.map +1 -0
  54. package/dist/vault.d.ts +7 -4
  55. package/dist/vault.d.ts.map +1 -0
  56. package/dist/vault.js +76 -51
  57. package/dist/vault.js.map +1 -0
  58. package/package.json +12 -1
  59. package/arch.md +0 -455
  60. package/dist/__tests__/account.test.d.ts +0 -1
  61. package/dist/__tests__/account.test.js +0 -36
  62. package/dist/__tests__/indexer.test.d.ts +0 -1
  63. package/dist/__tests__/indexer.test.js +0 -230
  64. package/dist/__tests__/lifecycle.test.d.ts +0 -1
  65. package/dist/__tests__/lifecycle.test.js +0 -186
  66. package/dist/__tests__/reads.test.d.ts +0 -1
  67. package/dist/__tests__/reads.test.js +0 -184
  68. package/dist/__tests__/utils.test.d.ts +0 -1
  69. package/dist/__tests__/utils.test.js +0 -185
  70. package/src/__tests__/account.test.ts +0 -40
  71. package/src/__tests__/indexer.test.ts +0 -255
  72. package/src/__tests__/lifecycle.test.ts +0 -231
  73. package/src/__tests__/reads.test.ts +0 -208
  74. package/src/__tests__/utils.test.ts +0 -245
  75. package/src/abi/factory.ts +0 -417
  76. package/src/abi/forwarder.ts +0 -373
  77. package/src/abi/fundedAccount.ts +0 -1296
  78. package/src/abi/mainVault.ts +0 -1852
  79. package/src/account.ts +0 -325
  80. package/src/constants.ts +0 -51
  81. package/src/factory.ts +0 -157
  82. package/src/index.ts +0 -16
  83. package/src/indexer.ts +0 -636
  84. package/src/sdk.ts +0 -93
  85. package/src/types.ts +0 -421
  86. package/src/utils.ts +0 -143
  87. package/src/vault.ts +0 -479
  88. package/tsconfig.json +0 -19
  89. package/vitest.config.ts +0 -8
package/src/indexer.ts DELETED
@@ -1,636 +0,0 @@
1
- import type {
2
- GqlAccount,
3
- GqlAccountEvent,
4
- GqlAllowedPerp,
5
- GqlBuilder,
6
- GqlCheckpoint,
7
- GqlConfigChange,
8
- GqlFactoryConfig,
9
- GqlDelegate,
10
- GqlDeposit,
11
- GqlLiquidityProviderDayData,
12
- GqlLP,
13
- GqlMainVaultConfig,
14
- GqlProfitWithdrawal,
15
- GqlProtocolConfig,
16
- GqlSubscriptionPayment,
17
- GqlVault,
18
- GqlVaultDayData,
19
- GqlWithdrawal,
20
- ConfigChangeFilters,
21
- IndexerAccountStateLabel,
22
- Pagination,
23
- Unsubscribe,
24
- WebSocketConstructor,
25
- WebSocketLike,
26
- } from "./types.js";
27
-
28
- export class IndexerModule {
29
- private ws: WebSocketLike | null = null;
30
- private wsId = 0;
31
- private subscriptions = new Map<number, { query: string; variables?: Record<string, unknown>; cb: (data: unknown) => void }>();
32
- private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
33
- private reconnectDelay = 1000;
34
-
35
- constructor(
36
- private graphqlUrl: string,
37
- private graphqlWsUrl?: string,
38
- private transports: {
39
- fetchFn?: typeof fetch;
40
- webSocketCtor?: WebSocketConstructor;
41
- } = {},
42
- ) {}
43
-
44
- private static readonly PAGE_SIZE = 1000;
45
- private static readonly VAULT_FIELDS = `id address totalDeposits totalWithdrawals totalActivationFees cumulativeActivationFeesToVault totalAllocated totalShares cumulativeProfitShares cumulativeCancelledProfit cumulativeSubscriptionFees cumulativeProtocolFeesRouted cumulativeHypercoreFees cumulativeAccountGains cumulativeAccountLosses cumulativeRescuedFunds totalAccountsActivated totalAccountsActive totalAccountsClosed paused subscriptionFeeMultiplier protocolFeeBps protocolFeeReceiver updatedAt`;
46
- private static readonly ACCOUNT_FIELDS = `id owner builder { id address vaultAddress } vaultAddress factoryAddress state accountSize drawdownBps dailyDrawdownEnabled activationFee highWaterMark profitableDaysCount lastCheckpointDay totalCheckpoints subscriptionPaidUntil subscriptionCancelled pendingSubscriptionFee subscriptionFeeMultiplier dayStartValue apiWallet createdAt activatedAt closingInitiatedAt returnInitiatedAt closedAt returnedAmount breachType breachValue breachFloor closePendingSubFee closePendingProfit lastWithdrawalId updatedAt`;
47
- private static readonly MAIN_VAULT_CONFIG_FIELDS = `id vaultAddress factoryAddress owner pendingOwner relayer paused protocolFeeReceiver feeMultiplier subscriptionFeeMultiplier dailyDdDiscountBps minBuilderShares builderLeverage protocolFeeBps minAccountSize maxAccountSize minDrawdownBps maxDrawdownBps maxUtilizationBps maxSingleAccountBps hypeForSpot maxCap createdAt updatedAt`;
48
- private static readonly FACTORY_CONFIG_FIELDS = `id factoryAddress vaultAddress owner pendingOwner createdAt updatedAt`;
49
- private static readonly CONFIG_CHANGE_FIELDS = `id contractAddress contractType field oldValue newValue blockNumber timestamp txHash`;
50
- private static readonly VAULT_DAY_DATA_FIELDS = `id date dayNumber evmBalance totalAllocated totalAssets totalShares sharePrice dailyDeposits dailyWithdrawals dailyNetLpFlow dailyActivationFees dailyActivationFeesToVault dailyProfitShares dailyCancelledProfit dailySubscriptionFees dailyProtocolFeesRouted dailyHypercoreFees dailyRescuedFunds dailyAccountReturnAmount dailyAccountPrincipalReturned dailyAccountGains dailyAccountLosses dailyPerformancePnl totalAccountsActivated totalAccountsActive totalAccountsClosed accountsActivatedToday accountsClosedToday navChange24h sharePriceChange24h return24hBps paused updatedAt`;
51
- private static readonly LP_DAY_DATA_FIELDS = `id lp { id } date dayNumber shares sharesValue dailyDeposits dailyWithdrawals depositCount withdrawCount`;
52
-
53
- // Explicit vault address always wins. ProtocolConfig is only a convenience default for
54
- // indexer-scoped reads when the caller does not provide one.
55
- private async resolveVaultAddress(vaultAddress?: string): Promise<string | null> {
56
- if (vaultAddress) return vaultAddress.toLowerCase();
57
- const protocol = await this.getProtocolConfig();
58
- return protocol?.vaultAddress?.toLowerCase() ?? null;
59
- }
60
-
61
- // Explicit factory address always wins. ProtocolConfig is only a convenience default for
62
- // indexer-scoped reads when the caller does not provide one.
63
- private async resolveFactoryAddress(factoryAddress?: string): Promise<string | null> {
64
- if (factoryAddress) return factoryAddress.toLowerCase();
65
- const protocol = await this.getProtocolConfig();
66
- return protocol?.factoryAddress?.toLowerCase() ?? null;
67
- }
68
-
69
- // ── GraphQL fetch helper ──────────────────────────
70
-
71
- private async query<T>(query: string, variables?: Record<string, unknown>): Promise<T> {
72
- const fetchFn = this.transports.fetchFn ?? globalThis.fetch;
73
- if (!fetchFn) throw new Error("fetch is not available; pass fetchFn in SDKConfig");
74
-
75
- const res = await fetchFn(this.graphqlUrl, {
76
- method: "POST",
77
- headers: { "Content-Type": "application/json" },
78
- body: JSON.stringify({ query, variables }),
79
- });
80
- if (!res.ok) throw new Error(`GraphQL request failed: ${res.status} ${res.statusText}`);
81
- const json = (await res.json()) as { data?: T; errors?: Array<{ message: string }> };
82
- if (json.errors?.length) throw new Error(`GraphQL error: ${json.errors[0].message}`);
83
- return json.data as T;
84
- }
85
-
86
- // ── VAULT ─────────────────────────────────────────
87
-
88
- async getVault(vaultAddress?: string): Promise<GqlVault | null> {
89
- const resolvedVault = await this.resolveVaultAddress(vaultAddress);
90
- if (!resolvedVault) {
91
- const data = await this.query<{ Vault: GqlVault[] }>(`
92
- query {
93
- Vault(order_by: { updatedAt: desc }, limit: 1) { ${IndexerModule.VAULT_FIELDS} }
94
- }
95
- `);
96
- return data.Vault[0] ?? null;
97
- }
98
-
99
- const data = await this.query<{ Vault: GqlVault[] }>(`
100
- query($id: String!) {
101
- Vault(where: { id: { _eq: $id } }) { ${IndexerModule.VAULT_FIELDS} }
102
- }
103
- `, { id: resolvedVault });
104
- return data.Vault[0] ?? null;
105
- }
106
-
107
- // ── LP ────────────────────────────────────────────
108
-
109
- async getLP(address: string): Promise<GqlLP | null> {
110
- const data = await this.query<{ LiquidityProvider: GqlLP[] }>(`
111
- query($id: String!) { LiquidityProvider(where: { id: { _ilike: $id } }) { id totalDeposited totalWithdrawn depositCount withdrawCount lastActionAt } }
112
- `, { id: address.toLowerCase() });
113
- return data.LiquidityProvider[0] ?? null;
114
- }
115
-
116
- async getLPDeposits(address: string, p?: Pagination): Promise<GqlDeposit[]> {
117
- const data = await this.query<{ VaultDeposit: GqlDeposit[] }>(`
118
- query($lp: String!, $limit: Int, $offset: Int) {
119
- VaultDeposit(where: { lp_id: { _ilike: $lp } }, order_by: { timestamp: desc }, limit: $limit, offset: $offset) {
120
- id lp { id } amount sharesReceived timestamp txHash
121
- }
122
- }
123
- `, { lp: address.toLowerCase(), limit: p?.limit ?? 50, offset: p?.offset ?? 0 });
124
- return data.VaultDeposit;
125
- }
126
-
127
- async getLPWithdrawals(address: string, p?: Pagination): Promise<GqlWithdrawal[]> {
128
- const data = await this.query<{ VaultWithdrawal: GqlWithdrawal[] }>(`
129
- query($lp: String!, $limit: Int, $offset: Int) {
130
- VaultWithdrawal(where: { lp_id: { _ilike: $lp } }, order_by: { timestamp: desc }, limit: $limit, offset: $offset) {
131
- id lp { id } amount sharesBurned timestamp txHash
132
- }
133
- }
134
- `, { lp: address.toLowerCase(), limit: p?.limit ?? 50, offset: p?.offset ?? 0 });
135
- return data.VaultWithdrawal;
136
- }
137
-
138
- // ── BUILDER ───────────────────────────────────────
139
-
140
- async getBuilder(address: string, vaultAddress?: string): Promise<GqlBuilder | null> {
141
- const resolvedVault = await this.resolveVaultAddress(vaultAddress);
142
- if (!resolvedVault) {
143
- const data = await this.query<{ Builder: GqlBuilder[] }>(`
144
- query($address: String!) {
145
- Builder(where: { address: { _ilike: $address } }) {
146
- id
147
- address
148
- vaultAddress
149
- active
150
- totalAccountsActivated
151
- totalAccountsClosed
152
- activeAccounts
153
- registeredAt
154
- deactivatedAt
155
- }
156
- }
157
- `, { address: address.toLowerCase() });
158
- return data.Builder[0] ?? null;
159
- }
160
-
161
- const data = await this.query<{ Builder: GqlBuilder[] }>(`
162
- query($address: String!, $vaultAddress: String) {
163
- Builder(
164
- where: {
165
- address: { _ilike: $address }
166
- vaultAddress: { _ilike: $vaultAddress }
167
- }
168
- ) {
169
- id
170
- address
171
- vaultAddress
172
- active
173
- totalAccountsActivated
174
- totalAccountsClosed
175
- activeAccounts
176
- registeredAt
177
- deactivatedAt
178
- }
179
- }
180
- `, { address: address.toLowerCase(), vaultAddress: resolvedVault });
181
- return data.Builder[0] ?? null;
182
- }
183
-
184
- async getDelegates(builder: string, vaultAddress?: string): Promise<GqlDelegate[]> {
185
- const resolvedVault = await this.resolveVaultAddress(vaultAddress);
186
- if (!resolvedVault) {
187
- const data = await this.query<{ Delegate: GqlDelegate[] }>(`
188
- query($builder: String!) {
189
- Delegate(where: { builderAddress: { _ilike: $builder } }) {
190
- id
191
- vaultAddress
192
- delegateAddress
193
- builderAddress
194
- }
195
- }
196
- `, { builder: builder.toLowerCase() });
197
- return data.Delegate;
198
- }
199
-
200
- const data = await this.query<{ Delegate: GqlDelegate[] }>(`
201
- query($builder: String!, $vaultAddress: String) {
202
- Delegate(
203
- where: {
204
- builderAddress: { _ilike: $builder }
205
- vaultAddress: { _ilike: $vaultAddress }
206
- }
207
- ) {
208
- id
209
- vaultAddress
210
- delegateAddress
211
- builderAddress
212
- }
213
- }
214
- `, { builder: builder.toLowerCase(), vaultAddress: resolvedVault });
215
- return data.Delegate;
216
- }
217
-
218
- // ── ACCOUNTS ──────────────────────────────────────
219
-
220
- async getAccount(address: string): Promise<GqlAccount | null> {
221
- const data = await this.query<{ FundedAccount: GqlAccount[] }>(`
222
- query($id: String!) { FundedAccount(where: { id: { _ilike: $id } }) { ${IndexerModule.ACCOUNT_FIELDS} } }
223
- `, { id: address.toLowerCase() });
224
- return data.FundedAccount[0] ?? null;
225
- }
226
-
227
- async getAccountsByOwner(owner: string): Promise<GqlAccount[]> {
228
- const data = await this.query<{ FundedAccount: GqlAccount[] }>(`
229
- query($owner: String!) { FundedAccount(where: { owner: { _ilike: $owner } }, order_by: { createdAt: desc }) { ${IndexerModule.ACCOUNT_FIELDS} } }
230
- `, { owner: owner.toLowerCase() });
231
- return data.FundedAccount;
232
- }
233
-
234
- async getAccountsByBuilder(builder: string, vaultAddress?: string): Promise<GqlAccount[]> {
235
- const resolvedVault = await this.resolveVaultAddress(vaultAddress);
236
- if (!resolvedVault) {
237
- const data = await this.query<{ FundedAccount: GqlAccount[] }>(`
238
- query($builder: String!) {
239
- FundedAccount(
240
- where: { builder: { address: { _ilike: $builder } } },
241
- order_by: { createdAt: desc }
242
- ) { ${IndexerModule.ACCOUNT_FIELDS} }
243
- }
244
- `, { builder: builder.toLowerCase() });
245
- return data.FundedAccount;
246
- }
247
-
248
- const data = await this.query<{ FundedAccount: GqlAccount[] }>(`
249
- query($builder: String!, $vaultAddress: String) {
250
- FundedAccount(
251
- where: {
252
- builder: {
253
- address: { _ilike: $builder }
254
- vaultAddress: { _ilike: $vaultAddress }
255
- }
256
- },
257
- order_by: { createdAt: desc }
258
- ) { ${IndexerModule.ACCOUNT_FIELDS} }
259
- }
260
- `, { builder: builder.toLowerCase(), vaultAddress: resolvedVault });
261
- return data.FundedAccount;
262
- }
263
-
264
- async getAccountsByState(state: IndexerAccountStateLabel): Promise<GqlAccount[]> {
265
- const data = await this.query<{ FundedAccount: GqlAccount[] }>(`
266
- query($state: accountstate!) { FundedAccount(where: { state: { _eq: $state } }, order_by: { updatedAt: desc }) { ${IndexerModule.ACCOUNT_FIELDS} } }
267
- `, { state });
268
- return data.FundedAccount;
269
- }
270
-
271
- async getAccountsByStates(states: IndexerAccountStateLabel[]): Promise<GqlAccount[]> {
272
- const all: GqlAccount[] = [];
273
- let offset = 0;
274
- while (true) {
275
- const data = await this.query<{ FundedAccount: GqlAccount[] }>(`
276
- query($states: [accountstate!]!, $limit: Int!, $offset: Int!) {
277
- FundedAccount(where: { state: { _in: $states } }, order_by: { updatedAt: desc }, limit: $limit, offset: $offset) { ${IndexerModule.ACCOUNT_FIELDS} }
278
- }
279
- `, { states, limit: IndexerModule.PAGE_SIZE, offset });
280
- all.push(...data.FundedAccount);
281
- if (data.FundedAccount.length < IndexerModule.PAGE_SIZE) break;
282
- offset += IndexerModule.PAGE_SIZE;
283
- }
284
- return all;
285
- }
286
-
287
- async getAccountsUpdatedSince(since: string): Promise<GqlAccount[]> {
288
- const all: GqlAccount[] = [];
289
- let offset = 0;
290
- while (true) {
291
- const data = await this.query<{ FundedAccount: GqlAccount[] }>(`
292
- query($since: numeric!, $limit: Int!, $offset: Int!) {
293
- FundedAccount(where: { updatedAt: { _gt: $since } }, order_by: { updatedAt: asc }, limit: $limit, offset: $offset) { ${IndexerModule.ACCOUNT_FIELDS} }
294
- }
295
- `, { since, limit: IndexerModule.PAGE_SIZE, offset });
296
- all.push(...data.FundedAccount);
297
- if (data.FundedAccount.length < IndexerModule.PAGE_SIZE) break;
298
- offset += IndexerModule.PAGE_SIZE;
299
- }
300
- return all;
301
- }
302
-
303
- // ── HISTORY ───────────────────────────────────────
304
-
305
- async getCheckpoints(account: string, p?: Pagination): Promise<GqlCheckpoint[]> {
306
- const data = await this.query<{ Checkpoint: GqlCheckpoint[] }>(`
307
- query($account: String!, $limit: Int, $offset: Int) {
308
- Checkpoint(where: { account_id: { _ilike: $account } }, order_by: { dayNumber: desc }, limit: $limit, offset: $offset) {
309
- id dayNumber accountValue previousValue profitable profitableDaysCount timestamp
310
- }
311
- }
312
- `, { account: account.toLowerCase(), limit: p?.limit ?? 50, offset: p?.offset ?? 0 });
313
- return data.Checkpoint;
314
- }
315
-
316
- async getWithdrawals(account: string, p?: Pagination): Promise<GqlProfitWithdrawal[]> {
317
- const data = await this.query<{ ProfitWithdrawal: GqlProfitWithdrawal[] }>(`
318
- query($account: String!, $limit: Int, $offset: Int) {
319
- ProfitWithdrawal(where: { account_id: { _ilike: $account } }, order_by: { requestedAt: desc }, limit: $limit, offset: $offset) {
320
- id profit ownerAmount vaultAmount protocolAmount status requestedAt claimedAt cancelledAt
321
- }
322
- }
323
- `, { account: account.toLowerCase(), limit: p?.limit ?? 50, offset: p?.offset ?? 0 });
324
- return data.ProfitWithdrawal;
325
- }
326
-
327
- async getSubscriptionPayments(account: string, p?: Pagination): Promise<GqlSubscriptionPayment[]> {
328
- const data = await this.query<{ SubscriptionPayment: GqlSubscriptionPayment[] }>(`
329
- query($account: String!, $limit: Int, $offset: Int) {
330
- SubscriptionPayment(where: { account_id: { _ilike: $account } }, order_by: { timestamp: desc }, limit: $limit, offset: $offset) {
331
- id amount paidUntil timestamp
332
- }
333
- }
334
- `, { account: account.toLowerCase(), limit: p?.limit ?? 50, offset: p?.offset ?? 0 });
335
- return data.SubscriptionPayment;
336
- }
337
-
338
- async getEvents(account: string, p?: Pagination): Promise<GqlAccountEvent[]> {
339
- const data = await this.query<{ AccountEvent: GqlAccountEvent[] }>(`
340
- query($account: String!, $limit: Int, $offset: Int) {
341
- AccountEvent(where: { account_id: { _ilike: $account } }, order_by: { blockNumber: desc }, limit: $limit, offset: $offset) {
342
- id eventType data timestamp blockNumber txHash
343
- }
344
- }
345
- `, { account: account.toLowerCase(), limit: p?.limit ?? 50, offset: p?.offset ?? 0 });
346
- return data.AccountEvent;
347
- }
348
-
349
- async getEventsByType(type: string, limit?: number): Promise<GqlAccountEvent[]> {
350
- const data = await this.query<{ AccountEvent: GqlAccountEvent[] }>(`
351
- query($type: String!, $limit: Int) {
352
- AccountEvent(where: { eventType: { _eq: $type } }, order_by: { blockNumber: desc }, limit: $limit) {
353
- id eventType data timestamp blockNumber txHash
354
- }
355
- }
356
- `, { type, limit: limit ?? 50 });
357
- return data.AccountEvent;
358
- }
359
-
360
- // ── PROTOCOL CONFIG ─────────────────────────────
361
-
362
- async getProtocolConfig(): Promise<GqlProtocolConfig | null> {
363
- const data = await this.query<{ ProtocolConfig: GqlProtocolConfig[] }>(`{
364
- ProtocolConfig { id vaultAddress factoryAddress updatedAt }
365
- }`);
366
- return data.ProtocolConfig[0] ?? null;
367
- }
368
-
369
- // ── CONFIG ────────────────────────────────────────
370
-
371
- async getMainVaultConfig(vaultAddress?: string): Promise<GqlMainVaultConfig | null> {
372
- const resolvedVault = await this.resolveVaultAddress(vaultAddress);
373
- if (!resolvedVault) {
374
- const data = await this.query<{ MainVaultConfig: GqlMainVaultConfig[] }>(`
375
- query {
376
- MainVaultConfig(order_by: { updatedAt: desc }, limit: 1) { ${IndexerModule.MAIN_VAULT_CONFIG_FIELDS} }
377
- }
378
- `);
379
- return data.MainVaultConfig[0] ?? null;
380
- }
381
-
382
- const data = await this.query<{ MainVaultConfig: GqlMainVaultConfig[] }>(`
383
- query($id: String!) {
384
- MainVaultConfig(where: { id: { _eq: $id } }) { ${IndexerModule.MAIN_VAULT_CONFIG_FIELDS} }
385
- }
386
- `, { id: resolvedVault });
387
- return data.MainVaultConfig[0] ?? null;
388
- }
389
-
390
- async getFactoryConfig(factoryAddress?: string): Promise<GqlFactoryConfig | null> {
391
- const resolvedFactory = await this.resolveFactoryAddress(factoryAddress);
392
- if (!resolvedFactory) {
393
- const data = await this.query<{ FactoryConfig: GqlFactoryConfig[] }>(`
394
- query {
395
- FactoryConfig(order_by: { updatedAt: desc }, limit: 1) { ${IndexerModule.FACTORY_CONFIG_FIELDS} }
396
- }
397
- `);
398
- return data.FactoryConfig[0] ?? null;
399
- }
400
-
401
- const data = await this.query<{ FactoryConfig: GqlFactoryConfig[] }>(`
402
- query($id: String!) {
403
- FactoryConfig(where: { id: { _eq: $id } }) { ${IndexerModule.FACTORY_CONFIG_FIELDS} }
404
- }
405
- `, { id: resolvedFactory });
406
- return data.FactoryConfig[0] ?? null;
407
- }
408
-
409
- async getConfigChanges(filters?: ConfigChangeFilters): Promise<GqlConfigChange[]> {
410
- const variables: Record<string, unknown> = {
411
- limit: filters?.limit ?? 50,
412
- offset: filters?.offset ?? 0,
413
- };
414
- const variableDefs = ["$limit: Int", "$offset: Int"];
415
- const whereClauses: string[] = [];
416
-
417
- if (filters?.contractAddress) {
418
- variableDefs.push("$contractAddress: String!");
419
- whereClauses.push("contractAddress: { _ilike: $contractAddress }");
420
- variables.contractAddress = filters.contractAddress.toLowerCase();
421
- }
422
- if (filters?.contractType) {
423
- variableDefs.push("$contractType: String!");
424
- whereClauses.push("contractType: { _eq: $contractType }");
425
- variables.contractType = filters.contractType;
426
- }
427
- if (filters?.field) {
428
- variableDefs.push("$field: String!");
429
- whereClauses.push("field: { _eq: $field }");
430
- variables.field = filters.field;
431
- }
432
-
433
- const whereBlock = whereClauses.length > 0
434
- ? `where: { ${whereClauses.join(" ")} }`
435
- : "";
436
-
437
- const data = await this.query<{ ConfigChange: GqlConfigChange[] }>(`
438
- query(${variableDefs.join(", ")}) {
439
- ConfigChange(
440
- ${whereBlock}
441
- order_by: [{ timestamp: desc }, { blockNumber: desc }]
442
- limit: $limit
443
- offset: $offset
444
- ) { ${IndexerModule.CONFIG_CHANGE_FIELDS} }
445
- }
446
- `, variables);
447
- return data.ConfigChange;
448
- }
449
-
450
- async getVaultDayData(vaultAddress?: string, p?: Pagination): Promise<GqlVaultDayData[]> {
451
- const resolvedVault = await this.resolveVaultAddress(vaultAddress);
452
- if (!resolvedVault) {
453
- const data = await this.query<{ VaultDayData: GqlVaultDayData[] }>(`
454
- query($limit: Int, $offset: Int) {
455
- VaultDayData(order_by: [{ date: desc }, { updatedAt: desc }], limit: $limit, offset: $offset) {
456
- ${IndexerModule.VAULT_DAY_DATA_FIELDS}
457
- }
458
- }
459
- `, { limit: p?.limit ?? 50, offset: p?.offset ?? 0 });
460
- return data.VaultDayData;
461
- }
462
-
463
- const data = await this.query<{ VaultDayData: GqlVaultDayData[] }>(`
464
- query($vaultAddress: String!, $limit: Int, $offset: Int) {
465
- VaultDayData(
466
- where: { id: { _ilike: $vaultAddress } }
467
- order_by: [{ date: desc }, { updatedAt: desc }]
468
- limit: $limit
469
- offset: $offset
470
- ) { ${IndexerModule.VAULT_DAY_DATA_FIELDS} }
471
- }
472
- `, {
473
- vaultAddress: `${resolvedVault}-%`,
474
- limit: p?.limit ?? 50,
475
- offset: p?.offset ?? 0,
476
- });
477
- return data.VaultDayData;
478
- }
479
-
480
- async getLPDayData(address: string, p?: Pagination): Promise<GqlLiquidityProviderDayData[]> {
481
- const data = await this.query<{ LiquidityProviderDayData: GqlLiquidityProviderDayData[] }>(`
482
- query($lp: String!, $limit: Int, $offset: Int) {
483
- LiquidityProviderDayData(
484
- where: { lp_id: { _ilike: $lp } }
485
- order_by: [{ date: desc }, { dayNumber: desc }]
486
- limit: $limit
487
- offset: $offset
488
- ) { ${IndexerModule.LP_DAY_DATA_FIELDS} }
489
- }
490
- `, {
491
- lp: address.toLowerCase(),
492
- limit: p?.limit ?? 50,
493
- offset: p?.offset ?? 0,
494
- });
495
- return data.LiquidityProviderDayData;
496
- }
497
-
498
- async getAllowedPerps(vaultAddress?: string): Promise<GqlAllowedPerp[]> {
499
- const resolvedVault = await this.resolveVaultAddress(vaultAddress);
500
- if (!resolvedVault) {
501
- const data = await this.query<{ AllowedPerp: GqlAllowedPerp[] }>(`
502
- query {
503
- AllowedPerp(where: { allowed: { _eq: true } }) { id vaultAddress perpIndex allowed }
504
- }
505
- `);
506
- return data.AllowedPerp;
507
- }
508
-
509
- const data = await this.query<{ AllowedPerp: GqlAllowedPerp[] }>(`
510
- query($vaultAddress: String) {
511
- AllowedPerp(
512
- where: {
513
- allowed: { _eq: true }
514
- vaultAddress: { _ilike: $vaultAddress }
515
- }
516
- ) { id vaultAddress perpIndex allowed }
517
- }
518
- `, { vaultAddress: resolvedVault });
519
- return data.AllowedPerp;
520
- }
521
-
522
- // ── SUBSCRIPTIONS (Hasura graphql-transport-ws) ───
523
-
524
- private ensureWs(): WebSocketLike {
525
- const WebSocketCtor = (this.transports.webSocketCtor ?? globalThis.WebSocket) as WebSocketConstructor | undefined;
526
- if (!WebSocketCtor) throw new Error("WebSocket is not available; pass webSocketCtor in SDKConfig");
527
- if (this.ws && this.ws.readyState === WebSocketCtor.OPEN) return this.ws;
528
- if (!this.graphqlWsUrl) throw new Error("graphqlWsUrl required for subscriptions");
529
-
530
- const ws: WebSocketLike = new WebSocketCtor(this.graphqlWsUrl, "graphql-transport-ws");
531
- this.ws = ws;
532
-
533
- ws.onopen = () => {
534
- this.reconnectDelay = 1000;
535
- ws.send(JSON.stringify({ type: "connection_init" }));
536
- };
537
-
538
- ws.onmessage = (event: { data: unknown }) => {
539
- const msg = JSON.parse(String(event.data)) as { type: string; id?: string; payload?: { data?: unknown } };
540
- if (msg.type === "connection_ack") {
541
- for (const [id, sub] of this.subscriptions) {
542
- ws.send(JSON.stringify({
543
- id: String(id),
544
- type: "subscribe",
545
- payload: { query: sub.query, variables: sub.variables },
546
- }));
547
- }
548
- }
549
- if (msg.type === "next" && msg.id && msg.payload?.data) {
550
- const sub = this.subscriptions.get(Number(msg.id));
551
- if (sub) sub.cb(msg.payload.data);
552
- }
553
- };
554
-
555
- ws.onclose = () => {
556
- this.reconnectTimer = setTimeout(() => {
557
- this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
558
- this.ensureWs();
559
- }, this.reconnectDelay);
560
- };
561
-
562
- ws.onerror = () => {
563
- ws.close();
564
- };
565
-
566
- return ws;
567
- }
568
-
569
- private subscribe<T>(query: string, variables: Record<string, unknown> | undefined, cb: (data: T) => void): Unsubscribe {
570
- const id = ++this.wsId;
571
- this.subscriptions.set(id, { query, variables, cb: cb as (data: unknown) => void });
572
-
573
- const ws = this.ensureWs();
574
- const WebSocketCtor = (this.transports.webSocketCtor ?? globalThis.WebSocket) as WebSocketConstructor | undefined;
575
- if (WebSocketCtor && ws.readyState === WebSocketCtor.OPEN) {
576
- ws.send(JSON.stringify({
577
- id: String(id),
578
- type: "subscribe",
579
- payload: { query, variables },
580
- }));
581
- }
582
-
583
- return () => {
584
- this.subscriptions.delete(id);
585
- const WebSocketCtor = (this.transports.webSocketCtor ?? globalThis.WebSocket) as WebSocketConstructor | undefined;
586
- if (this.ws && WebSocketCtor && this.ws.readyState === WebSocketCtor.OPEN) {
587
- this.ws.send(JSON.stringify({ id: String(id), type: "complete" }));
588
- }
589
- };
590
- }
591
-
592
- subscribeActiveAccounts(cb: (accounts: GqlAccount[]) => void): Unsubscribe {
593
- return this.subscribe<{ FundedAccount: GqlAccount[] }>(
594
- `subscription { FundedAccount(where: { state: { _eq: ACTIVE } }) { ${IndexerModule.ACCOUNT_FIELDS} } }`,
595
- undefined,
596
- (data) => cb(data.FundedAccount),
597
- );
598
- }
599
-
600
- subscribeAccount(address: string, cb: (account: GqlAccount) => void): Unsubscribe {
601
- return this.subscribe<{ FundedAccount: GqlAccount[] }>(
602
- `subscription($id: String!) { FundedAccount(where: { id: { _ilike: $id } }) { ${IndexerModule.ACCOUNT_FIELDS} } }`,
603
- { id: address.toLowerCase() },
604
- (data) => { if (data.FundedAccount[0]) cb(data.FundedAccount[0]); },
605
- );
606
- }
607
-
608
- subscribeAccountEvents(address: string, cb: (events: GqlAccountEvent[]) => void): Unsubscribe {
609
- return this.subscribe<{ AccountEvent: GqlAccountEvent[] }>(
610
- `subscription($account: String!) { AccountEvent(where: { account_id: { _ilike: $account } }, order_by: { blockNumber: desc }, limit: 10) { id eventType data timestamp blockNumber txHash } }`,
611
- { account: address.toLowerCase() },
612
- (data) => cb(data.AccountEvent),
613
- );
614
- }
615
-
616
- subscribeStateChanges(cb: (accounts: GqlAccount[]) => void): Unsubscribe {
617
- return this.subscribe<{ FundedAccount: GqlAccount[] }>(
618
- `subscription { FundedAccount(where: { state: { _neq: CLOSED } }, order_by: { updatedAt: desc }) { ${IndexerModule.ACCOUNT_FIELDS} } }`,
619
- undefined,
620
- (data) => cb(data.FundedAccount),
621
- );
622
- }
623
-
624
- disconnect(): void {
625
- if (this.reconnectTimer) {
626
- clearTimeout(this.reconnectTimer);
627
- this.reconnectTimer = null;
628
- }
629
- this.subscriptions.clear();
630
- if (this.ws) {
631
- this.ws.onclose = null;
632
- this.ws.close();
633
- this.ws = null;
634
- }
635
- }
636
- }