@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
@@ -0,0 +1,197 @@
1
+ # План улучшения Proportion SDK
2
+
3
+ ## Статус после повторной сверки
4
+
5
+ Этот документ обновлён после повторной проверки against:
6
+
7
+ - `contracts/MainVault.sol`
8
+ - `contracts/FundedAccount.sol`
9
+ - `contracts/FundedAccountFactory.sol`
10
+ - `prop-sdk/src/*`
11
+ - критичные indexer-файлы `envio-funded/schema.graphql` и `envio-funded/src/EventHandlers.ts`
12
+
13
+ Если этот план и Solidity расходятся, Solidity остаётся source of truth.
14
+
15
+ ## Что уже закрыто
16
+
17
+ ### Контрактная синхронизация
18
+
19
+ - ABI в SDK синхронизированы с текущими локальными контрактами.
20
+ - Формулы `activationFee`, `subscriptionFee`, `splitProfit`, `trailingFloor`, `dailyFloor` приведены к актуальной Solidity-логике.
21
+ - `arch.md` обновлён и теперь описывает текущий surface SDK, а не старую версию контрактов.
22
+
23
+ ### VaultModule
24
+
25
+ - Добавлены admin setters для текущих mutable параметров vault:
26
+ - `setFeeMultiplier`
27
+ - `setSubscriptionFeeMultiplier`
28
+ - `setHypeForSpot`
29
+ - `setMaxCap`
30
+ - `setMinBuilderShares`
31
+ - `setBuilderLeverage`
32
+ - `setProtocolFeeBps`
33
+ - `setMinAccountSize`
34
+ - `setMaxAccountSize`
35
+ - `setMinDrawdownBps`
36
+ - `setMaxDrawdownBps`
37
+ - `setMaxUtilizationBps`
38
+ - `setMaxSingleAccountBps`
39
+ - `getStats()` расширен и читает текущий конфиг и runtime state vault.
40
+ - Добавлены utility getters:
41
+ - `isFundedAccount`
42
+ - `getDelegateBuilder`
43
+ - `getRemainingCap`
44
+ - `activateFunded()` больше не полагается на stale native fee default и читает `hypeForSpot()` из vault.
45
+
46
+ ### AccountModule
47
+
48
+ - Добавлен `initiateCloseAdmin`.
49
+ - Добавлен `getAccountConfig()`.
50
+ - `getDetails()` теперь возвращает `subscriptionFeeMultiplier`, `checkpointIndex` и нормализованные checkpoints вместо сырого ring-buffer порядка.
51
+ - Текущий write surface выровнен с локальным `FundedAccount.sol`.
52
+
53
+ ### FactoryModule
54
+
55
+ - SDK переведён на реальный on-chain surface новой фабрики:
56
+ - `allAccounts(index)`
57
+ - `allAccountsIndex(account)`
58
+ - `traderBuilderNonce(owner, builder)`
59
+ - `predictFundedAccountAddress(owner, builder)`
60
+ - Сохранены совместимые helper-методы `getAccountsCount()` и `getAccounts()`, но теперь они реализованы поверх probing `allAccounts(index)`.
61
+ - Добавлены safe helpers:
62
+ - `hasAccount`
63
+ - `findAccountIndex`
64
+
65
+ ### Indexer Layer
66
+
67
+ - GraphQL-типы приведены к реальной `schema.graphql`.
68
+ - Разделены on-chain state labels и indexer state labels.
69
+ - Учтён `POSITION_LEVERAGE`.
70
+ - Учтены дополнительные индексируемые поля вроде `subscriptionFeeMultiplier`, `lastWithdrawalId`, `totalAllocated`, `totalShares`.
71
+ - Vault analytics больше не должны опираться на жёстко зашитый 90/10 split: актуальны `protocolFeeReceiver`, mutable `PROTOCOL_FEE_BPS` и фактический amount, остающийся в vault.
72
+ - Для service/backend use cases добавлена инъекция `fetchFn` и `webSocketCtor`, чтобы SDK не зависел жёстко от глобальных runtime объектов.
73
+
74
+ ## Что в старом плане устарело
75
+
76
+ ### Устаревшие статусы
77
+
78
+ Следующие пункты были верны для прошлой версии SDK, но уже закрыты:
79
+
80
+ - отсутствие `initiateCloseAdmin`
81
+ - отсутствие `getAccountConfig`
82
+ - отсутствие большинства admin setters в `VaultModule`
83
+ - неполный `VaultStats`
84
+ - отсутствие `isFundedAccount` и `getDelegateBuilder`
85
+
86
+ ### Устаревшие константы
87
+
88
+ В старом плане были значения, которые больше не совпадают с текущими контрактами:
89
+
90
+ - `MIN_PROFIT_BPS` указан как `150`, но в текущем `FundedAccount.sol` он равен `50`
91
+ - важные экспортируемые значения нужно сверять с локальной Solidity, а не со старым plan draft
92
+
93
+ ### Необязательная рекомендация про отдельный `AdminModule`
94
+
95
+ Идея отдельного `AdminModule` сама по себе нормальная, но сейчас это не must-have.
96
+ Текущий `VaultModule` уже покрывает актуальный owner admin surface без архитектурного долга, который требовал бы срочного выделения нового модуля.
97
+
98
+ ## Что остаётся актуальным
99
+
100
+ ### Phase 2. Улучшения API и DX
101
+
102
+ 1. Выровнять числовую модель SDK для service use cases.
103
+ Сейчас часть helper-ов (`fromUsdc`, `from8Dec`, `formatUsd`) переводит `bigint` в `number`, что удобно для UI, но не идеально для финансовой серверной логики.
104
+
105
+ 2. Выровнять timestamp-модель.
106
+ Сейчас часть временных полей в `AccountDetails` хранится как `bigint`, часть как `number`. Лучше выбрать одну ясную стратегию API.
107
+
108
+ 3. Добавить типы для параметризуемого admin-конфига.
109
+ Практичный следующий шаг:
110
+
111
+ ```ts
112
+ export interface VaultParameters {
113
+ feeMultiplier: number;
114
+ subscriptionFeeMultiplier: number;
115
+ minBuilderShares: bigint;
116
+ builderLeverage: bigint;
117
+ protocolFeeBps: bigint;
118
+ minAccountSize: bigint;
119
+ maxAccountSize: bigint;
120
+ minDrawdownBps: number;
121
+ maxDrawdownBps: number;
122
+ maxUtilizationBps: bigint;
123
+ maxSingleAccountBps: bigint;
124
+ hypeForSpot: bigint;
125
+ maxCap: bigint;
126
+ }
127
+ ```
128
+
129
+ 4. Добавить validation helpers поверх текущего `VaultStats`.
130
+ Это уже полезный SDK-level слой, который не зависит от деплоя:
131
+
132
+ ```ts
133
+ export function validateAccountSize(size: bigint, vaultStats: VaultStats): {
134
+ valid: boolean;
135
+ reason?: string;
136
+ }
137
+
138
+ export function validateDrawdownBps(bps: number, vaultStats: VaultStats): {
139
+ valid: boolean;
140
+ reason?: string;
141
+ }
142
+
143
+ export function canBuilderActivate(
144
+ builderInfo: BuilderInfo,
145
+ accountSize: bigint,
146
+ vaultStats: VaultStats
147
+ ): {
148
+ canActivate: boolean;
149
+ reason?: string;
150
+ }
151
+ ```
152
+
153
+ ### Phase 3. Дополнительные helper-обёртки
154
+
155
+ 1. Batch reads:
156
+
157
+ ```ts
158
+ async getMultipleLPPositions(addresses: Address[]): Promise<LPPosition[]>
159
+ async getMultipleBuilderInfo(addresses: Address[]): Promise<BuilderInfo[]>
160
+ async getMultipleAccountDetails(addresses: Address[]): Promise<AccountDetails[]>
161
+ ```
162
+
163
+ 2. Дополнительные indexer helpers:
164
+
165
+ ```ts
166
+ async getEventsByDateRange(
167
+ accountAddress: Address,
168
+ fromDate: Date,
169
+ toDate: Date
170
+ ): Promise<GqlAccountEvent[]>
171
+
172
+ async getCheckpointHistory(
173
+ accountAddress: Address,
174
+ limit?: number
175
+ ): Promise<GqlCheckpoint[]>
176
+ ```
177
+
178
+ 3. Higher-level admin ergonomics:
179
+
180
+ - `getCurrentParameters()`
181
+ - `validateParameters()`
182
+ - опциональный bulk-update helper для owner workflows
183
+
184
+ ## Рекомендуемый следующий порядок работ
185
+
186
+ 1. Сначала привести numeric/timestamp API к более строгой и единообразной модели.
187
+ 2. Затем добавить validation helpers поверх уже актуального `VaultStats`.
188
+ 3. После этого расширять batch helpers и high-level admin ergonomics.
189
+
190
+ ## Проверка
191
+
192
+ После последних изменений:
193
+
194
+ - `npm run check` проходит
195
+ - `npm run test:unit` проходит
196
+
197
+ RPC/GraphQL-зависимые тесты не считаются источником истины, пока локальные контракты не задеплоены.
package/arch.md ADDED
@@ -0,0 +1,448 @@
1
+ # Proportion SDK
2
+
3
+ TypeScript SDK over the current local Proportion Solidity contracts.
4
+
5
+ This document is intentionally aligned to the code in:
6
+
7
+ - `contracts/MainVault.sol`
8
+ - `contracts/FundedAccount.sol`
9
+ - `contracts/FundedAccountFactory.sol`
10
+ - `prop-sdk/src/*`
11
+
12
+ If documentation and Solidity diverge, Solidity is the source of truth.
13
+
14
+ ## Architecture
15
+
16
+ The SDK has four runtime modules:
17
+
18
+ - `vault`: `MainVault` reads, LP flows, builder flows, and owner admin writes.
19
+ - `account`: `FundedAccount` reads and writes for a specific funded account clone.
20
+ - `factory`: `FundedAccountFactory` helpers around deterministic account addresses and factory state.
21
+ - `indexer`: optional Envio GraphQL/WebSocket client for off-chain querying and subscriptions.
22
+
23
+ `ProportionSDK` itself is a thin composition layer over `viem`.
24
+
25
+ ## Initialization
26
+
27
+ ```ts
28
+ import { ProportionSDK, hyperEvm } from "@proportion/sdk";
29
+ import { createWalletClient, http } from "viem";
30
+ import { privateKeyToAccount } from "viem/accounts";
31
+
32
+ const wallet = createWalletClient({
33
+ account: privateKeyToAccount("0x..."),
34
+ chain: hyperEvm,
35
+ transport: http(),
36
+ });
37
+
38
+ const sdk = new ProportionSDK({
39
+ graphqlUrl: "https://your-envio-endpoint/graphql",
40
+ walletClient: wallet,
41
+ });
42
+ ```
43
+
44
+ There are two construction modes:
45
+
46
+ - `new ProportionSDK(config)`: uses addresses from `config.addresses` or `src/constants.ts`.
47
+ - `await ProportionSDK.create(config)`: resolves addresses in this order:
48
+ 1. `config.addresses`
49
+ 2. `PROPORTION_VAULT_ADDRESS`, `PROPORTION_FACTORY_ADDRESS`, `PROPORTION_USDC_ADDRESS`
50
+ 3. `indexer.getProtocolConfig()`
51
+ 4. if vault is already known but `USDC` is missing, `usdc()` is read from `MainVault`
52
+ 5. hardcoded SDK defaults
53
+
54
+ Best practice:
55
+
56
+ - Prefer explicit `config.addresses` or ENV in applications and services.
57
+ - Treat `indexer.getProtocolConfig()` as a convenience fallback, not as the primary source of deployment addresses.
58
+ - Use `new ProportionSDK(config)` when you already know the full address set and want direct construction without async resolution.
59
+
60
+ ## Config
61
+
62
+ ```ts
63
+ interface SDKConfig {
64
+ graphqlUrl: string;
65
+ graphqlWsUrl?: string;
66
+ rpcUrl?: string;
67
+ walletClient?: WalletClient;
68
+ fetchFn?: typeof fetch;
69
+ webSocketCtor?: WebSocketConstructor;
70
+ addresses?: {
71
+ MAIN_VAULT?: string;
72
+ FACTORY?: string;
73
+ USDC?: string;
74
+ };
75
+ }
76
+ ```
77
+
78
+ For service or server integrations, `fetchFn` and `webSocketCtor` can be injected explicitly instead of relying on global runtime objects.
79
+
80
+ The exported addresses in `src/constants.ts` are SDK defaults for the current environment, not a guarantee of future deployments.
81
+
82
+ ## Modules
83
+
84
+ ### `prop.vault`
85
+
86
+ Reads:
87
+
88
+ ```ts
89
+ const stats = await prop.vault.getStats();
90
+ const position = await prop.vault.getLPPosition("0x...");
91
+ const builder = await prop.vault.getBuilderInfo("0x...");
92
+ const perps = await prop.vault.getAllowedPerps();
93
+ const allowed = await prop.vault.isAllowedPerp(0);
94
+ const fee = await prop.vault.calculateFee(toUsdc(5000), 500, false);
95
+ const paused = await prop.vault.isPaused();
96
+ const remainingCap = await prop.vault.getRemainingCap();
97
+ const isFa = await prop.vault.isFundedAccount("0x...");
98
+ const delegatedTo = await prop.vault.getDelegateBuilder("0x...");
99
+ ```
100
+
101
+ `getStats()` returns both operational metrics and current mutable vault config:
102
+
103
+ ```ts
104
+ type VaultStats = {
105
+ totalAssets: bigint;
106
+ sharePrice: bigint;
107
+ utilizationBps: bigint;
108
+ availableForAllocation: bigint;
109
+ maxAccountSize: bigint;
110
+ remainingCap: bigint;
111
+ paused: boolean;
112
+ feeMultiplier: number;
113
+ subscriptionFeeMultiplier: number;
114
+ totalShares: bigint;
115
+ totalAllocated: bigint;
116
+ owner: Address;
117
+ pendingOwner: Address;
118
+ relayer: Address;
119
+ protocolFeeReceiver: Address;
120
+ maxCap: bigint;
121
+ hypeForSpot: bigint;
122
+ factory: Address;
123
+ minBuilderShares: bigint;
124
+ builderLeverage: bigint;
125
+ protocolFeeBps: bigint;
126
+ minAccountSize: bigint;
127
+ maxAccountSizeLimit: bigint;
128
+ minDrawdownBps: number;
129
+ maxDrawdownBps: number;
130
+ maxUtilizationBps: bigint;
131
+ maxSingleAccountBps: bigint;
132
+ dailyDdDiscountBps: number;
133
+ };
134
+ ```
135
+
136
+ Writes:
137
+
138
+ ```ts
139
+ await prop.vault.approveUsdc(toUsdc(1000));
140
+ await prop.vault.deposit(toUsdc(1000));
141
+ await prop.vault.withdraw(shareAmount);
142
+
143
+ await prop.vault.registerBuilder();
144
+ await prop.vault.setDelegate("0x...");
145
+ await prop.vault.removeDelegate("0x...");
146
+
147
+ await prop.vault.activateFunded({
148
+ trader: "0x...",
149
+ accountSize: toUsdc(5000),
150
+ drawdownBps: 500,
151
+ dailyDrawdownEnabled: false,
152
+ });
153
+ ```
154
+
155
+ `activateFunded()` now reads `hypeForSpot()` from the vault when `hypeValue` is not supplied, so the SDK follows the current contract config instead of hardcoding a stale native value.
156
+
157
+ Admin writes covered by the SDK:
158
+
159
+ ```ts
160
+ await prop.vault.pause();
161
+ await prop.vault.unpause();
162
+ await prop.vault.setRelayer("0x...");
163
+ await prop.vault.setFactory("0x...");
164
+ await prop.vault.setProtocolFeeReceiver("0x...");
165
+ await prop.vault.setFeeMultiplier(120);
166
+ await prop.vault.setSubscriptionFeeMultiplier(150);
167
+ await prop.vault.setHypeForSpot(1_000_000_000_000_000n);
168
+ await prop.vault.setMaxCap(0n);
169
+ await prop.vault.setMinBuilderShares(toUsdc(10));
170
+ await prop.vault.setBuilderLeverage(10n);
171
+ await prop.vault.setProtocolFeeBps(1000n);
172
+ await prop.vault.setMinAccountSize(toUsdc(10));
173
+ await prop.vault.setMaxAccountSize(toUsdc(50_000));
174
+ await prop.vault.setMinDrawdownBps(500);
175
+ await prop.vault.setMaxDrawdownBps(2500);
176
+ await prop.vault.setMaxUtilizationBps(8000n);
177
+ await prop.vault.setMaxSingleAccountBps(1000n);
178
+ await prop.vault.setDailyDdDiscountBps(2000);
179
+ await prop.vault.addAllowedPerp(4);
180
+ await prop.vault.removeAllowedPerp(4);
181
+ await prop.vault.transferOwnership("0x...");
182
+ await prop.vault.acceptOwnership();
183
+ ```
184
+
185
+ ### `prop.account`
186
+
187
+ Reads:
188
+
189
+ ```ts
190
+ const details = await prop.account.getDetails("0xAccount");
191
+ const state = await prop.account.getState("0xAccount");
192
+ const trailing = await prop.account.getTrailingFloor("0xAccount");
193
+ const daily = await prop.account.getDailyFloor("0xAccount");
194
+ const canWithdraw = await prop.account.canWithdrawProfit("0xAccount");
195
+ const subActive = await prop.account.isSubscriptionActive("0xAccount");
196
+ const checkpoints = await prop.account.getCheckpoints("0xAccount");
197
+ const pending = await prop.account.getPendingWithdrawal("0xAccount");
198
+ const config = await prop.account.getAccountConfig("0xAccount");
199
+ ```
200
+
201
+ `getDetails()` includes the current per-account `subscriptionFeeMultiplier`, exposes `checkpointIndex`, and normalizes the on-chain checkpoint ring buffer into chronological order.
202
+
203
+ Writes:
204
+
205
+ ```ts
206
+ await prop.account.setApiWallet("0xAccount", "0xWallet");
207
+ await prop.account.setRiskEngineWallet("0xAccount", "0xWallet");
208
+ await prop.account.requestWithdrawProfit("0xAccount");
209
+ await prop.account.cancelSubscription("0xAccount");
210
+ await prop.account.cancelStuckWithdrawal("0xAccount");
211
+
212
+ await prop.account.checkpoint("0xAccount");
213
+ await prop.account.initiateClose("0xAccount");
214
+ await prop.account.initiateCloseForPerp("0xAccount", 0);
215
+ await prop.account.initiateCloseAdmin("0xAccount");
216
+ await prop.account.closeAndReturn("0xAccount");
217
+ await prop.account.finalizeReturn("0xAccount");
218
+ await prop.account.claimWithdrawal("0xAccount");
219
+ await prop.account.claimSubscriptionFee("0xAccount");
220
+ await prop.account.rescueStuckFunds("0xAccount");
221
+ ```
222
+
223
+ `setRiskEngineWallet()` maps to the current contract behavior in which the same method is used by the protocol relayer and by the builder; on-chain access control decides which of `riskEngineWallet` or `builderRiskEngineWallet` is updated.
224
+
225
+ ### `prop.factory`
226
+
227
+ Canonical on-chain surface of the current factory:
228
+
229
+ - `predictFundedAccountAddress(owner, builder)`
230
+ - `allAccounts(index)`
231
+ - `allAccountsIndex(account)`
232
+ - `traderBuilderNonce(owner, builder)`
233
+ - public config getters such as `implementation`, `owner`, `pendingOwner`, `usdc`, and `vault`
234
+
235
+ SDK helpers:
236
+
237
+ ```ts
238
+ const predicted = await prop.factory.predictAddress(owner, builder);
239
+ const nonce = await prop.factory.getTraderBuilderNonce(owner, builder);
240
+ const account0 = await prop.factory.getAccountAt(0n);
241
+ const index = await prop.factory.getAccountIndex(account0);
242
+ const exists = await prop.factory.hasAccount(account0);
243
+ const safeIndex = await prop.factory.findAccountIndex(account0);
244
+ const cfg = await prop.factory.getConfig();
245
+ ```
246
+
247
+ For backwards compatibility, the SDK still exposes:
248
+
249
+ ```ts
250
+ const count = await prop.factory.getAccountsCount();
251
+ const page = await prop.factory.getAccounts(0n, 10n);
252
+ ```
253
+
254
+ These are now implemented in the SDK by probing `allAccounts(index)` because the current Solidity factory does not expose explicit `getAccountsCount()` or paginated `getAccounts()` functions.
255
+
256
+ ### `prop.indexer`
257
+
258
+ GraphQL queries:
259
+
260
+ ```ts
261
+ await prop.indexer.getVault();
262
+ await prop.indexer.getLP("0x...");
263
+ await prop.indexer.getLPDeposits("0x...", { limit: 20, offset: 0 });
264
+ await prop.indexer.getLPWithdrawals("0x...");
265
+ await prop.indexer.getBuilder("0x...");
266
+ await prop.indexer.getBuilder("0x...", "0xVault...");
267
+ await prop.indexer.getDelegates("0x...");
268
+ await prop.indexer.getDelegates("0x...", "0xVault...");
269
+ await prop.indexer.getAccount("0x...");
270
+ await prop.indexer.getAccountsByOwner("0x...");
271
+ await prop.indexer.getAccountsByBuilder("0x...");
272
+ await prop.indexer.getAccountsByBuilder("0x...", "0xVault...");
273
+ await prop.indexer.getAccountsByState("ACTIVE");
274
+ await prop.indexer.getAccountsByStates(["ACTIVE", "CLOSING"]);
275
+ await prop.indexer.getAccountsUpdatedSince("1709500000");
276
+ await prop.indexer.getCheckpoints("0x...");
277
+ await prop.indexer.getWithdrawals("0x...");
278
+ await prop.indexer.getSubscriptionPayments("0x...");
279
+ await prop.indexer.getEvents("0x...");
280
+ await prop.indexer.getEventsByType("Initialized", 5);
281
+ await prop.indexer.getProtocolConfig();
282
+ await prop.indexer.getMainVaultConfig();
283
+ await prop.indexer.getFactoryConfig();
284
+ await prop.indexer.getConfigChanges({ field: "protocolFeeBps", limit: 20 });
285
+ await prop.indexer.getVaultDayData();
286
+ await prop.indexer.getVaultDayData("0xVault...", { limit: 30 });
287
+ await prop.indexer.getLPDayData("0x...");
288
+ await prop.indexer.getAllowedPerps();
289
+ await prop.indexer.getAllowedPerps("0xVault...");
290
+ ```
291
+
292
+ When `vaultAddress` is omitted, `getVault()`, `getMainVaultConfig()`, `getBuilder()`, `getDelegates()`, `getAccountsByBuilder()`, `getVaultDayData()`, and `getAllowedPerps()` default to the active vault from `ProtocolConfig`.
293
+ This is a convenience default only; production callers can pass `vaultAddress` explicitly to avoid hidden address resolution.
294
+
295
+ When `factoryAddress` is omitted, `getFactoryConfig()` defaults to the active factory from `ProtocolConfig`.
296
+ This is a convenience default only; production callers can pass `factoryAddress` explicitly to avoid hidden address resolution.
297
+
298
+ The SDK indexer layer follows `envio-funded/schema.graphql`, not the on-chain enum surface directly. That means:
299
+
300
+ - indexer account states start at `INITIALIZED`, not `UNINITIALIZED`
301
+ - `breachType` includes `POSITION_LEVERAGE`
302
+ - the SDK exposes address-keyed config/history entities like `MainVaultConfig`, `FactoryConfig`, `ConfigChange`, and `VaultDayData`
303
+ - `Vault` and `FundedAccount` GraphQL objects expose extra indexed fields such as `totalAllocated`, `totalShares`, `subscriptionFeeMultiplier`, `lastWithdrawalId`, `closePendingSubFee`, `closePendingProfit`, `cumulativeActivationFeesToVault`, `cumulativeCancelledProfit`, `cumulativeProtocolFeesRouted`, `cumulativeHypercoreFees`, `cumulativeRescuedFunds`, `protocolFeeBps`, and `protocolFeeReceiver`
304
+ - `LiquidityProviderDayData` exists in the current schema and is wrapped by the SDK, but the current indexer handlers do not populate it yet, so queries may legitimately return empty arrays
305
+
306
+ Subscriptions:
307
+
308
+ ```ts
309
+ const unsubActive = prop.indexer.subscribeActiveAccounts((accounts) => {});
310
+ const unsubAccount = prop.indexer.subscribeAccount("0x...", (account) => {});
311
+ const unsubEvents = prop.indexer.subscribeAccountEvents("0x...", (events) => {});
312
+ const unsubStates = prop.indexer.subscribeStateChanges((accounts) => {});
313
+ ```
314
+
315
+ Call `prop.disconnect()` to close all WebSocket state.
316
+
317
+ ## Utility Math
318
+
319
+ The utility layer mirrors the current contract formulas.
320
+
321
+ Decimals:
322
+
323
+ ```ts
324
+ toUsdc(100); // 100_000_000n
325
+ fromUsdc(100_000_000n); // 100
326
+ to8Dec(100_000_000n); // 10_000_000_000n
327
+ toUsdc6(10_000_000_000n); // 100_000_000n
328
+ ```
329
+
330
+ Activation fee:
331
+
332
+ ```ts
333
+ activationFee(toUsdc(5000), 500, false, 120); // 300 USDC
334
+ activationFee(toUsdc(5000), 500, true, 120, 2000); // 240 USDC
335
+ ```
336
+
337
+ Formula:
338
+
339
+ ```text
340
+ accountSize * drawdownBps * feeMultiplier / 1_000_000
341
+ ```
342
+
343
+ The helper then rounds up to whole USDC units, matching the current contract behavior.
344
+
345
+ If daily drawdown is enabled, the helper applies `dailyDdDiscountBps`:
346
+
347
+ ```text
348
+ fee * (10000 - dailyDdDiscountBps) / 10000
349
+ ```
350
+
351
+ `activationFee()` defaults `dailyDdDiscountBps` from `src/constants.ts`.
352
+ For exact live parity with a deployed vault, pass `vault.getStats().dailyDdDiscountBps`.
353
+
354
+ Subscription fee:
355
+
356
+ ```ts
357
+ subscriptionFee(toUsdc(5000), 150); // 75 USDC
358
+ ```
359
+
360
+ Canonical formula:
361
+
362
+ ```text
363
+ accountSize * subscriptionFeeMultiplier / 10000
364
+ ```
365
+
366
+ The helper rounds the result up to whole USDC units.
367
+
368
+ The helper still accepts the legacy 4-argument signature for compatibility, but drawdown and daily-DD no longer affect the result because the current contracts do not use them in subscription pricing.
369
+
370
+ Profit split:
371
+
372
+ ```ts
373
+ const { owner, vault, protocol } = splitProfit(toUsdc(1000));
374
+ // owner = 800
375
+ // vault = 130
376
+ // protocol = 70
377
+ ```
378
+
379
+ State labels:
380
+
381
+ ```ts
382
+ stateToLabel(AccountState.Uninitialized); // "UNINITIALIZED"
383
+ labelToState("CLOSING"); // AccountState.Closing
384
+ ```
385
+
386
+ ## Defaults vs Dynamic Config
387
+
388
+ Two categories exist in the SDK:
389
+
390
+ - Static source defaults exported from `src/constants.ts`.
391
+ - Mutable on-chain config read from `MainVault`.
392
+
393
+ Examples of values that can change on-chain through admin setters:
394
+
395
+ - `feeMultiplier`
396
+ - `subscriptionFeeMultiplier`
397
+ - `MIN_BUILDER_SHARES`
398
+ - `BUILDER_LEVERAGE`
399
+ - `PROTOCOL_FEE_BPS`
400
+ - `MIN_ACCOUNT_SIZE`
401
+ - `MAX_ACCOUNT_SIZE`
402
+ - `MIN_DRAWDOWN_BPS`
403
+ - `MAX_DRAWDOWN_BPS`
404
+ - `MAX_UTILIZATION_BPS`
405
+ - `MAX_SINGLE_ACCOUNT_BPS`
406
+ - `dailyDdDiscountBps`
407
+ - `hypeForSpot`
408
+ - `maxCap`
409
+
410
+ Best practice:
411
+
412
+ - Use exported constants for local validation defaults and tests.
413
+ - Use `vault.getStats()` before showing risk, pricing, or activation constraints to users.
414
+
415
+ ## Testing
416
+
417
+ ```bash
418
+ npm run check
419
+ npm run test:unit
420
+ npm run test:reads
421
+ npm run test:lifecycle
422
+ npm run test:indexer
423
+ ```
424
+
425
+ Notes:
426
+
427
+ - `test:unit` is fully local.
428
+ - `test:reads`, `test:lifecycle`, and `test:indexer` require reachable RPC/GraphQL endpoints.
429
+ - Current Foundry tests in `/test` are still on an older contract API and should not be treated as SDK source of truth until updated.
430
+
431
+ ## File Layout
432
+
433
+ ```text
434
+ src/
435
+ sdk.ts
436
+ vault.ts
437
+ account.ts
438
+ factory.ts
439
+ indexer.ts
440
+ types.ts
441
+ constants.ts
442
+ utils.ts
443
+ index.ts
444
+ abi/
445
+ mainVault.ts
446
+ fundedAccount.ts
447
+ factory.ts
448
+ ```
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { normalizeCheckpoints } from "../account.js";
3
+ describe("normalizeCheckpoints", () => {
4
+ it("keeps linear order before the ring buffer wraps", () => {
5
+ const checkpoints = [
6
+ { accountValue: 100n, timestamp: 1n, profitable: false },
7
+ { accountValue: 110n, timestamp: 2n, profitable: true },
8
+ { accountValue: 120n, timestamp: 3n, profitable: true },
9
+ { accountValue: 0n, timestamp: 0n, profitable: false },
10
+ { accountValue: 0n, timestamp: 0n, profitable: false },
11
+ { accountValue: 0n, timestamp: 0n, profitable: false },
12
+ { accountValue: 0n, timestamp: 0n, profitable: false },
13
+ ];
14
+ expect(normalizeCheckpoints(checkpoints, 3, 3).map((cp) => cp.accountValue)).toEqual([100n, 110n, 120n]);
15
+ });
16
+ it("returns chronological order after the ring buffer wraps", () => {
17
+ const checkpoints = [
18
+ { accountValue: 800n, timestamp: 8n, profitable: true },
19
+ { accountValue: 200n, timestamp: 2n, profitable: false },
20
+ { accountValue: 300n, timestamp: 3n, profitable: false },
21
+ { accountValue: 400n, timestamp: 4n, profitable: false },
22
+ { accountValue: 500n, timestamp: 5n, profitable: true },
23
+ { accountValue: 600n, timestamp: 6n, profitable: true },
24
+ { accountValue: 700n, timestamp: 7n, profitable: true },
25
+ ];
26
+ expect(normalizeCheckpoints(checkpoints, 7, 1).map((cp) => cp.accountValue)).toEqual([
27
+ 200n,
28
+ 300n,
29
+ 400n,
30
+ 500n,
31
+ 600n,
32
+ 700n,
33
+ 800n,
34
+ ]);
35
+ });
36
+ });
@@ -0,0 +1 @@
1
+ import "dotenv/config";