@kehtus/proportion-sdk 0.1.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.
Files changed (56) hide show
  1. package/SDK_IMPROVEMENT_PLAN.md +197 -0
  2. package/arch.md +448 -0
  3. package/dist/__tests__/account.test.d.ts +1 -0
  4. package/dist/__tests__/account.test.js +36 -0
  5. package/dist/__tests__/indexer.test.d.ts +1 -0
  6. package/dist/__tests__/indexer.test.js +230 -0
  7. package/dist/__tests__/lifecycle.test.d.ts +1 -0
  8. package/dist/__tests__/lifecycle.test.js +186 -0
  9. package/dist/__tests__/reads.test.d.ts +1 -0
  10. package/dist/__tests__/reads.test.js +185 -0
  11. package/dist/__tests__/utils.test.d.ts +1 -0
  12. package/dist/__tests__/utils.test.js +185 -0
  13. package/dist/abi/factory.d.ts +308 -0
  14. package/dist/abi/factory.js +399 -0
  15. package/dist/abi/fundedAccount.d.ts +1074 -0
  16. package/dist/abi/fundedAccount.js +1373 -0
  17. package/dist/abi/mainVault.d.ts +1448 -0
  18. package/dist/abi/mainVault.js +1865 -0
  19. package/dist/account.d.ts +55 -0
  20. package/dist/account.js +290 -0
  21. package/dist/constants.d.ts +48 -0
  22. package/dist/constants.js +42 -0
  23. package/dist/factory.d.ts +23 -0
  24. package/dist/factory.js +139 -0
  25. package/dist/index.d.ts +11 -0
  26. package/dist/index.js +14 -0
  27. package/dist/indexer.d.ts +57 -0
  28. package/dist/indexer.js +540 -0
  29. package/dist/sdk.d.ts +22 -0
  30. package/dist/sdk.js +89 -0
  31. package/dist/types.d.ts +384 -0
  32. package/dist/types.js +11 -0
  33. package/dist/utils.d.ts +21 -0
  34. package/dist/utils.js +89 -0
  35. package/dist/vault.d.ts +60 -0
  36. package/dist/vault.js +429 -0
  37. package/package.json +32 -0
  38. package/src/__tests__/account.test.ts +40 -0
  39. package/src/__tests__/indexer.test.ts +255 -0
  40. package/src/__tests__/lifecycle.test.ts +231 -0
  41. package/src/__tests__/reads.test.ts +209 -0
  42. package/src/__tests__/utils.test.ts +245 -0
  43. package/src/abi/factory.ts +399 -0
  44. package/src/abi/fundedAccount.ts +1373 -0
  45. package/src/abi/mainVault.ts +1865 -0
  46. package/src/account.ts +339 -0
  47. package/src/constants.ts +50 -0
  48. package/src/factory.ts +157 -0
  49. package/src/index.ts +16 -0
  50. package/src/indexer.ts +636 -0
  51. package/src/sdk.ts +93 -0
  52. package/src/types.ts +426 -0
  53. package/src/utils.ts +143 -0
  54. package/src/vault.ts +479 -0
  55. package/tsconfig.json +19 -0
  56. package/vitest.config.ts +8 -0
package/src/sdk.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { createPublicClient, http, type Address } from "viem";
2
+ import { ADDRESSES, hyperEvm } from "./constants.js";
3
+ import { VaultModule } from "./vault.js";
4
+ import { AccountModule } from "./account.js";
5
+ import { FactoryModule } from "./factory.js";
6
+ import { IndexerModule } from "./indexer.js";
7
+ import { mainVaultAbi } from "./abi/mainVault.js";
8
+ import type { SDKConfig } from "./types.js";
9
+
10
+ export class ProportionSDK {
11
+ readonly vault: VaultModule;
12
+ readonly account: AccountModule;
13
+ readonly factory: FactoryModule;
14
+ readonly indexer: IndexerModule;
15
+
16
+ /**
17
+ * Async factory: resolves addresses from explicit config > ENV > ProtocolConfig > hardcoded constants.
18
+ *
19
+ * ProtocolConfig is only a convenience fallback from the indexer. It is not the source of truth
20
+ * for protocol configuration and should not replace explicit deployment config in applications.
21
+ *
22
+ * ENV vars: PROPORTION_VAULT_ADDRESS, PROPORTION_FACTORY_ADDRESS, PROPORTION_USDC_ADDRESS
23
+ */
24
+ static async create(config: SDKConfig): Promise<ProportionSDK> {
25
+ const resolved: Record<string, string> = { ...config.addresses };
26
+
27
+ // ENV fallbacks (Node; safely ignored in browsers)
28
+ const env = typeof process !== "undefined" ? process.env : undefined;
29
+ if (!resolved.MAIN_VAULT && env?.PROPORTION_VAULT_ADDRESS) resolved.MAIN_VAULT = env.PROPORTION_VAULT_ADDRESS;
30
+ if (!resolved.FACTORY && env?.PROPORTION_FACTORY_ADDRESS) resolved.FACTORY = env.PROPORTION_FACTORY_ADDRESS;
31
+ if (!resolved.USDC && env?.PROPORTION_USDC_ADDRESS) resolved.USDC = env.PROPORTION_USDC_ADDRESS;
32
+
33
+ // Fetch missing addresses from the indexer only as a convenience fallback.
34
+ // Primary address sources remain explicit config.addresses and ENV.
35
+ if (!resolved.MAIN_VAULT || !resolved.FACTORY) {
36
+ try {
37
+ const indexer = new IndexerModule(config.graphqlUrl, undefined, {
38
+ fetchFn: config.fetchFn,
39
+ webSocketCtor: config.webSocketCtor,
40
+ });
41
+ const protocol = await indexer.getProtocolConfig();
42
+ if (protocol) {
43
+ if (!resolved.MAIN_VAULT && protocol.vaultAddress) resolved.MAIN_VAULT = protocol.vaultAddress;
44
+ if (!resolved.FACTORY && protocol.factoryAddress) resolved.FACTORY = protocol.factoryAddress;
45
+ }
46
+ } catch { /* fall back to hardcoded constants */ }
47
+ }
48
+
49
+ if (resolved.MAIN_VAULT && !resolved.USDC) {
50
+ try {
51
+ const publicClient = createPublicClient({
52
+ chain: hyperEvm,
53
+ transport: http(config.rpcUrl ?? hyperEvm.rpcUrls.default.http[0]),
54
+ });
55
+ resolved.USDC = await publicClient.readContract({
56
+ address: resolved.MAIN_VAULT as Address,
57
+ abi: mainVaultAbi,
58
+ functionName: "usdc",
59
+ });
60
+ } catch { /* fall back to hardcoded constants */ }
61
+ }
62
+
63
+ return new ProportionSDK({ ...config, addresses: resolved });
64
+ }
65
+
66
+ constructor(config: SDKConfig) {
67
+ const addresses = {
68
+ vault: (config.addresses?.MAIN_VAULT ?? ADDRESSES.MAIN_VAULT) as Address,
69
+ usdc: (config.addresses?.USDC ?? ADDRESSES.USDC) as Address,
70
+ factory: (config.addresses?.FACTORY ?? ADDRESSES.FACTORY) as Address,
71
+ };
72
+
73
+ const publicClient = createPublicClient({
74
+ chain: hyperEvm,
75
+ transport: http(config.rpcUrl ?? hyperEvm.rpcUrls.default.http[0]),
76
+ });
77
+
78
+ this.vault = new VaultModule(publicClient, config.walletClient, hyperEvm, {
79
+ vault: addresses.vault,
80
+ usdc: addresses.usdc,
81
+ });
82
+ this.account = new AccountModule(publicClient, config.walletClient, hyperEvm);
83
+ this.factory = new FactoryModule(publicClient, addresses.factory);
84
+ this.indexer = new IndexerModule(config.graphqlUrl, config.graphqlWsUrl, {
85
+ fetchFn: config.fetchFn,
86
+ webSocketCtor: config.webSocketCtor,
87
+ });
88
+ }
89
+
90
+ disconnect(): void {
91
+ this.indexer.disconnect();
92
+ }
93
+ }
package/src/types.ts ADDED
@@ -0,0 +1,426 @@
1
+ import type { Address, WalletClient } from "viem";
2
+ import type { ADDRESSES } from "./constants.js";
3
+
4
+ // ── Config ──────────────────────────────────────────
5
+ export interface SDKConfig {
6
+ rpcUrl?: string;
7
+ graphqlUrl: string;
8
+ graphqlWsUrl?: string;
9
+ walletClient?: WalletClient;
10
+ fetchFn?: typeof fetch;
11
+ webSocketCtor?: WebSocketConstructor;
12
+ addresses?: Partial<typeof ADDRESSES>;
13
+ }
14
+
15
+ export interface WebSocketLike {
16
+ readyState: number;
17
+ send(data: string): void;
18
+ close(): void;
19
+ onopen: ((this: unknown, ...args: unknown[]) => unknown) | null;
20
+ onmessage: ((this: unknown, event: { data: unknown }, ...args: unknown[]) => unknown) | null;
21
+ onclose: ((this: unknown, ...args: unknown[]) => unknown) | null;
22
+ onerror: ((this: unknown, ...args: unknown[]) => unknown) | null;
23
+ }
24
+
25
+ export interface WebSocketConstructor {
26
+ new (url: string, protocols?: string | string[]): WebSocketLike;
27
+ readonly OPEN: number;
28
+ }
29
+
30
+ // ── Enums ───────────────────────────────────────────
31
+ export enum AccountState {
32
+ Uninitialized,
33
+ Initialized,
34
+ Active,
35
+ WithdrawalPending,
36
+ Closing,
37
+ Returning,
38
+ Closed,
39
+ }
40
+
41
+ export type AccountStateLabel =
42
+ | "UNINITIALIZED"
43
+ | "INITIALIZED"
44
+ | "ACTIVE"
45
+ | "WITHDRAWAL_PENDING"
46
+ | "CLOSING"
47
+ | "RETURNING"
48
+ | "CLOSED";
49
+
50
+ export type IndexerAccountStateLabel =
51
+ | "INITIALIZED"
52
+ | "ACTIVE"
53
+ | "WITHDRAWAL_PENDING"
54
+ | "CLOSING"
55
+ | "RETURNING"
56
+ | "CLOSED";
57
+
58
+ export type BreachType =
59
+ | "TRAILING_DRAWDOWN"
60
+ | "DAILY_DRAWDOWN"
61
+ | "LEVERAGE"
62
+ | "POSITION_LEVERAGE"
63
+ | "UNAUTHORIZED_ASSET"
64
+ | "SUBSCRIPTION_EXPIRED";
65
+
66
+ // ── On-chain return types ───────────────────────────
67
+ export interface VaultStats {
68
+ totalAssets: bigint;
69
+ sharePrice: bigint;
70
+ utilizationBps: bigint;
71
+ availableForAllocation: bigint;
72
+ maxAccountSize: bigint;
73
+ remainingCap: bigint;
74
+ paused: boolean;
75
+ feeMultiplier: number;
76
+ subscriptionFeeMultiplier: number;
77
+ totalShares: bigint;
78
+ totalAllocated: bigint;
79
+ owner: Address;
80
+ pendingOwner: Address;
81
+ relayer: Address;
82
+ protocolFeeReceiver: Address;
83
+ maxCap: bigint;
84
+ hypeForSpot: bigint;
85
+ factory: Address;
86
+ minBuilderShares: bigint;
87
+ builderLeverage: bigint;
88
+ protocolFeeBps: bigint;
89
+ minAccountSize: bigint;
90
+ maxAccountSizeLimit: bigint;
91
+ minDrawdownBps: number;
92
+ maxDrawdownBps: number;
93
+ maxUtilizationBps: bigint;
94
+ maxSingleAccountBps: bigint;
95
+ dailyDdDiscountBps: number;
96
+ }
97
+
98
+ export interface LPPosition {
99
+ shares: bigint;
100
+ sharesValue: bigint;
101
+ withdrawableShares: bigint;
102
+ }
103
+
104
+ export interface BuilderInfo {
105
+ active: boolean;
106
+ activeFunded: bigint;
107
+ availableFunded: bigint;
108
+ }
109
+
110
+ export interface AccountDetails {
111
+ address: Address;
112
+ state: AccountState;
113
+ owner: Address;
114
+ builder: Address;
115
+ accountSize: bigint;
116
+ drawdownBps: number;
117
+ dailyDrawdownEnabled: boolean;
118
+ subscriptionFeeMultiplier: number;
119
+ highWaterMark: bigint;
120
+ dayStartValue: bigint;
121
+ activatedAt: bigint;
122
+ subscriptionPaidUntil: bigint;
123
+ subscriptionCancelled: boolean;
124
+ pendingSubscriptionFee: bigint;
125
+ apiWallet: Address;
126
+ riskEngineWallet: Address;
127
+ builderRiskEngineWallet: Address;
128
+ checkpointIndex: number;
129
+ totalCheckpoints: number;
130
+ profitableDaysCount: number;
131
+ lastCheckpointDay: bigint;
132
+ closingInitiatedAt: number;
133
+ returnInitiatedAt: number;
134
+ pendingWithdrawal: { profit: bigint; requestedAt: number };
135
+ trailingFloor: bigint;
136
+ dailyFloor: bigint;
137
+ subscriptionFee: bigint;
138
+ isSubscriptionActive: boolean;
139
+ canWithdrawProfit: boolean;
140
+ checkpoints: Array<{
141
+ accountValue: bigint;
142
+ timestamp: number;
143
+ profitable: boolean;
144
+ }>;
145
+ }
146
+
147
+ // ── GraphQL entity types ────────────────────────────
148
+ export interface GqlVault {
149
+ id: string;
150
+ address: string;
151
+ totalDeposits: string;
152
+ totalWithdrawals: string;
153
+ totalActivationFees: string;
154
+ cumulativeActivationFeesToVault: string;
155
+ totalAllocated: string;
156
+ totalShares: string;
157
+ cumulativeProfitShares: string;
158
+ cumulativeCancelledProfit: string;
159
+ cumulativeSubscriptionFees: string;
160
+ cumulativeProtocolFeesRouted: string;
161
+ cumulativeHypercoreFees: string;
162
+ cumulativeAccountGains: string;
163
+ cumulativeAccountLosses: string;
164
+ cumulativeRescuedFunds: string;
165
+ totalAccountsActivated: number;
166
+ totalAccountsActive: number;
167
+ totalAccountsClosed: number;
168
+ paused: boolean;
169
+ subscriptionFeeMultiplier: number;
170
+ protocolFeeBps: number;
171
+ protocolFeeReceiver: string | null;
172
+ updatedAt: string;
173
+ }
174
+
175
+ export interface GqlLP {
176
+ id: string;
177
+ totalDeposited: string;
178
+ totalWithdrawn: string;
179
+ depositCount: number;
180
+ withdrawCount: number;
181
+ lastActionAt: string;
182
+ }
183
+
184
+ export interface GqlBuilder {
185
+ id: string;
186
+ address: string;
187
+ vaultAddress: string;
188
+ active: boolean;
189
+ totalAccountsActivated: number;
190
+ totalAccountsClosed: number;
191
+ activeAccounts: number;
192
+ registeredAt: string;
193
+ deactivatedAt: string | null;
194
+ }
195
+
196
+ export interface GqlAccount {
197
+ id: string;
198
+ owner: string;
199
+ builder: { id: string; address: string; vaultAddress: string };
200
+ vaultAddress: string;
201
+ factoryAddress: string;
202
+ state: IndexerAccountStateLabel;
203
+ accountSize: string;
204
+ drawdownBps: number;
205
+ dailyDrawdownEnabled: boolean;
206
+ activationFee: string;
207
+ highWaterMark: string;
208
+ profitableDaysCount: number;
209
+ lastCheckpointDay: string;
210
+ totalCheckpoints: number;
211
+ subscriptionPaidUntil: string;
212
+ subscriptionCancelled: boolean;
213
+ pendingSubscriptionFee: string;
214
+ subscriptionFeeMultiplier: number;
215
+ dayStartValue: string;
216
+ apiWallet: string | null;
217
+ riskEngineWallet: string | null;
218
+ builderRiskEngineWallet: string | null;
219
+ createdAt: string;
220
+ activatedAt: string | null;
221
+ closingInitiatedAt: string | null;
222
+ returnInitiatedAt: string | null;
223
+ closedAt: string | null;
224
+ returnedAmount: string | null;
225
+ breachType: BreachType | null;
226
+ breachValue: string | null;
227
+ breachFloor: string | null;
228
+ closePendingSubFee: string | null;
229
+ closePendingProfit: string | null;
230
+ lastWithdrawalId: string | null;
231
+ updatedAt: string;
232
+ }
233
+
234
+ export interface GqlCheckpoint {
235
+ id: string;
236
+ dayNumber: string;
237
+ accountValue: string;
238
+ previousValue: string;
239
+ profitable: boolean;
240
+ profitableDaysCount: number;
241
+ timestamp: string;
242
+ }
243
+
244
+ export interface GqlProfitWithdrawal {
245
+ id: string;
246
+ profit: string;
247
+ ownerAmount: string;
248
+ vaultAmount: string;
249
+ protocolAmount: string;
250
+ status: "REQUESTED" | "CLAIMED" | "CANCELLED";
251
+ requestedAt: string;
252
+ claimedAt: string | null;
253
+ cancelledAt: string | null;
254
+ }
255
+
256
+ export interface GqlSubscriptionPayment {
257
+ id: string;
258
+ amount: string;
259
+ paidUntil: string;
260
+ timestamp: string;
261
+ }
262
+
263
+ export interface GqlDeposit {
264
+ id: string;
265
+ lp: { id: string };
266
+ amount: string;
267
+ sharesReceived: string;
268
+ timestamp: string;
269
+ blockNumber?: number;
270
+ txHash: string;
271
+ }
272
+
273
+ export interface GqlWithdrawal {
274
+ id: string;
275
+ lp: { id: string };
276
+ amount: string;
277
+ sharesBurned: string;
278
+ timestamp: string;
279
+ blockNumber?: number;
280
+ txHash: string;
281
+ }
282
+
283
+ export interface GqlAccountEvent {
284
+ id: string;
285
+ eventType: string;
286
+ data: string;
287
+ timestamp: string;
288
+ blockNumber: number;
289
+ txHash: string;
290
+ }
291
+
292
+ export interface GqlAllowedPerp {
293
+ id: string;
294
+ vaultAddress: string;
295
+ perpIndex: number;
296
+ allowed: boolean;
297
+ }
298
+
299
+ export interface GqlDelegate {
300
+ id: string;
301
+ vaultAddress: string;
302
+ delegateAddress: string;
303
+ builderAddress: string;
304
+ }
305
+
306
+ export interface GqlProtocolConfig {
307
+ id: string;
308
+ vaultAddress: string;
309
+ factoryAddress: string;
310
+ updatedAt: string;
311
+ }
312
+
313
+ export type GqlConfigContractType = "MainVault" | "FundedAccountFactory";
314
+
315
+ export interface GqlMainVaultConfig {
316
+ id: string;
317
+ vaultAddress: string;
318
+ factoryAddress: string;
319
+ owner: string;
320
+ pendingOwner: string;
321
+ relayer: string;
322
+ paused: boolean;
323
+ protocolFeeReceiver: string;
324
+ feeMultiplier: number;
325
+ subscriptionFeeMultiplier: number;
326
+ dailyDdDiscountBps: number;
327
+ minBuilderShares: string;
328
+ builderLeverage: string;
329
+ protocolFeeBps: number;
330
+ minAccountSize: string;
331
+ maxAccountSize: string;
332
+ minDrawdownBps: number;
333
+ maxDrawdownBps: number;
334
+ maxUtilizationBps: number;
335
+ maxSingleAccountBps: number;
336
+ hypeForSpot: string;
337
+ maxCap: string;
338
+ createdAt: string;
339
+ updatedAt: string;
340
+ }
341
+
342
+ export interface GqlFactoryConfig {
343
+ id: string;
344
+ factoryAddress: string;
345
+ vaultAddress: string;
346
+ owner: string;
347
+ pendingOwner: string;
348
+ createdAt: string;
349
+ updatedAt: string;
350
+ }
351
+
352
+ export interface GqlConfigChange {
353
+ id: string;
354
+ contractAddress: string;
355
+ contractType: GqlConfigContractType;
356
+ field: string;
357
+ oldValue: string;
358
+ newValue: string;
359
+ blockNumber: number;
360
+ timestamp: string;
361
+ txHash: string;
362
+ }
363
+
364
+ export interface GqlVaultDayData {
365
+ id: string;
366
+ date: number;
367
+ dayNumber: number;
368
+ evmBalance: string;
369
+ totalAllocated: string;
370
+ totalAssets: string;
371
+ totalShares: string;
372
+ sharePrice: string;
373
+ dailyDeposits: string;
374
+ dailyWithdrawals: string;
375
+ dailyNetLpFlow: string;
376
+ dailyActivationFees: string;
377
+ dailyActivationFeesToVault: string;
378
+ dailyProfitShares: string;
379
+ dailyCancelledProfit: string;
380
+ dailySubscriptionFees: string;
381
+ dailyProtocolFeesRouted: string;
382
+ dailyHypercoreFees: string;
383
+ dailyRescuedFunds: string;
384
+ dailyAccountReturnAmount: string;
385
+ dailyAccountPrincipalReturned: string;
386
+ dailyAccountGains: string;
387
+ dailyAccountLosses: string;
388
+ dailyPerformancePnl: string;
389
+ totalAccountsActivated: number;
390
+ totalAccountsActive: number;
391
+ totalAccountsClosed: number;
392
+ accountsActivatedToday: number;
393
+ accountsClosedToday: number;
394
+ navChange24h: string;
395
+ sharePriceChange24h: string;
396
+ return24hBps: number;
397
+ paused: boolean;
398
+ updatedAt: string;
399
+ }
400
+
401
+ export interface GqlLiquidityProviderDayData {
402
+ id: string;
403
+ lp: { id: string };
404
+ date: number;
405
+ dayNumber: number;
406
+ shares: string;
407
+ sharesValue: string;
408
+ dailyDeposits: string;
409
+ dailyWithdrawals: string;
410
+ depositCount: number;
411
+ withdrawCount: number;
412
+ }
413
+
414
+ // ── Helpers ─────────────────────────────────────────
415
+ export interface Pagination {
416
+ limit?: number;
417
+ offset?: number;
418
+ }
419
+
420
+ export interface ConfigChangeFilters extends Pagination {
421
+ contractAddress?: string;
422
+ contractType?: GqlConfigContractType;
423
+ field?: string;
424
+ }
425
+
426
+ export type Unsubscribe = () => void;
package/src/utils.ts ADDED
@@ -0,0 +1,143 @@
1
+ import {
2
+ BPS,
3
+ DAILY_DD_DISCOUNT_BPS,
4
+ DECIMALS_MULTIPLIER,
5
+ OWNER_PROFIT_BPS,
6
+ PROTOCOL_PROFIT_BPS,
7
+ USDC_DECIMALS,
8
+ } from "./constants.js";
9
+ import { AccountState, type AccountStateLabel } from "./types.js";
10
+
11
+ // ── Decimals ────────────────────────────────────────
12
+
13
+ export function fromUsdc(amount: bigint): number {
14
+ return Number(amount) / 10 ** USDC_DECIMALS;
15
+ }
16
+
17
+ export function toUsdc(amount: number): bigint {
18
+ return BigInt(Math.round(amount * 10 ** USDC_DECIMALS));
19
+ }
20
+
21
+ export function from8Dec(value: bigint): number {
22
+ return Number(value) / 1e8;
23
+ }
24
+
25
+ export function toUsdc6(value8dec: bigint): bigint {
26
+ return value8dec / DECIMALS_MULTIPLIER;
27
+ }
28
+
29
+ export function to8Dec(value6dec: bigint): bigint {
30
+ return value6dec * DECIMALS_MULTIPLIER;
31
+ }
32
+
33
+ export function formatUsd(amount: bigint, dec: number = USDC_DECIMALS): string {
34
+ const num = Number(amount) / 10 ** dec;
35
+ return "$" + num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
36
+ }
37
+
38
+ // ── Drawdown ────────────────────────────────────────
39
+
40
+ export function trailingFloor(hwm: bigint, drawdownBps: number): bigint {
41
+ return hwm - (hwm * BigInt(drawdownBps)) / BPS;
42
+ }
43
+
44
+ export function dailyFloor(dayStartValue: bigint, drawdownBps: number): bigint {
45
+ const dailyBps = BigInt(drawdownBps) / 2n;
46
+ return dayStartValue - (dayStartValue * dailyBps) / BPS;
47
+ }
48
+
49
+ export function isTrailingBreached(value: bigint, hwm: bigint, bps: number): boolean {
50
+ return value < trailingFloor(hwm, bps);
51
+ }
52
+
53
+ export function isDailyBreached(value: bigint, dsv: bigint, bps: number): boolean {
54
+ return value < dailyFloor(dsv, bps);
55
+ }
56
+
57
+ // ── Fees ────────────────────────────────────────────
58
+
59
+ const FEE_DIVISOR = 1_000_000n;
60
+ const USDC_UNIT = 1_000_000n;
61
+
62
+ function roundUpUsdc(fee: bigint): bigint {
63
+ return ((fee + USDC_UNIT - 1n) / USDC_UNIT) * USDC_UNIT;
64
+ }
65
+
66
+ function applyDailyDiscount(fee: bigint, discountBps: bigint): bigint {
67
+ return (fee * (BPS - discountBps)) / BPS;
68
+ }
69
+
70
+ export function activationFee(
71
+ accountSize: bigint,
72
+ drawdownBps: number,
73
+ dailyDD: boolean,
74
+ feeMultiplier: number = 120,
75
+ dailyDdDiscountBps: number = Number(DAILY_DD_DISCOUNT_BPS),
76
+ ): bigint {
77
+ let fee = (accountSize * BigInt(drawdownBps) * BigInt(feeMultiplier)) / FEE_DIVISOR;
78
+ if (dailyDD) {
79
+ fee = applyDailyDiscount(fee, BigInt(dailyDdDiscountBps));
80
+ }
81
+ return roundUpUsdc(fee);
82
+ }
83
+
84
+ export function subscriptionFee(accountSize: bigint, subFeeMultiplier: number): bigint;
85
+ export function subscriptionFee(
86
+ accountSize: bigint,
87
+ _drawdownBps: number,
88
+ _dailyDD: boolean,
89
+ subFeeMultiplier: number,
90
+ ): bigint;
91
+ export function subscriptionFee(
92
+ accountSize: bigint,
93
+ a: number,
94
+ _b?: boolean,
95
+ c?: number,
96
+ ): bigint {
97
+ const subFeeMultiplier = c ?? a;
98
+ const fee = (accountSize * BigInt(subFeeMultiplier)) / BPS;
99
+ return roundUpUsdc(fee);
100
+ }
101
+
102
+ // ── Profit split ────────────────────────────────────
103
+
104
+ export function splitProfit(profit: bigint): {
105
+ owner: bigint;
106
+ vault: bigint;
107
+ protocol: bigint;
108
+ } {
109
+ const owner = (profit * OWNER_PROFIT_BPS) / BPS;
110
+ const protocol = (profit * PROTOCOL_PROFIT_BPS) / BPS;
111
+ const vault = profit - owner - protocol;
112
+ return { owner, vault, protocol };
113
+ }
114
+
115
+ // ── Enum mapping ────────────────────────────────────
116
+
117
+ const stateLabels: Record<AccountState, AccountStateLabel> = {
118
+ [AccountState.Uninitialized]: "UNINITIALIZED",
119
+ [AccountState.Initialized]: "INITIALIZED",
120
+ [AccountState.Active]: "ACTIVE",
121
+ [AccountState.WithdrawalPending]: "WITHDRAWAL_PENDING",
122
+ [AccountState.Closing]: "CLOSING",
123
+ [AccountState.Returning]: "RETURNING",
124
+ [AccountState.Closed]: "CLOSED",
125
+ };
126
+
127
+ const labelStates: Record<AccountStateLabel, AccountState> = {
128
+ UNINITIALIZED: AccountState.Uninitialized,
129
+ INITIALIZED: AccountState.Initialized,
130
+ ACTIVE: AccountState.Active,
131
+ WITHDRAWAL_PENDING: AccountState.WithdrawalPending,
132
+ CLOSING: AccountState.Closing,
133
+ RETURNING: AccountState.Returning,
134
+ CLOSED: AccountState.Closed,
135
+ };
136
+
137
+ export function stateToLabel(state: AccountState): AccountStateLabel {
138
+ return stateLabels[state];
139
+ }
140
+
141
+ export function labelToState(label: AccountStateLabel): AccountState {
142
+ return labelStates[label];
143
+ }