@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,245 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ activationFee,
4
+ subscriptionFee,
5
+ splitProfit,
6
+ trailingFloor,
7
+ dailyFloor,
8
+ isTrailingBreached,
9
+ isDailyBreached,
10
+ fromUsdc,
11
+ toUsdc,
12
+ from8Dec,
13
+ to8Dec,
14
+ toUsdc6,
15
+ formatUsd,
16
+ stateToLabel,
17
+ labelToState,
18
+ } from "../utils.js";
19
+ import { AccountState } from "../types.js";
20
+
21
+ // ── Helpers ──────────────────────────────────────────
22
+
23
+ const usdc = (n: number) => BigInt(n) * 1_000_000n; // 6-dec USDC
24
+ const dec8 = (n: number) => BigInt(n) * 100_000_000n; // 8-dec internal
25
+
26
+ // ── Decimal conversions ──────────────────────────────
27
+
28
+ describe("decimal conversions", () => {
29
+ it("toUsdc converts number to 6-dec bigint", () => {
30
+ expect(toUsdc(100)).toBe(100_000_000n);
31
+ expect(toUsdc(0.5)).toBe(500_000n);
32
+ expect(toUsdc(5000)).toBe(5_000_000_000n);
33
+ });
34
+
35
+ it("fromUsdc converts 6-dec bigint to number", () => {
36
+ expect(fromUsdc(100_000_000n)).toBe(100);
37
+ expect(fromUsdc(500_000n)).toBe(0.5);
38
+ });
39
+
40
+ it("to8Dec scales 6-dec to 8-dec (*100)", () => {
41
+ expect(to8Dec(usdc(1000))).toBe(dec8(1000));
42
+ });
43
+
44
+ it("toUsdc6 scales 8-dec down to 6-dec (/100)", () => {
45
+ expect(toUsdc6(dec8(1000))).toBe(usdc(1000));
46
+ });
47
+
48
+ it("from8Dec converts 8-dec bigint to number", () => {
49
+ expect(from8Dec(dec8(5000))).toBe(5000);
50
+ expect(from8Dec(50_000_000n)).toBe(0.5);
51
+ });
52
+
53
+ it("roundtrip: toUsdc -> to8Dec -> toUsdc6", () => {
54
+ const original = toUsdc(1234);
55
+ const up = to8Dec(original);
56
+ const down = toUsdc6(up);
57
+ expect(down).toBe(original);
58
+ });
59
+ });
60
+
61
+ // ── Activation fee ───────────────────────────────────
62
+
63
+ describe("activationFee", () => {
64
+ const FM = 120; // current contract feeMultiplier
65
+
66
+ it("$5000 account, 500bps, no daily DD → $300", () => {
67
+ // 5000 * 500 * 120 / 1_000_000 = 300
68
+ expect(activationFee(usdc(5000), 500, false, FM)).toBe(usdc(300));
69
+ });
70
+
71
+ it("$5000 account, 500bps, daily DD → $240", () => {
72
+ // 300 * 8000 / 10000 = 240 (20% discount)
73
+ expect(activationFee(usdc(5000), 500, true, FM)).toBe(usdc(240));
74
+ });
75
+
76
+ it("$10000 account, 1000bps, no daily DD → $1200", () => {
77
+ // 10000 * 1000 * 120 / 1_000_000 = 1200
78
+ expect(activationFee(usdc(10000), 1000, false, FM)).toBe(usdc(1200));
79
+ });
80
+
81
+ it("$10000 account, 1000bps, daily DD → $960", () => {
82
+ // 1200 * 8000 / 10000 = 960 (20% discount)
83
+ expect(activationFee(usdc(10000), 1000, true, FM)).toBe(usdc(960));
84
+ });
85
+
86
+ it("min params: $100 account, 500bps → $6", () => {
87
+ // 100 * 500 * 120 / 1_000_000 = 6
88
+ const fee = activationFee(usdc(100), 500, false, FM);
89
+ expect(fee).toBe(usdc(6));
90
+ });
91
+ });
92
+
93
+ // ── Subscription fee ─────────────────────────────────
94
+
95
+ describe("subscriptionFee", () => {
96
+ const SFM = 150; // current contract subscriptionFeeMultiplier
97
+
98
+ it("$5000 account → $75/month", () => {
99
+ // 5000 * 150 / 10000 = 75
100
+ expect(subscriptionFee(usdc(5000), SFM)).toBe(usdc(75));
101
+ });
102
+
103
+ it("legacy signature is still accepted for compatibility", () => {
104
+ expect(subscriptionFee(usdc(5000), 500, true, SFM)).toBe(usdc(75));
105
+ });
106
+
107
+ it("$10000 account → $150/month", () => {
108
+ expect(subscriptionFee(usdc(10000), SFM)).toBe(usdc(150));
109
+ });
110
+ });
111
+
112
+ // ── Profit split ─────────────────────────────────────
113
+
114
+ describe("splitProfit", () => {
115
+ it("$1000 profit → 80% owner, 7% protocol, 13% vault", () => {
116
+ const { owner, vault, protocol } = splitProfit(usdc(1000));
117
+ expect(owner).toBe(usdc(800));
118
+ expect(protocol).toBe(usdc(70));
119
+ expect(vault).toBe(usdc(130));
120
+ });
121
+
122
+ it("sum of parts equals total", () => {
123
+ const profit = usdc(7777);
124
+ const { owner, vault, protocol } = splitProfit(profit);
125
+ expect(owner + vault + protocol).toBe(profit);
126
+ });
127
+
128
+ it("zero profit", () => {
129
+ const { owner, vault, protocol } = splitProfit(0n);
130
+ expect(owner).toBe(0n);
131
+ expect(vault).toBe(0n);
132
+ expect(protocol).toBe(0n);
133
+ });
134
+
135
+ it("small profit — rounding goes to vault (remainder)", () => {
136
+ // $1 = 1_000_000
137
+ // owner = 1_000_000 * 8000 / 10000 = 800_000
138
+ // protocol = 1_000_000 * 700 / 10000 = 70_000
139
+ // vault = 1_000_000 - 800_000 - 70_000 = 130_000
140
+ const { owner, vault, protocol } = splitProfit(usdc(1));
141
+ expect(owner).toBe(800_000n);
142
+ expect(protocol).toBe(70_000n);
143
+ expect(vault).toBe(130_000n);
144
+ });
145
+ });
146
+
147
+ // ── Drawdown floors ──────────────────────────────────
148
+
149
+ describe("trailingFloor", () => {
150
+ it("500bps = 5% drawdown from HWM", () => {
151
+ const hwm = dec8(10000); // $10,000 in 8-dec
152
+ expect(trailingFloor(hwm, 500)).toBe(dec8(9500));
153
+ });
154
+
155
+ it("1000bps = 10% drawdown", () => {
156
+ const hwm = dec8(5000);
157
+ expect(trailingFloor(hwm, 1000)).toBe(dec8(4500));
158
+ });
159
+ });
160
+
161
+ describe("dailyFloor", () => {
162
+ it("500bps → daily = 250bps = 2.5% from day start", () => {
163
+ const dsv = dec8(10000);
164
+ // dailyBps = 500 / 2 = 250
165
+ // floor = 10000 - 10000 * 250 / 10000 = 10000 - 250 = 9750
166
+ expect(dailyFloor(dsv, 500)).toBe(dec8(9750));
167
+ });
168
+
169
+ it("1000bps → daily = 500bps = 5%", () => {
170
+ const dsv = dec8(10000);
171
+ expect(dailyFloor(dsv, 1000)).toBe(dec8(9500));
172
+ });
173
+ });
174
+
175
+ describe("breach checks", () => {
176
+ const hwm = dec8(10000);
177
+ const dsv = dec8(10000);
178
+
179
+ it("isTrailingBreached — below floor → true", () => {
180
+ expect(isTrailingBreached(dec8(9499), hwm, 500)).toBe(true);
181
+ });
182
+
183
+ it("isTrailingBreached — at floor → false", () => {
184
+ expect(isTrailingBreached(dec8(9500), hwm, 500)).toBe(false);
185
+ });
186
+
187
+ it("isTrailingBreached — above floor → false", () => {
188
+ expect(isTrailingBreached(dec8(9501), hwm, 500)).toBe(false);
189
+ });
190
+
191
+ it("isDailyBreached — below floor → true", () => {
192
+ expect(isDailyBreached(dec8(9749), dsv, 500)).toBe(true);
193
+ });
194
+
195
+ it("isDailyBreached — at floor → false", () => {
196
+ expect(isDailyBreached(dec8(9750), dsv, 500)).toBe(false);
197
+ });
198
+ });
199
+
200
+ // ── Enum mapping ─────────────────────────────────────
201
+
202
+ describe("state enum mapping", () => {
203
+ it("stateToLabel maps all states", () => {
204
+ expect(stateToLabel(AccountState.Uninitialized)).toBe("UNINITIALIZED");
205
+ expect(stateToLabel(AccountState.Active)).toBe("ACTIVE");
206
+ expect(stateToLabel(AccountState.Closing)).toBe("CLOSING");
207
+ expect(stateToLabel(AccountState.Closed)).toBe("CLOSED");
208
+ expect(stateToLabel(AccountState.WithdrawalPending)).toBe("WITHDRAWAL_PENDING");
209
+ expect(stateToLabel(AccountState.Returning)).toBe("RETURNING");
210
+ expect(stateToLabel(AccountState.Initialized)).toBe("INITIALIZED");
211
+ });
212
+
213
+ it("labelToState reverses correctly", () => {
214
+ expect(labelToState("UNINITIALIZED")).toBe(AccountState.Uninitialized);
215
+ expect(labelToState("ACTIVE")).toBe(AccountState.Active);
216
+ expect(labelToState("CLOSED")).toBe(AccountState.Closed);
217
+ expect(labelToState("CLOSING")).toBe(AccountState.Closing);
218
+ });
219
+
220
+ it("roundtrip: state → label → state", () => {
221
+ for (const state of [
222
+ AccountState.Initialized,
223
+ AccountState.Active,
224
+ AccountState.WithdrawalPending,
225
+ AccountState.Closing,
226
+ AccountState.Returning,
227
+ AccountState.Closed,
228
+ ]) {
229
+ expect(labelToState(stateToLabel(state))).toBe(state);
230
+ }
231
+ });
232
+ });
233
+
234
+ // ── formatUsd ────────────────────────────────────────
235
+
236
+ describe("formatUsd", () => {
237
+ it("formats 6-dec bigint as USD", () => {
238
+ expect(formatUsd(usdc(1000))).toBe("$1,000.00");
239
+ expect(formatUsd(usdc(0))).toBe("$0.00");
240
+ });
241
+
242
+ it("formats with custom decimals", () => {
243
+ expect(formatUsd(dec8(5000), 8)).toBe("$5,000.00");
244
+ });
245
+ });
@@ -0,0 +1,399 @@
1
+ export const factoryAbi = [
2
+ {
3
+ "inputs": [
4
+ {
5
+ "internalType": "address",
6
+ "name": "_usdc",
7
+ "type": "address"
8
+ },
9
+ {
10
+ "internalType": "address",
11
+ "name": "_implementation",
12
+ "type": "address"
13
+ }
14
+ ],
15
+ "stateMutability": "nonpayable",
16
+ "type": "constructor"
17
+ },
18
+ {
19
+ "inputs": [],
20
+ "name": "FailedDeployment",
21
+ "type": "error"
22
+ },
23
+ {
24
+ "inputs": [
25
+ {
26
+ "internalType": "uint256",
27
+ "name": "balance",
28
+ "type": "uint256"
29
+ },
30
+ {
31
+ "internalType": "uint256",
32
+ "name": "needed",
33
+ "type": "uint256"
34
+ }
35
+ ],
36
+ "name": "InsufficientBalance",
37
+ "type": "error"
38
+ },
39
+ {
40
+ "anonymous": false,
41
+ "inputs": [
42
+ {
43
+ "indexed": true,
44
+ "internalType": "address",
45
+ "name": "vault",
46
+ "type": "address"
47
+ },
48
+ {
49
+ "indexed": true,
50
+ "internalType": "address",
51
+ "name": "owner",
52
+ "type": "address"
53
+ }
54
+ ],
55
+ "name": "FactoryConfigInitialized",
56
+ "type": "event"
57
+ },
58
+ {
59
+ "anonymous": false,
60
+ "inputs": [
61
+ {
62
+ "indexed": true,
63
+ "internalType": "address",
64
+ "name": "account",
65
+ "type": "address"
66
+ },
67
+ {
68
+ "indexed": true,
69
+ "internalType": "address",
70
+ "name": "owner",
71
+ "type": "address"
72
+ },
73
+ {
74
+ "indexed": true,
75
+ "internalType": "address",
76
+ "name": "builder",
77
+ "type": "address"
78
+ },
79
+ {
80
+ "indexed": false,
81
+ "internalType": "uint96",
82
+ "name": "accountSize",
83
+ "type": "uint96"
84
+ },
85
+ {
86
+ "indexed": false,
87
+ "internalType": "uint16",
88
+ "name": "drawdownBps",
89
+ "type": "uint16"
90
+ },
91
+ {
92
+ "indexed": false,
93
+ "internalType": "bool",
94
+ "name": "dailyDrawdownEnabled",
95
+ "type": "bool"
96
+ }
97
+ ],
98
+ "name": "FundedAccountCreated",
99
+ "type": "event"
100
+ },
101
+ {
102
+ "anonymous": false,
103
+ "inputs": [
104
+ {
105
+ "indexed": true,
106
+ "internalType": "address",
107
+ "name": "previousOwner",
108
+ "type": "address"
109
+ },
110
+ {
111
+ "indexed": true,
112
+ "internalType": "address",
113
+ "name": "newOwner",
114
+ "type": "address"
115
+ }
116
+ ],
117
+ "name": "OwnershipTransferStarted",
118
+ "type": "event"
119
+ },
120
+ {
121
+ "anonymous": false,
122
+ "inputs": [
123
+ {
124
+ "indexed": true,
125
+ "internalType": "address",
126
+ "name": "oldOwner",
127
+ "type": "address"
128
+ },
129
+ {
130
+ "indexed": true,
131
+ "internalType": "address",
132
+ "name": "newOwner",
133
+ "type": "address"
134
+ }
135
+ ],
136
+ "name": "OwnershipTransferred",
137
+ "type": "event"
138
+ },
139
+ {
140
+ "anonymous": false,
141
+ "inputs": [
142
+ {
143
+ "indexed": true,
144
+ "internalType": "address",
145
+ "name": "oldVault",
146
+ "type": "address"
147
+ },
148
+ {
149
+ "indexed": true,
150
+ "internalType": "address",
151
+ "name": "newVault",
152
+ "type": "address"
153
+ }
154
+ ],
155
+ "name": "VaultUpdated",
156
+ "type": "event"
157
+ },
158
+ {
159
+ "inputs": [],
160
+ "name": "acceptOwnership",
161
+ "outputs": [],
162
+ "stateMutability": "nonpayable",
163
+ "type": "function"
164
+ },
165
+ {
166
+ "inputs": [
167
+ {
168
+ "internalType": "uint256",
169
+ "name": "",
170
+ "type": "uint256"
171
+ }
172
+ ],
173
+ "name": "allAccounts",
174
+ "outputs": [
175
+ {
176
+ "internalType": "address",
177
+ "name": "",
178
+ "type": "address"
179
+ }
180
+ ],
181
+ "stateMutability": "view",
182
+ "type": "function"
183
+ },
184
+ {
185
+ "inputs": [
186
+ {
187
+ "internalType": "address",
188
+ "name": "",
189
+ "type": "address"
190
+ }
191
+ ],
192
+ "name": "allAccountsIndex",
193
+ "outputs": [
194
+ {
195
+ "internalType": "uint256",
196
+ "name": "",
197
+ "type": "uint256"
198
+ }
199
+ ],
200
+ "stateMutability": "view",
201
+ "type": "function"
202
+ },
203
+ {
204
+ "inputs": [
205
+ {
206
+ "internalType": "address",
207
+ "name": "accountOwner",
208
+ "type": "address"
209
+ },
210
+ {
211
+ "internalType": "address",
212
+ "name": "builder",
213
+ "type": "address"
214
+ },
215
+ {
216
+ "internalType": "uint96",
217
+ "name": "accountSize",
218
+ "type": "uint96"
219
+ },
220
+ {
221
+ "internalType": "uint16",
222
+ "name": "drawdownBps",
223
+ "type": "uint16"
224
+ },
225
+ {
226
+ "internalType": "bool",
227
+ "name": "dailyDrawdownEnabled",
228
+ "type": "bool"
229
+ },
230
+ {
231
+ "internalType": "uint16",
232
+ "name": "subscriptionFeeMultiplier",
233
+ "type": "uint16"
234
+ }
235
+ ],
236
+ "name": "createFundedAccount",
237
+ "outputs": [
238
+ {
239
+ "internalType": "address",
240
+ "name": "",
241
+ "type": "address"
242
+ }
243
+ ],
244
+ "stateMutability": "nonpayable",
245
+ "type": "function"
246
+ },
247
+ {
248
+ "inputs": [],
249
+ "name": "implementation",
250
+ "outputs": [
251
+ {
252
+ "internalType": "address",
253
+ "name": "",
254
+ "type": "address"
255
+ }
256
+ ],
257
+ "stateMutability": "view",
258
+ "type": "function"
259
+ },
260
+ {
261
+ "inputs": [],
262
+ "name": "owner",
263
+ "outputs": [
264
+ {
265
+ "internalType": "address",
266
+ "name": "",
267
+ "type": "address"
268
+ }
269
+ ],
270
+ "stateMutability": "view",
271
+ "type": "function"
272
+ },
273
+ {
274
+ "inputs": [],
275
+ "name": "pendingOwner",
276
+ "outputs": [
277
+ {
278
+ "internalType": "address",
279
+ "name": "",
280
+ "type": "address"
281
+ }
282
+ ],
283
+ "stateMutability": "view",
284
+ "type": "function"
285
+ },
286
+ {
287
+ "inputs": [
288
+ {
289
+ "internalType": "address",
290
+ "name": "accountOwner",
291
+ "type": "address"
292
+ },
293
+ {
294
+ "internalType": "address",
295
+ "name": "builder",
296
+ "type": "address"
297
+ }
298
+ ],
299
+ "name": "predictFundedAccountAddress",
300
+ "outputs": [
301
+ {
302
+ "internalType": "address",
303
+ "name": "",
304
+ "type": "address"
305
+ }
306
+ ],
307
+ "stateMutability": "view",
308
+ "type": "function"
309
+ },
310
+ {
311
+ "inputs": [
312
+ {
313
+ "internalType": "address",
314
+ "name": "account",
315
+ "type": "address"
316
+ }
317
+ ],
318
+ "name": "removeAccount",
319
+ "outputs": [],
320
+ "stateMutability": "nonpayable",
321
+ "type": "function"
322
+ },
323
+ {
324
+ "inputs": [
325
+ {
326
+ "internalType": "address",
327
+ "name": "_vault",
328
+ "type": "address"
329
+ }
330
+ ],
331
+ "name": "setVault",
332
+ "outputs": [],
333
+ "stateMutability": "nonpayable",
334
+ "type": "function"
335
+ },
336
+ {
337
+ "inputs": [
338
+ {
339
+ "internalType": "address",
340
+ "name": "",
341
+ "type": "address"
342
+ },
343
+ {
344
+ "internalType": "address",
345
+ "name": "",
346
+ "type": "address"
347
+ }
348
+ ],
349
+ "name": "traderBuilderNonce",
350
+ "outputs": [
351
+ {
352
+ "internalType": "uint256",
353
+ "name": "",
354
+ "type": "uint256"
355
+ }
356
+ ],
357
+ "stateMutability": "view",
358
+ "type": "function"
359
+ },
360
+ {
361
+ "inputs": [
362
+ {
363
+ "internalType": "address",
364
+ "name": "newOwner",
365
+ "type": "address"
366
+ }
367
+ ],
368
+ "name": "transferOwnership",
369
+ "outputs": [],
370
+ "stateMutability": "nonpayable",
371
+ "type": "function"
372
+ },
373
+ {
374
+ "inputs": [],
375
+ "name": "usdc",
376
+ "outputs": [
377
+ {
378
+ "internalType": "address",
379
+ "name": "",
380
+ "type": "address"
381
+ }
382
+ ],
383
+ "stateMutability": "view",
384
+ "type": "function"
385
+ },
386
+ {
387
+ "inputs": [],
388
+ "name": "vault",
389
+ "outputs": [
390
+ {
391
+ "internalType": "address",
392
+ "name": "",
393
+ "type": "address"
394
+ }
395
+ ],
396
+ "stateMutability": "view",
397
+ "type": "function"
398
+ }
399
+ ] as const;