@wzrd_sol/sdk 0.1.0 → 0.1.2

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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Unit tests for SDK instruction builders and helpers.
3
+ *
4
+ * Tests the sync / pure parts of instructions.ts, pda.ts, accounts.ts, and constants.ts
5
+ * without requiring an RPC Connection.
6
+ */
7
+ export {};
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Unit tests for SDK instruction builders and helpers.
3
+ *
4
+ * Tests the sync / pure parts of instructions.ts, pda.ts, accounts.ts, and constants.ts
5
+ * without requiring an RPC Connection.
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+ import { PublicKey, SystemProgram } from '@solana/web3.js';
9
+ import { anchorDisc, createAtaIdempotentIx, createAddLiquidityIx, createRemoveLiquidityIx, DLMM_PROGRAM_ID, } from './instructions.js';
10
+ import { PROGRAM_ID, MAINNET_PROGRAM_ID, DEVNET_PROGRAM_ID, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, PROTOCOL_STATE_SEED, MARKET_VAULT_SEED, MARKET_POSITION_SEED, GLOBAL_ROOT_SEED, CLAIM_STATE_GLOBAL_SEED, CHANNEL_CONFIG_V2_SEED, } from './constants.js';
11
+ import { getProtocolStatePDA, getMarketVaultPDA, getUserPositionPDA, getGlobalRootConfigPDA, getClaimStatePDA, getChannelConfigV2PDA, getAta, } from './pda.js';
12
+ import { parseMarketVault, parseProtocolState, parseUserMarketPosition, } from './accounts.js';
13
+ // ── Test fixtures ────────────────────────────────────────
14
+ const DUMMY_KEY_A = new PublicKey('11111111111111111111111111111111');
15
+ const DUMMY_KEY_B = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
16
+ const CCM_MINT = new PublicKey('Dxk8mAb3C7AM8JN6tAJfVuSja5yidhZM5sEKW3SRX2BM');
17
+ const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
18
+ // ── anchorDisc ──────────────────────────────────────────
19
+ describe('anchorDisc', () => {
20
+ it('produces an 8-byte Buffer', async () => {
21
+ const disc = await anchorDisc('deposit_market');
22
+ expect(disc).toBeInstanceOf(Buffer);
23
+ expect(disc.length).toBe(8);
24
+ });
25
+ it('is deterministic — same name gives same bytes', async () => {
26
+ const a = await anchorDisc('deposit_market');
27
+ const b = await anchorDisc('deposit_market');
28
+ expect(a.equals(b)).toBe(true);
29
+ });
30
+ it('different names produce different discriminators', async () => {
31
+ const deposit = await anchorDisc('deposit_market');
32
+ const settle = await anchorDisc('settle_market');
33
+ expect(deposit.equals(settle)).toBe(false);
34
+ });
35
+ it('matches SHA-256("global:<name>")[0..8]', async () => {
36
+ // We manually verify deposit_market against the Node.js crypto module
37
+ const { createHash } = await import('crypto');
38
+ const hash = createHash('sha256').update('global:deposit_market').digest();
39
+ const expected = hash.subarray(0, 8);
40
+ const disc = await anchorDisc('deposit_market');
41
+ expect(disc.equals(expected)).toBe(true);
42
+ });
43
+ it('produces correct disc for claim_global', async () => {
44
+ const { createHash } = await import('crypto');
45
+ const hash = createHash('sha256').update('global:claim_global').digest();
46
+ const expected = hash.subarray(0, 8);
47
+ const disc = await anchorDisc('claim_global');
48
+ expect(disc.equals(expected)).toBe(true);
49
+ });
50
+ it('handles empty name gracefully', async () => {
51
+ const disc = await anchorDisc('');
52
+ expect(disc.length).toBe(8);
53
+ });
54
+ it('handles snake_case names', async () => {
55
+ const disc = await anchorDisc('initialize_market_vault');
56
+ expect(disc.length).toBe(8);
57
+ });
58
+ });
59
+ // ── createAtaIdempotentIx ───────────────────────────────
60
+ describe('createAtaIdempotentIx', () => {
61
+ it('uses ASSOCIATED_TOKEN_PROGRAM_ID as programId', () => {
62
+ const ix = createAtaIdempotentIx(DUMMY_KEY_A, DUMMY_KEY_B, DUMMY_KEY_A, USDC_MINT);
63
+ expect(ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)).toBe(true);
64
+ });
65
+ it('has exactly 6 account keys', () => {
66
+ const ix = createAtaIdempotentIx(DUMMY_KEY_A, DUMMY_KEY_B, DUMMY_KEY_A, USDC_MINT);
67
+ expect(ix.keys.length).toBe(6);
68
+ });
69
+ it('sets payer as signer + writable', () => {
70
+ const payer = CCM_MINT;
71
+ const ix = createAtaIdempotentIx(payer, DUMMY_KEY_B, DUMMY_KEY_A, USDC_MINT);
72
+ expect(ix.keys[0].pubkey.equals(payer)).toBe(true);
73
+ expect(ix.keys[0].isSigner).toBe(true);
74
+ expect(ix.keys[0].isWritable).toBe(true);
75
+ });
76
+ it('sets ata as writable, not signer', () => {
77
+ const ata = DUMMY_KEY_B;
78
+ const ix = createAtaIdempotentIx(DUMMY_KEY_A, ata, DUMMY_KEY_A, USDC_MINT);
79
+ expect(ix.keys[1].pubkey.equals(ata)).toBe(true);
80
+ expect(ix.keys[1].isSigner).toBe(false);
81
+ expect(ix.keys[1].isWritable).toBe(true);
82
+ });
83
+ it('data is single byte [1] (CreateIdempotent)', () => {
84
+ const ix = createAtaIdempotentIx(DUMMY_KEY_A, DUMMY_KEY_B, DUMMY_KEY_A, USDC_MINT);
85
+ expect(ix.data.length).toBe(1);
86
+ expect(ix.data[0]).toBe(1);
87
+ });
88
+ it('defaults to TOKEN_PROGRAM_ID when no tokenProgramId provided', () => {
89
+ const ix = createAtaIdempotentIx(DUMMY_KEY_A, DUMMY_KEY_B, DUMMY_KEY_A, USDC_MINT);
90
+ // 5th account is tokenProgramId
91
+ expect(ix.keys[5].pubkey.equals(TOKEN_PROGRAM_ID)).toBe(true);
92
+ });
93
+ it('uses Token-2022 when specified', () => {
94
+ const ix = createAtaIdempotentIx(DUMMY_KEY_A, DUMMY_KEY_B, DUMMY_KEY_A, CCM_MINT, TOKEN_2022_PROGRAM_ID);
95
+ expect(ix.keys[5].pubkey.equals(TOKEN_2022_PROGRAM_ID)).toBe(true);
96
+ });
97
+ it('includes SystemProgram in account keys', () => {
98
+ const ix = createAtaIdempotentIx(DUMMY_KEY_A, DUMMY_KEY_B, DUMMY_KEY_A, USDC_MINT);
99
+ expect(ix.keys[4].pubkey.equals(SystemProgram.programId)).toBe(true);
100
+ });
101
+ });
102
+ // ── createAddLiquidityIx ────────────────────────────────
103
+ describe('createAddLiquidityIx', () => {
104
+ const pool = PublicKey.unique();
105
+ const position = PublicKey.unique();
106
+ const owner = PublicKey.unique();
107
+ const tokenXMint = PublicKey.unique();
108
+ const tokenYMint = PublicKey.unique();
109
+ const userTokenX = PublicKey.unique();
110
+ const userTokenY = PublicKey.unique();
111
+ const reserveX = PublicKey.unique();
112
+ const reserveY = PublicKey.unique();
113
+ const binArrayLower = PublicKey.unique();
114
+ const binArrayUpper = PublicKey.unique();
115
+ const ix = createAddLiquidityIx(pool, position, owner, tokenXMint, tokenYMint, userTokenX, userTokenY, reserveX, reserveY, binArrayLower, binArrayUpper, 1000n, 2000n, 100, 5);
116
+ it('uses DLMM_PROGRAM_ID', () => {
117
+ expect(ix.programId.equals(DLMM_PROGRAM_ID)).toBe(true);
118
+ });
119
+ it('has 14 account keys', () => {
120
+ expect(ix.keys.length).toBe(14);
121
+ });
122
+ it('data is 33 bytes (8 disc + 8 amountX + 8 amountY + 4 activeBinId + 4 binCount + 1 strategyType)', () => {
123
+ expect(ix.data.length).toBe(33);
124
+ });
125
+ it('encodes amountX correctly as LE u64', () => {
126
+ const amountX = ix.data.readBigUInt64LE(8);
127
+ expect(amountX).toBe(1000n);
128
+ });
129
+ it('encodes amountY correctly as LE u64', () => {
130
+ const amountY = ix.data.readBigUInt64LE(16);
131
+ expect(amountY).toBe(2000n);
132
+ });
133
+ it('encodes activeBinId as LE i32', () => {
134
+ const activeBinId = ix.data.readInt32LE(24);
135
+ expect(activeBinId).toBe(100);
136
+ });
137
+ it('encodes binCount as LE i32', () => {
138
+ const binCount = ix.data.readInt32LE(28);
139
+ expect(binCount).toBe(5);
140
+ });
141
+ it('sets strategy type to 0 (Spot)', () => {
142
+ expect(ix.data.readUInt8(32)).toBe(0);
143
+ });
144
+ it('marks owner as signer', () => {
145
+ const ownerKey = ix.keys.find(k => k.pubkey.equals(owner));
146
+ expect(ownerKey?.isSigner).toBe(true);
147
+ });
148
+ });
149
+ // ── createRemoveLiquidityIx ─────────────────────────────
150
+ describe('createRemoveLiquidityIx', () => {
151
+ const pool = PublicKey.unique();
152
+ const position = PublicKey.unique();
153
+ const owner = PublicKey.unique();
154
+ const reserveX = PublicKey.unique();
155
+ const reserveY = PublicKey.unique();
156
+ const userTokenX = PublicKey.unique();
157
+ const userTokenY = PublicKey.unique();
158
+ const tokenXMint = PublicKey.unique();
159
+ const tokenYMint = PublicKey.unique();
160
+ const binArrayLower = PublicKey.unique();
161
+ const binArrayUpper = PublicKey.unique();
162
+ const ix = createRemoveLiquidityIx(pool, position, owner, reserveX, reserveY, userTokenX, userTokenY, tokenXMint, tokenYMint, binArrayLower, binArrayUpper, 10000);
163
+ it('uses DLMM_PROGRAM_ID', () => {
164
+ expect(ix.programId.equals(DLMM_PROGRAM_ID)).toBe(true);
165
+ });
166
+ it('has 13 account keys', () => {
167
+ expect(ix.keys.length).toBe(13);
168
+ });
169
+ it('data is 10 bytes (8 disc + 2 bps)', () => {
170
+ expect(ix.data.length).toBe(10);
171
+ });
172
+ it('encodes bpsBasisPointsToRemove as LE u16', () => {
173
+ const bps = ix.data.readUInt16LE(8);
174
+ expect(bps).toBe(10000);
175
+ });
176
+ it('marks owner as signer', () => {
177
+ const ownerKey = ix.keys.find(k => k.pubkey.equals(owner));
178
+ expect(ownerKey?.isSigner).toBe(true);
179
+ });
180
+ it('encodes partial removal correctly', () => {
181
+ const partialIx = createRemoveLiquidityIx(pool, position, owner, reserveX, reserveY, userTokenX, userTokenY, tokenXMint, tokenYMint, binArrayLower, binArrayUpper, 5000);
182
+ expect(partialIx.data.readUInt16LE(8)).toBe(5000);
183
+ });
184
+ });
185
+ // ── PDA derivation ──────────────────────────────────────
186
+ describe('PDA derivation', () => {
187
+ it('getProtocolStatePDA is deterministic', () => {
188
+ const a = getProtocolStatePDA(PROGRAM_ID);
189
+ const b = getProtocolStatePDA(PROGRAM_ID);
190
+ expect(a.equals(b)).toBe(true);
191
+ });
192
+ it('getProtocolStatePDA differs by program', () => {
193
+ const mainnet = getProtocolStatePDA(MAINNET_PROGRAM_ID);
194
+ const devnet = getProtocolStatePDA(DEVNET_PROGRAM_ID);
195
+ expect(mainnet.equals(devnet)).toBe(false);
196
+ });
197
+ it('getMarketVaultPDA differs by market ID', () => {
198
+ const ps = getProtocolStatePDA(PROGRAM_ID);
199
+ const vault1 = getMarketVaultPDA(ps, 1, PROGRAM_ID);
200
+ const vault2 = getMarketVaultPDA(ps, 2, PROGRAM_ID);
201
+ expect(vault1.equals(vault2)).toBe(false);
202
+ });
203
+ it('getUserPositionPDA differs by user', () => {
204
+ const ps = getProtocolStatePDA(PROGRAM_ID);
205
+ const vault = getMarketVaultPDA(ps, 1, PROGRAM_ID);
206
+ const pos1 = getUserPositionPDA(vault, CCM_MINT, PROGRAM_ID);
207
+ const pos2 = getUserPositionPDA(vault, USDC_MINT, PROGRAM_ID);
208
+ expect(pos1.equals(pos2)).toBe(false);
209
+ });
210
+ it('getGlobalRootConfigPDA derives from CCM mint', () => {
211
+ const root = getGlobalRootConfigPDA(CCM_MINT, PROGRAM_ID);
212
+ expect(PublicKey.isOnCurve(root.toBuffer())).toBe(false); // PDAs are off-curve
213
+ });
214
+ it('getClaimStatePDA is unique per claimer', () => {
215
+ const claim1 = getClaimStatePDA(CCM_MINT, USDC_MINT, PROGRAM_ID);
216
+ const claim2 = getClaimStatePDA(CCM_MINT, CCM_MINT, PROGRAM_ID);
217
+ expect(claim1.equals(claim2)).toBe(false);
218
+ });
219
+ it('getChannelConfigV2PDA is unique per subject', () => {
220
+ const ch1 = getChannelConfigV2PDA(CCM_MINT, USDC_MINT, PROGRAM_ID);
221
+ const ch2 = getChannelConfigV2PDA(CCM_MINT, CCM_MINT, PROGRAM_ID);
222
+ expect(ch1.equals(ch2)).toBe(false);
223
+ });
224
+ it('getAta derives a valid off-curve address', () => {
225
+ const ata = getAta(CCM_MINT, USDC_MINT, TOKEN_PROGRAM_ID);
226
+ expect(PublicKey.isOnCurve(ata.toBuffer())).toBe(false);
227
+ });
228
+ });
229
+ // ── Constants ───────────────────────────────────────────
230
+ describe('constants', () => {
231
+ it('PROGRAM_ID equals MAINNET_PROGRAM_ID', () => {
232
+ expect(PROGRAM_ID.equals(MAINNET_PROGRAM_ID)).toBe(true);
233
+ });
234
+ it('DEVNET_PROGRAM_ID is different from mainnet', () => {
235
+ expect(DEVNET_PROGRAM_ID.equals(MAINNET_PROGRAM_ID)).toBe(false);
236
+ });
237
+ it('TOKEN_PROGRAM_ID is the legacy SPL token program', () => {
238
+ expect(TOKEN_PROGRAM_ID.toBase58()).toBe('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
239
+ });
240
+ it('TOKEN_2022_PROGRAM_ID is the Token-2022 program', () => {
241
+ expect(TOKEN_2022_PROGRAM_ID.toBase58()).toBe('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb');
242
+ });
243
+ it('DLMM_PROGRAM_ID matches Meteora mainnet', () => {
244
+ expect(DLMM_PROGRAM_ID.toBase58()).toBe('LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo');
245
+ });
246
+ it('seed strings match expected values', () => {
247
+ expect(PROTOCOL_STATE_SEED).toBe('protocol_state');
248
+ expect(MARKET_VAULT_SEED).toBe('market_vault');
249
+ expect(MARKET_POSITION_SEED).toBe('market_position');
250
+ expect(GLOBAL_ROOT_SEED).toBe('global_root');
251
+ expect(CLAIM_STATE_GLOBAL_SEED).toBe('claim_global');
252
+ expect(CHANNEL_CONFIG_V2_SEED).toBe('channel_cfg_v2');
253
+ });
254
+ });
255
+ // ── Account parsers ─────────────────────────────────────
256
+ describe('parseMarketVault', () => {
257
+ it('extracts depositMint at correct offset', () => {
258
+ // Build a fake MarketVault buffer: 8 disc + 1 bump + 8 marketId + 32 depositMint + ...
259
+ const buf = Buffer.alloc(8 + 1 + 8 + 32 + 32 + 32 + 16);
260
+ USDC_MINT.toBuffer().copy(buf, 8 + 1 + 8); // depositMint at offset 17
261
+ CCM_MINT.toBuffer().copy(buf, 8 + 1 + 8 + 32); // vlofiMint at offset 49
262
+ const vault = parseMarketVault(buf);
263
+ expect(vault.depositMint.equals(USDC_MINT)).toBe(true);
264
+ });
265
+ it('extracts vlofiMint at correct offset', () => {
266
+ const buf = Buffer.alloc(8 + 1 + 8 + 32 + 32 + 32 + 16);
267
+ USDC_MINT.toBuffer().copy(buf, 8 + 1 + 8);
268
+ CCM_MINT.toBuffer().copy(buf, 8 + 1 + 8 + 32);
269
+ const vault = parseMarketVault(buf);
270
+ expect(vault.vlofiMint.equals(CCM_MINT)).toBe(true);
271
+ });
272
+ it('reads bump from first byte after discriminator', () => {
273
+ const buf = Buffer.alloc(8 + 1 + 8 + 32 + 32 + 32 + 16);
274
+ buf[8] = 254; // bump
275
+ const vault = parseMarketVault(buf);
276
+ expect(vault.bump).toBe(254);
277
+ });
278
+ });
279
+ describe('parseProtocolState', () => {
280
+ it('reads isInitialized correctly', () => {
281
+ const buf = Buffer.alloc(8 + 165);
282
+ buf[8] = 1; // isInitialized
283
+ const state = parseProtocolState(buf);
284
+ expect(state.isInitialized).toBe(true);
285
+ });
286
+ it('reads paused flag correctly', () => {
287
+ const buf = Buffer.alloc(8 + 165);
288
+ buf[8 + 162] = 1; // paused
289
+ const state = parseProtocolState(buf);
290
+ expect(state.paused).toBe(true);
291
+ });
292
+ it('reads version byte', () => {
293
+ const buf = Buffer.alloc(8 + 165);
294
+ buf[8 + 1] = 3; // version
295
+ const state = parseProtocolState(buf);
296
+ expect(state.version).toBe(3);
297
+ });
298
+ });
299
+ describe('parseUserMarketPosition', () => {
300
+ it('returns null for data too short', () => {
301
+ const buf = Buffer.alloc(50);
302
+ expect(parseUserMarketPosition(buf)).toBeNull();
303
+ });
304
+ it('reads settled flag correctly', () => {
305
+ const buf = Buffer.alloc(8 + 100);
306
+ buf[8 + 89] = 1; // settled
307
+ const pos = parseUserMarketPosition(buf);
308
+ expect(pos).not.toBeNull();
309
+ expect(pos.settled).toBe(true);
310
+ });
311
+ it('reads depositedAmount as u64 LE', () => {
312
+ const buf = Buffer.alloc(8 + 100);
313
+ buf.writeBigUInt64LE(1000000n, 8 + 1 + 32 + 32); // depositedAmount at offset 73 from disc
314
+ const pos = parseUserMarketPosition(buf);
315
+ expect(pos).not.toBeNull();
316
+ expect(pos.depositedAmount).toBe(1000000n);
317
+ });
318
+ });
package/dist/nav.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * NAV (Net Asset Value) helpers for vLOFI share computation.
3
+ *
4
+ * These pure functions mirror the on-chain deposit/settle math so that
5
+ * clients can preview shares received or principal returned without
6
+ * submitting a transaction.
7
+ *
8
+ * NAV BPS domain:
9
+ * - 10_000 = 1.00x (1 USDC per share, initial NAV)
10
+ * - 50_000 = 5.00x (max NAV, protocol ceiling)
11
+ * - 0 = uninitialized (pre-realloc vaults, treated as 10_000)
12
+ */
13
+ /** Structured NAV data extracted from a MarketVault. */
14
+ export interface NavInfo {
15
+ /** NAV per share in basis points (10_000 = 1.0x). Zero means uninitialized. */
16
+ navPerShareBps: bigint;
17
+ /** Slot at which NAV was last updated on-chain. */
18
+ lastNavUpdateSlot: bigint;
19
+ /** Human-readable share price in USDC terms (navPerShareBps / 10_000). */
20
+ sharePrice: number;
21
+ }
22
+ /**
23
+ * Compute the number of vLOFI shares a deposit of `amount` base units will mint.
24
+ *
25
+ * Formula (matches vault.rs deposit_market):
26
+ * shares = amount * 10_000 / nav_per_share_bps
27
+ *
28
+ * If navPerShareBps is 0 (uninitialized / pre-realloc vault), falls back to
29
+ * the default 10_000 BPS (1:1 ratio).
30
+ */
31
+ export declare function computeSharesForDeposit(amount: bigint, navPerShareBps: bigint): bigint;
32
+ /**
33
+ * Compute the USDC principal returned when settling `shares` of vLOFI.
34
+ *
35
+ * Formula (matches vault.rs settle_market):
36
+ * principal = shares * nav_per_share_bps / 10_000
37
+ *
38
+ * If navPerShareBps is 0 (uninitialized), falls back to 10_000 BPS.
39
+ */
40
+ export declare function computePrincipalForSettle(shares: bigint, navPerShareBps: bigint): bigint;
package/dist/nav.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * NAV (Net Asset Value) helpers for vLOFI share computation.
3
+ *
4
+ * These pure functions mirror the on-chain deposit/settle math so that
5
+ * clients can preview shares received or principal returned without
6
+ * submitting a transaction.
7
+ *
8
+ * NAV BPS domain:
9
+ * - 10_000 = 1.00x (1 USDC per share, initial NAV)
10
+ * - 50_000 = 5.00x (max NAV, protocol ceiling)
11
+ * - 0 = uninitialized (pre-realloc vaults, treated as 10_000)
12
+ */
13
+ const DEFAULT_NAV_BPS = 10000n;
14
+ const BPS_SCALE = 10000n;
15
+ /**
16
+ * Compute the number of vLOFI shares a deposit of `amount` base units will mint.
17
+ *
18
+ * Formula (matches vault.rs deposit_market):
19
+ * shares = amount * 10_000 / nav_per_share_bps
20
+ *
21
+ * If navPerShareBps is 0 (uninitialized / pre-realloc vault), falls back to
22
+ * the default 10_000 BPS (1:1 ratio).
23
+ */
24
+ export function computeSharesForDeposit(amount, navPerShareBps) {
25
+ const effectiveNav = navPerShareBps === 0n ? DEFAULT_NAV_BPS : navPerShareBps;
26
+ return (amount * BPS_SCALE) / effectiveNav;
27
+ }
28
+ /**
29
+ * Compute the USDC principal returned when settling `shares` of vLOFI.
30
+ *
31
+ * Formula (matches vault.rs settle_market):
32
+ * principal = shares * nav_per_share_bps / 10_000
33
+ *
34
+ * If navPerShareBps is 0 (uninitialized), falls back to 10_000 BPS.
35
+ */
36
+ export function computePrincipalForSettle(shares, navPerShareBps) {
37
+ const effectiveNav = navPerShareBps === 0n ? DEFAULT_NAV_BPS : navPerShareBps;
38
+ return (shares * effectiveNav) / BPS_SCALE;
39
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for NAV computation helpers (computeSharesForDeposit, computePrincipalForSettle).
3
+ */
4
+ export {};
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Unit tests for NAV computation helpers (computeSharesForDeposit, computePrincipalForSettle).
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { computeSharesForDeposit, computePrincipalForSettle } from './nav.js';
6
+ // ── computeSharesForDeposit ──────────────────────────────
7
+ describe('computeSharesForDeposit', () => {
8
+ it('returns 1:1 shares at default NAV (10_000 BPS)', () => {
9
+ const shares = computeSharesForDeposit(1000000n, 10000n);
10
+ expect(shares).toBe(1000000n);
11
+ });
12
+ it('returns half shares at 2x NAV (20_000 BPS)', () => {
13
+ const shares = computeSharesForDeposit(1000000n, 20000n);
14
+ expect(shares).toBe(500000n);
15
+ });
16
+ it('handles zero NAV fallback — treats as 10_000 BPS', () => {
17
+ // zero nav fallback: uninitialized vault should behave like 1:1
18
+ const shares = computeSharesForDeposit(1000000n, 0n);
19
+ expect(shares).toBe(1000000n);
20
+ });
21
+ it('handles max NAV of 50_000 BPS (5.0x)', () => {
22
+ // max nav: 50_000 BPS ceiling
23
+ const shares = computeSharesForDeposit(5000000n, 50000n);
24
+ expect(shares).toBe(1000000n);
25
+ });
26
+ it('returns 0 shares for 0 deposit amount', () => {
27
+ const shares = computeSharesForDeposit(0n, 10000n);
28
+ expect(shares).toBe(0n);
29
+ });
30
+ it('handles large deposit without overflow', () => {
31
+ // overflow test: large amount should not throw
32
+ const amount = 1000000000000n; // 1M USDC in base units
33
+ const shares = computeSharesForDeposit(amount, 10000n);
34
+ expect(shares).toBe(amount);
35
+ });
36
+ it('truncates fractional shares (integer division)', () => {
37
+ // 3 base units at 2x NAV = 1.5 → truncates to 1
38
+ const shares = computeSharesForDeposit(3n, 20000n);
39
+ expect(shares).toBe(1n);
40
+ });
41
+ });
42
+ // ── computePrincipalForSettle ─────────────────────────────
43
+ describe('computePrincipalForSettle', () => {
44
+ it('returns 1:1 principal at default NAV (10_000 BPS)', () => {
45
+ const principal = computePrincipalForSettle(1000000n, 10000n);
46
+ expect(principal).toBe(1000000n);
47
+ });
48
+ it('returns double principal at 2x NAV (20_000 BPS)', () => {
49
+ const principal = computePrincipalForSettle(1000000n, 20000n);
50
+ expect(principal).toBe(2000000n);
51
+ });
52
+ it('handles zero NAV fallback — treats as 10_000 BPS', () => {
53
+ // zero nav fallback
54
+ const principal = computePrincipalForSettle(1000000n, 0n);
55
+ expect(principal).toBe(1000000n);
56
+ });
57
+ it('handles max NAV of 50_000 BPS (5.0x)', () => {
58
+ // max nav: 50_000 BPS ceiling
59
+ const principal = computePrincipalForSettle(1000000n, 50000n);
60
+ expect(principal).toBe(5000000n);
61
+ });
62
+ it('returns 0 principal for 0 shares', () => {
63
+ const principal = computePrincipalForSettle(0n, 10000n);
64
+ expect(principal).toBe(0n);
65
+ });
66
+ it('handles large shares without overflow', () => {
67
+ // overflow test: large shares
68
+ const shares = 1000000000000n;
69
+ const principal = computePrincipalForSettle(shares, 10000n);
70
+ expect(principal).toBe(shares);
71
+ });
72
+ });
73
+ // ── NavInfo type ─────────────────────────────────────────
74
+ describe('NavInfo type', () => {
75
+ it('can be constructed with valid fields', () => {
76
+ const info = {
77
+ navPerShareBps: 15000n,
78
+ lastNavUpdateSlot: 405000000n,
79
+ sharePrice: 1.5,
80
+ };
81
+ expect(info.navPerShareBps).toBe(15000n);
82
+ expect(info.lastNavUpdateSlot).toBe(405000000n);
83
+ expect(info.sharePrice).toBe(1.5);
84
+ });
85
+ });
86
+ // ── Round-trip deposit→settle ────────────────────────────
87
+ describe('deposit-settle round trip', () => {
88
+ it('deposit then settle at same NAV returns original amount', () => {
89
+ const amount = 1000000n;
90
+ const nav = 12500n;
91
+ const shares = computeSharesForDeposit(amount, nav);
92
+ const principal = computePrincipalForSettle(shares, nav);
93
+ // Due to integer division, principal <= amount
94
+ expect(principal).toBeLessThanOrEqual(amount);
95
+ // But should be close (within 1 base unit rounding)
96
+ expect(amount - principal).toBeLessThan(nav);
97
+ });
98
+ });
package/dist/pda.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Seeds must match programs/attention-oracle/src/constants.rs.
5
5
  */
6
6
  import { PublicKey } from '@solana/web3.js';
7
- import { PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, PROTOCOL_STATE_SEED, MARKET_VAULT_SEED, MARKET_POSITION_SEED, GLOBAL_ROOT_SEED, CLAIM_STATE_GLOBAL_SEED, CHANNEL_CONFIG_V2_SEED, } from './constants';
7
+ import { PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, PROTOCOL_STATE_SEED, MARKET_VAULT_SEED, MARKET_POSITION_SEED, GLOBAL_ROOT_SEED, CLAIM_STATE_GLOBAL_SEED, CHANNEL_CONFIG_V2_SEED, } from './constants.js';
8
8
  /** Derive the singleton ProtocolState PDA. */
9
9
  export function getProtocolStatePDA(programId = PROGRAM_ID) {
10
10
  return PublicKey.findProgramAddressSync([Buffer.from(PROTOCOL_STATE_SEED)], programId)[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wzrd_sol/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "TypeScript SDK for the WZRD Liquid Attention Protocol — deposit, settle, claim on Solana",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",