@kehtus/proportion-sdk 0.1.1 → 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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/dist/abi/factory.d.ts +1 -0
  3. package/dist/abi/factory.d.ts.map +1 -0
  4. package/dist/abi/factory.js +1 -0
  5. package/dist/abi/factory.js.map +1 -0
  6. package/dist/abi/forwarder.d.ts +1 -0
  7. package/dist/abi/forwarder.d.ts.map +1 -0
  8. package/dist/abi/forwarder.js +1 -0
  9. package/dist/abi/forwarder.js.map +1 -0
  10. package/dist/abi/fundedAccount.d.ts +1 -0
  11. package/dist/abi/fundedAccount.d.ts.map +1 -0
  12. package/dist/abi/fundedAccount.js +1 -0
  13. package/dist/abi/fundedAccount.js.map +1 -0
  14. package/dist/abi/mainVault.d.ts +198 -16
  15. package/dist/abi/mainVault.d.ts.map +1 -0
  16. package/dist/abi/mainVault.js +253 -19
  17. package/dist/abi/mainVault.js.map +1 -0
  18. package/dist/account.d.ts +3 -2
  19. package/dist/account.d.ts.map +1 -0
  20. package/dist/account.js +46 -43
  21. package/dist/account.js.map +1 -0
  22. package/dist/base.d.ts +9 -0
  23. package/dist/base.d.ts.map +1 -0
  24. package/dist/base.js +19 -0
  25. package/dist/base.js.map +1 -0
  26. package/dist/constants.d.ts +5 -4
  27. package/dist/constants.d.ts.map +1 -0
  28. package/dist/constants.js +5 -4
  29. package/dist/constants.js.map +1 -0
  30. package/dist/factory.d.ts +1 -0
  31. package/dist/factory.d.ts.map +1 -0
  32. package/dist/factory.js +56 -10
  33. package/dist/factory.js.map +1 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/indexer.d.ts +8 -3
  39. package/dist/indexer.d.ts.map +1 -0
  40. package/dist/indexer.js +81 -136
  41. package/dist/indexer.js.map +1 -0
  42. package/dist/sdk.d.ts +1 -0
  43. package/dist/sdk.d.ts.map +1 -0
  44. package/dist/sdk.js +1 -0
  45. package/dist/sdk.js.map +1 -0
  46. package/dist/types.d.ts +5 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +1 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/utils.d.ts +8 -0
  51. package/dist/utils.d.ts.map +1 -0
  52. package/dist/utils.js +10 -0
  53. package/dist/utils.js.map +1 -0
  54. package/dist/vault.d.ts +7 -4
  55. package/dist/vault.d.ts.map +1 -0
  56. package/dist/vault.js +76 -51
  57. package/dist/vault.js.map +1 -0
  58. package/package.json +12 -1
  59. package/SDK_IMPROVEMENT_PLAN.md +0 -197
  60. package/arch.md +0 -455
  61. package/dist/__tests__/account.test.d.ts +0 -1
  62. package/dist/__tests__/account.test.js +0 -36
  63. package/dist/__tests__/indexer.test.d.ts +0 -1
  64. package/dist/__tests__/indexer.test.js +0 -230
  65. package/dist/__tests__/lifecycle.test.d.ts +0 -1
  66. package/dist/__tests__/lifecycle.test.js +0 -186
  67. package/dist/__tests__/reads.test.d.ts +0 -1
  68. package/dist/__tests__/reads.test.js +0 -184
  69. package/dist/__tests__/utils.test.d.ts +0 -1
  70. package/dist/__tests__/utils.test.js +0 -185
  71. package/src/__tests__/account.test.ts +0 -40
  72. package/src/__tests__/indexer.test.ts +0 -255
  73. package/src/__tests__/lifecycle.test.ts +0 -231
  74. package/src/__tests__/reads.test.ts +0 -208
  75. package/src/__tests__/utils.test.ts +0 -245
  76. package/src/abi/factory.ts +0 -417
  77. package/src/abi/forwarder.ts +0 -373
  78. package/src/abi/fundedAccount.ts +0 -1296
  79. package/src/abi/mainVault.ts +0 -1852
  80. package/src/account.ts +0 -325
  81. package/src/constants.ts +0 -51
  82. package/src/factory.ts +0 -157
  83. package/src/index.ts +0 -16
  84. package/src/indexer.ts +0 -636
  85. package/src/sdk.ts +0 -93
  86. package/src/types.ts +0 -421
  87. package/src/utils.ts +0 -143
  88. package/src/vault.ts +0 -479
  89. package/tsconfig.json +0 -19
  90. package/vitest.config.ts +0 -8
@@ -1,231 +0,0 @@
1
- import { describe, it, expect, beforeAll } from "vitest";
2
- import { createPublicClient, createWalletClient, http, parseEther, type Address } from "viem";
3
- import { privateKeyToAccount } from "viem/accounts";
4
- import { hyperEvm, ADDRESSES, HYPE_FOR_GAS } from "../constants.js";
5
- import { VaultModule } from "../vault.js";
6
- import { AccountModule } from "../account.js";
7
- import { FactoryModule } from "../factory.js";
8
- import { AccountState } from "../types.js";
9
- import { toUsdc } from "../utils.js";
10
- import "dotenv/config";
11
-
12
- // ── Config ───────────────────────────────────────────
13
-
14
- const PRIVATE_KEY = process.env.PRIVATE_KEY;
15
- if (!PRIVATE_KEY) throw new Error("PRIVATE_KEY required in .env for lifecycle tests");
16
-
17
- const RPC_URL = process.env.RPC_URL ?? hyperEvm.rpcUrls.default.http[0];
18
-
19
- // Test amounts — keep small
20
- const DEPOSIT_AMOUNT = toUsdc(200); // $200 USDC
21
- const ACCOUNT_SIZE = toUsdc(100); // $100 funded account
22
- const DRAWDOWN_BPS = 500; // 5%
23
- const DAILY_DD = false;
24
-
25
- // ── Setup ────────────────────────────────────────────
26
-
27
- const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`);
28
-
29
- const publicClient = createPublicClient({
30
- chain: hyperEvm,
31
- transport: http(RPC_URL),
32
- });
33
-
34
- const walletClient = createWalletClient({
35
- account,
36
- chain: hyperEvm,
37
- transport: http(RPC_URL),
38
- });
39
-
40
- const addresses = {
41
- vault: ADDRESSES.MAIN_VAULT as Address,
42
- usdc: ADDRESSES.USDC as Address,
43
- };
44
-
45
- const vault = new VaultModule(publicClient, walletClient, hyperEvm, addresses);
46
- const acc = new AccountModule(publicClient, walletClient, hyperEvm);
47
- const factory = new FactoryModule(publicClient, ADDRESSES.FACTORY as Address);
48
-
49
- let fundedAccountAddress: Address | null = null;
50
-
51
- async function waitForTx(hash: `0x${string}`) {
52
- const receipt = await publicClient.waitForTransactionReceipt({ hash, timeout: 60_000 });
53
- if (receipt.status !== "success") {
54
- throw new Error(`Transaction reverted: ${hash}`);
55
- }
56
- return receipt;
57
- }
58
-
59
- // ── Lifecycle (sequential) ───────────────────────────
60
-
61
- describe("lifecycle", { sequential: true }, () => {
62
- // ── 1. Approve USDC ────────────────────────────────
63
-
64
- it("1. approve USDC for vault", async () => {
65
- const hash = await vault.approveUsdc(DEPOSIT_AMOUNT);
66
- console.log(` approve tx: ${hash}`);
67
- const receipt = await waitForTx(hash);
68
- expect(receipt.status).toBe("success");
69
- });
70
-
71
- // ── 2. Deposit to vault ────────────────────────────
72
-
73
- it("2. deposit USDC to vault", async () => {
74
- const beforePos = await vault.getLPPosition(account.address);
75
- const hash = await vault.deposit(DEPOSIT_AMOUNT);
76
- console.log(` deposit tx: ${hash}`);
77
- const receipt = await waitForTx(hash);
78
- expect(receipt.status).toBe("success");
79
-
80
- const afterPos = await vault.getLPPosition(account.address);
81
- expect(afterPos.shares).toBeGreaterThan(beforePos.shares);
82
- console.log(` shares: ${beforePos.shares} → ${afterPos.shares}`);
83
- });
84
-
85
- // ── 3. Register builder ────────────────────────────
86
-
87
- it("3. register as builder (skip if already registered)", async () => {
88
- const info = await vault.getBuilderInfo(account.address);
89
- if (info.active) {
90
- console.log(" builder already registered, skipping");
91
- return;
92
- }
93
-
94
- const hash = await vault.registerBuilder();
95
- console.log(` registerBuilder tx: ${hash}`);
96
- const receipt = await waitForTx(hash);
97
- expect(receipt.status).toBe("success");
98
-
99
- const after = await vault.getBuilderInfo(account.address);
100
- expect(after.active).toBe(true);
101
- });
102
-
103
- // ── 4. Activate funded account ─────────────────────
104
-
105
- it("4. activate funded account", async () => {
106
- // Predict address first
107
- fundedAccountAddress = await factory.predictAddress(account.address, account.address);
108
- console.log(` predicted account: ${fundedAccountAddress}`);
109
-
110
- const hash = await vault.activateFunded({
111
- trader: account.address,
112
- accountSize: ACCOUNT_SIZE,
113
- drawdownBps: DRAWDOWN_BPS,
114
- dailyDrawdownEnabled: DAILY_DD,
115
- });
116
- console.log(` activateFunded tx: ${hash}`);
117
- const receipt = await waitForTx(hash);
118
- expect(receipt.status).toBe("success");
119
- });
120
-
121
- // ── 5. Read account details ────────────────────────
122
-
123
- it("5. read created account details", async () => {
124
- expect(fundedAccountAddress).not.toBeNull();
125
-
126
- const details = await acc.getDetails(fundedAccountAddress!);
127
- console.log(` state: ${AccountState[details.state]}`);
128
- console.log(` accountSize: ${details.accountSize}`);
129
- console.log(` drawdownBps: ${details.drawdownBps}`);
130
- console.log(` owner: ${details.owner}`);
131
-
132
- expect(details.state).toBe(AccountState.Active);
133
- expect(details.owner.toLowerCase()).toBe(account.address.toLowerCase());
134
- expect(details.accountSize).toBe(ACCOUNT_SIZE);
135
- expect(details.drawdownBps).toBe(DRAWDOWN_BPS);
136
- expect(details.dailyDrawdownEnabled).toBe(DAILY_DD);
137
- });
138
-
139
- // ── 6. Set API wallet ──────────────────────────────
140
-
141
- it("6. set API wallet", async () => {
142
- expect(fundedAccountAddress).not.toBeNull();
143
-
144
- const newApiWallet = "0x0000000000000000000000000000000000000042" as Address;
145
- const hash = await acc.setApiWallet(fundedAccountAddress!, newApiWallet);
146
- console.log(` setApiWallet tx: ${hash}`);
147
- const receipt = await waitForTx(hash);
148
- expect(receipt.status).toBe("success");
149
-
150
- const details = await acc.getDetails(fundedAccountAddress!);
151
- expect(details.apiWallet.toLowerCase()).toBe(newApiWallet.toLowerCase());
152
- });
153
-
154
- // ── 7. Checkpoint (soft fail — 24h gate) ───────────
155
-
156
- it("7. checkpoint (may revert if < 24h)", async () => {
157
- expect(fundedAccountAddress).not.toBeNull();
158
-
159
- try {
160
- const hash = await acc.checkpoint(fundedAccountAddress!);
161
- console.log(` checkpoint tx: ${hash}`);
162
- const receipt = await waitForTx(hash);
163
- expect(receipt.status).toBe("success");
164
- } catch (e: unknown) {
165
- const msg = e instanceof Error ? e.message : String(e);
166
- console.log(` [EXPECTED] checkpoint reverted: ${msg.slice(0, 120)}`);
167
- // Not a failure — time-gated operation
168
- }
169
- });
170
-
171
- // ── 8. Initiate close ──────────────────────────────
172
-
173
- it("8. initiate close", async () => {
174
- expect(fundedAccountAddress).not.toBeNull();
175
-
176
- const hash = await acc.initiateClose(fundedAccountAddress!);
177
- console.log(` initiateClose tx: ${hash}`);
178
- const receipt = await waitForTx(hash);
179
- expect(receipt.status).toBe("success");
180
-
181
- const state = await acc.getState(fundedAccountAddress!);
182
- expect(state).toBe(AccountState.Closing);
183
- });
184
-
185
- // ── 9. Close and return (soft fail — open positions or orders may still block) ─
186
-
187
- it("9. closeAndReturn (may revert if < 3 days)", async () => {
188
- expect(fundedAccountAddress).not.toBeNull();
189
-
190
- try {
191
- const hash = await acc.closeAndReturn(fundedAccountAddress!);
192
- console.log(` closeAndReturn tx: ${hash}`);
193
- const receipt = await waitForTx(hash);
194
- expect(receipt.status).toBe("success");
195
- } catch (e: unknown) {
196
- const msg = e instanceof Error ? e.message : String(e);
197
- console.log(` [EXPECTED] closeAndReturn reverted (positions/orders still block return): ${msg.slice(0, 120)}`);
198
- }
199
- });
200
-
201
- // ── 10. Finalize return (soft fail) ────────────────
202
-
203
- it("10. finalizeReturn (may revert if not in Returning state)", async () => {
204
- expect(fundedAccountAddress).not.toBeNull();
205
-
206
- try {
207
- const hash = await acc.finalizeReturn(fundedAccountAddress!);
208
- console.log(` finalizeReturn tx: ${hash}`);
209
- const receipt = await waitForTx(hash);
210
- expect(receipt.status).toBe("success");
211
- } catch (e: unknown) {
212
- const msg = e instanceof Error ? e.message : String(e);
213
- console.log(` [EXPECTED] finalizeReturn reverted: ${msg.slice(0, 120)}`);
214
- }
215
- });
216
-
217
- // ── 11. Withdraw LP shares ─────────────────────────
218
-
219
- it("11. withdraw LP shares", async () => {
220
- const pos = await vault.getLPPosition(account.address);
221
- if (pos.withdrawableShares === 0n) {
222
- console.log(" no withdrawable shares, skipping");
223
- return;
224
- }
225
-
226
- const hash = await vault.withdraw(pos.withdrawableShares);
227
- console.log(` withdraw tx: ${hash}`);
228
- const receipt = await waitForTx(hash);
229
- expect(receipt.status).toBe("success");
230
- });
231
- });
@@ -1,208 +0,0 @@
1
- import { describe, it, expect, beforeAll } from "vitest";
2
- import { createPublicClient, http, type Address } from "viem";
3
- import { hyperEvm, ADDRESSES } from "../constants.js";
4
- import { VaultModule } from "../vault.js";
5
- import { AccountModule } from "../account.js";
6
- import { FactoryModule } from "../factory.js";
7
- import { AccountState } from "../types.js";
8
- import { activationFee } from "../utils.js";
9
-
10
- // ── Setup ────────────────────────────────────────────
11
-
12
- const publicClient = createPublicClient({
13
- chain: hyperEvm,
14
- transport: http(hyperEvm.rpcUrls.default.http[0]),
15
- });
16
-
17
- const vault = new VaultModule(publicClient, undefined, hyperEvm, {
18
- vault: ADDRESSES.MAIN_VAULT as Address,
19
- usdc: ADDRESSES.USDC as Address,
20
- });
21
-
22
- const factory = new FactoryModule(publicClient, ADDRESSES.FACTORY as Address);
23
- const account = new AccountModule(publicClient, undefined, hyperEvm);
24
-
25
- // We'll find an existing account to test reads against
26
- let existingAccount: Address | null = null;
27
-
28
- beforeAll(async () => {
29
- try {
30
- const count = await factory.getAccountsCount();
31
- if (count > 0n) {
32
- const accounts = await factory.getAccounts(0n, 1n);
33
- existingAccount = accounts[0];
34
- }
35
- } catch {
36
- // factory enumeration may be unavailable on empty or unreachable networks
37
- }
38
- });
39
-
40
- // ── Vault reads ──────────────────────────────────────
41
-
42
- describe("vault reads", () => {
43
- it("getStats returns valid vault stats", async () => {
44
- const stats = await vault.getStats();
45
- expect(stats.totalAssets).toBeTypeOf("bigint");
46
- expect(stats.sharePrice).toBeTypeOf("bigint");
47
- expect(stats.utilizationBps).toBeTypeOf("bigint");
48
- expect(stats.availableForAllocation).toBeTypeOf("bigint");
49
- expect(stats.maxAccountSize).toBeTypeOf("bigint");
50
- expect(stats.remainingCap).toBeTypeOf("bigint");
51
- expect(typeof stats.paused).toBe("boolean");
52
- });
53
-
54
- it("getLPPosition returns zeroes for random address", async () => {
55
- const pos = await vault.getLPPosition("0x0000000000000000000000000000000000000001");
56
- expect(pos.shares).toBe(0n);
57
- expect(pos.sharesValue).toBe(0n);
58
- });
59
-
60
- it("getAllowedPerps returns array of numbers", async () => {
61
- const perps = await vault.getAllowedPerps();
62
- expect(Array.isArray(perps)).toBe(true);
63
- for (const p of perps) {
64
- expect(typeof p).toBe("number");
65
- }
66
- });
67
-
68
- it("isAllowedPerp returns boolean", async () => {
69
- const result = await vault.isAllowedPerp(0);
70
- expect(typeof result).toBe("boolean");
71
- });
72
-
73
- it("isPaused returns boolean", async () => {
74
- const paused = await vault.isPaused();
75
- expect(typeof paused).toBe("boolean");
76
- });
77
-
78
- it("calculateFee matches utils.activationFee (cross-validation)", async () => {
79
- const size = 5_000_000_000n; // $5000 USDC (6-dec)
80
- const bps = 500;
81
-
82
- const stats = await vault.getStats();
83
- const fm = stats.feeMultiplier;
84
-
85
- const onChain = await vault.calculateFee(size, bps, false);
86
- const local = activationFee(size, bps, false, fm);
87
- expect(local).toBe(onChain);
88
-
89
- const onChainDD = await vault.calculateFee(size, bps, true);
90
- const localDD = activationFee(size, bps, true, fm);
91
- expect(localDD).toBe(onChainDD);
92
- });
93
- });
94
-
95
- // ── Factory reads ────────────────────────────────────
96
-
97
- describe("factory reads", () => {
98
- it("getAccountsCount returns bigint >= 0", async () => {
99
- const count = await factory.getAccountsCount();
100
- expect(count).toBeTypeOf("bigint");
101
- expect(count >= 0n).toBe(true);
102
- });
103
-
104
- it("getAccounts returns array of addresses", async () => {
105
- const count = await factory.getAccountsCount();
106
- if (count === 0n) return; // skip if no accounts
107
-
108
- const accounts = await factory.getAccounts(0n, BigInt(Math.min(Number(count), 5)));
109
- expect(Array.isArray(accounts)).toBe(true);
110
- expect(accounts.length).toBeGreaterThan(0);
111
- for (const addr of accounts) {
112
- expect(addr).toMatch(/^0x[0-9a-fA-F]{40}$/);
113
- }
114
- });
115
-
116
- it("predictAddress returns valid address", async () => {
117
- const owner = "0x0000000000000000000000000000000000000001" as Address;
118
- const builder = "0x0000000000000000000000000000000000000002" as Address;
119
- const predicted = await factory.predictAddress(owner, builder);
120
- expect(predicted).toMatch(/^0x[0-9a-fA-F]{40}$/);
121
- });
122
-
123
- it("getTraderBuilderNonce returns bigint", async () => {
124
- const owner = "0x0000000000000000000000000000000000000001" as Address;
125
- const builder = "0x0000000000000000000000000000000000000002" as Address;
126
- const nonce = await factory.getTraderBuilderNonce(owner, builder);
127
- expect(nonce).toBeTypeOf("bigint");
128
- });
129
- });
130
-
131
- // ── Account reads ────────────────────────────────────
132
-
133
- describe("account reads", () => {
134
- it("getDetails returns full structure for existing account", async () => {
135
- if (!existingAccount) {
136
- console.log(" [SKIP] no existing accounts on-chain");
137
- return;
138
- }
139
-
140
- const details = await account.getDetails(existingAccount);
141
- expect(details.address).toBe(existingAccount);
142
- expect(details.owner).toMatch(/^0x[0-9a-fA-F]{40}$/);
143
- expect(details.builder).toMatch(/^0x[0-9a-fA-F]{40}$/);
144
- expect(details.accountSize).toBeTypeOf("bigint");
145
- expect(typeof details.drawdownBps).toBe("number");
146
- expect(typeof details.dailyDrawdownEnabled).toBe("boolean");
147
- expect(typeof details.subscriptionFeeMultiplier).toBe("number");
148
- expect(details.highWaterMark).toBeTypeOf("bigint");
149
- expect(details.dayStartValue).toBeTypeOf("bigint");
150
- expect(typeof details.checkpointIndex).toBe("number");
151
- expect(typeof details.subscriptionCancelled).toBe("boolean");
152
- expect(typeof details.isSubscriptionActive).toBe("boolean");
153
- expect(typeof details.canWithdrawProfit).toBe("boolean");
154
- expect(Array.isArray(details.checkpoints)).toBe(true);
155
-
156
- // State should be a valid enum value
157
- expect(details.state).toBeGreaterThanOrEqual(AccountState.Uninitialized);
158
- expect(details.state).toBeLessThanOrEqual(AccountState.Closed);
159
- });
160
-
161
- it("getState returns valid enum", async () => {
162
- if (!existingAccount) return;
163
- const state = await account.getState(existingAccount);
164
- expect(state).toBeGreaterThanOrEqual(AccountState.Uninitialized);
165
- expect(state).toBeLessThanOrEqual(AccountState.Closed);
166
- });
167
-
168
- it("getTrailingFloor returns bigint", async () => {
169
- if (!existingAccount) return;
170
- const floor = await account.getTrailingFloor(existingAccount);
171
- expect(floor).toBeTypeOf("bigint");
172
- });
173
-
174
- it("getDailyFloor returns bigint", async () => {
175
- if (!existingAccount) return;
176
- const floor = await account.getDailyFloor(existingAccount);
177
- expect(floor).toBeTypeOf("bigint");
178
- });
179
-
180
- it("canWithdrawProfit returns boolean", async () => {
181
- if (!existingAccount) return;
182
- const can = await account.canWithdrawProfit(existingAccount);
183
- expect(typeof can).toBe("boolean");
184
- });
185
-
186
- it("isSubscriptionActive returns boolean", async () => {
187
- if (!existingAccount) return;
188
- const active = await account.isSubscriptionActive(existingAccount);
189
- expect(typeof active).toBe("boolean");
190
- });
191
-
192
- it("getCheckpoints returns array", async () => {
193
- if (!existingAccount) return;
194
- const checkpoints = await account.getCheckpoints(existingAccount);
195
- expect(Array.isArray(checkpoints)).toBe(true);
196
- for (const cp of checkpoints) {
197
- expect(cp.accountValue).toBeTypeOf("bigint");
198
- expect(typeof cp.timestamp).toBe("number");
199
- expect(typeof cp.profitable).toBe("boolean");
200
- }
201
- });
202
-
203
- it("getPendingWithdrawal returns struct", async () => {
204
- if (!existingAccount) return;
205
- const pw = await account.getPendingWithdrawal(existingAccount);
206
- expect(pw.profit).toBeTypeOf("bigint");
207
- });
208
- });
@@ -1,245 +0,0 @@
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
- });