@kehtus/proportion-sdk 0.1.2 → 0.1.3
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/LICENSE +21 -0
- package/dist/abi/factory.d.ts +1 -0
- package/dist/abi/factory.d.ts.map +1 -0
- package/dist/abi/factory.js +1 -0
- package/dist/abi/factory.js.map +1 -0
- package/dist/abi/forwarder.d.ts +1 -0
- package/dist/abi/forwarder.d.ts.map +1 -0
- package/dist/abi/forwarder.js +1 -0
- package/dist/abi/forwarder.js.map +1 -0
- package/dist/abi/fundedAccount.d.ts +1 -0
- package/dist/abi/fundedAccount.d.ts.map +1 -0
- package/dist/abi/fundedAccount.js +1 -0
- package/dist/abi/fundedAccount.js.map +1 -0
- package/dist/abi/mainVault.d.ts +198 -16
- package/dist/abi/mainVault.d.ts.map +1 -0
- package/dist/abi/mainVault.js +253 -19
- package/dist/abi/mainVault.js.map +1 -0
- package/dist/account.d.ts +3 -2
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +46 -43
- package/dist/account.js.map +1 -0
- package/dist/base.d.ts +9 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +19 -0
- package/dist/base.js.map +1 -0
- package/dist/constants.d.ts +5 -4
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +5 -4
- package/dist/constants.js.map +1 -0
- package/dist/factory.d.ts +1 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +56 -10
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +8 -3
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +81 -136
- package/dist/indexer.js.map +1 -0
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +1 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +10 -0
- package/dist/utils.js.map +1 -0
- package/dist/vault.d.ts +7 -4
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +76 -51
- package/dist/vault.js.map +1 -0
- package/package.json +12 -1
- package/arch.md +0 -455
- package/dist/__tests__/account.test.d.ts +0 -1
- package/dist/__tests__/account.test.js +0 -36
- package/dist/__tests__/indexer.test.d.ts +0 -1
- package/dist/__tests__/indexer.test.js +0 -230
- package/dist/__tests__/lifecycle.test.d.ts +0 -1
- package/dist/__tests__/lifecycle.test.js +0 -186
- package/dist/__tests__/reads.test.d.ts +0 -1
- package/dist/__tests__/reads.test.js +0 -184
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -185
- package/src/__tests__/account.test.ts +0 -40
- package/src/__tests__/indexer.test.ts +0 -255
- package/src/__tests__/lifecycle.test.ts +0 -231
- package/src/__tests__/reads.test.ts +0 -208
- package/src/__tests__/utils.test.ts +0 -245
- package/src/abi/factory.ts +0 -417
- package/src/abi/forwarder.ts +0 -373
- package/src/abi/fundedAccount.ts +0 -1296
- package/src/abi/mainVault.ts +0 -1852
- package/src/account.ts +0 -325
- package/src/constants.ts +0 -51
- package/src/factory.ts +0 -157
- package/src/index.ts +0 -16
- package/src/indexer.ts +0 -636
- package/src/sdk.ts +0 -93
- package/src/types.ts +0 -421
- package/src/utils.ts +0 -143
- package/src/vault.ts +0 -479
- package/tsconfig.json +0 -19
- 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
|
-
}
|