@mania-labs/mania-sdk 1.0.0 → 1.0.1

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,495 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { parseEther, type Address } from 'viem';
3
+ import { ManiaSDK } from '../../mania.js';
4
+ import {
5
+ createMockPublicClient,
6
+ createMockWalletClient,
7
+ createMockBondingCurveState,
8
+ createMockGlobalState,
9
+ MOCK_TX_HASH,
10
+ } from '../helpers/mocks.js';
11
+ import { TEST_ADDRESSES, CURVE_SCENARIOS, DEFAULT_GLOBAL_STATE } from '../helpers/fixtures.js';
12
+ import { CHAIN_CONFIGS } from '../../constants.js';
13
+
14
+ describe('ManiaSDK', () => {
15
+ let sdk: ManiaSDK;
16
+
17
+ beforeEach(() => {
18
+ sdk = new ManiaSDK({
19
+ factoryAddress: TEST_ADDRESSES.FACTORY,
20
+ rpcUrl: 'https://example.com/rpc',
21
+ chainId: 6343,
22
+ });
23
+ });
24
+
25
+ describe('constructor', () => {
26
+ it('should initialize with provided config', () => {
27
+ expect(sdk.factoryAddress).toBe(TEST_ADDRESSES.FACTORY);
28
+ expect(sdk.chainId).toBe(6343);
29
+ });
30
+
31
+ it('should default chainId to 1 if not provided', () => {
32
+ const defaultSdk = new ManiaSDK({
33
+ factoryAddress: TEST_ADDRESSES.FACTORY,
34
+ rpcUrl: 'https://example.com/rpc',
35
+ });
36
+ expect(defaultSdk.chainId).toBe(1);
37
+ });
38
+ });
39
+
40
+ describe('fromChainId', () => {
41
+ it('should create SDK for supported chain', () => {
42
+ const sdkFromChain = ManiaSDK.fromChainId(6343, 'https://example.com/rpc');
43
+ expect(sdkFromChain.factoryAddress).toBe(CHAIN_CONFIGS[6343].factoryAddress);
44
+ expect(sdkFromChain.chainId).toBe(6343);
45
+ });
46
+
47
+ it('should throw for unsupported chain', () => {
48
+ expect(() => ManiaSDK.fromChainId(99999)).toThrow('Unsupported chain ID: 99999');
49
+ });
50
+ });
51
+
52
+ describe('getWalletAddress', () => {
53
+ it('should return undefined when wallet not connected', () => {
54
+ expect(sdk.getWalletAddress()).toBeUndefined();
55
+ });
56
+
57
+ it('should return address when wallet is connected', () => {
58
+ const mockWallet = createMockWalletClient({ address: TEST_ADDRESSES.USER1 });
59
+ sdk.setWalletClient(mockWallet);
60
+ expect(sdk.getWalletAddress()).toBe(TEST_ADDRESSES.USER1);
61
+ });
62
+ });
63
+
64
+ describe('getFactoryAddress', () => {
65
+ it('should return factory address', () => {
66
+ expect(sdk.getFactoryAddress()).toBe(TEST_ADDRESSES.FACTORY);
67
+ });
68
+ });
69
+
70
+ describe('getPublicClient', () => {
71
+ it('should return public client', () => {
72
+ expect(sdk.getPublicClient()).toBeDefined();
73
+ });
74
+ });
75
+
76
+ describe('getWalletClient', () => {
77
+ it('should return null when not connected', () => {
78
+ expect(sdk.getWalletClient()).toBeNull();
79
+ });
80
+
81
+ it('should return wallet client when connected', () => {
82
+ const mockWallet = createMockWalletClient();
83
+ sdk.setWalletClient(mockWallet);
84
+ expect(sdk.getWalletClient()).toBe(mockWallet);
85
+ });
86
+ });
87
+
88
+ describe('Read Methods', () => {
89
+ let mockPublicClient: ReturnType<typeof createMockPublicClient>;
90
+
91
+ beforeEach(() => {
92
+ mockPublicClient = createMockPublicClient({
93
+ bondingCurve: CURVE_SCENARIOS.FRESH,
94
+ globalState: DEFAULT_GLOBAL_STATE,
95
+ });
96
+ sdk.setPublicClient(mockPublicClient);
97
+ });
98
+
99
+ describe('getGlobalState', () => {
100
+ it('should fetch and parse global state', async () => {
101
+ const state = await sdk.getGlobalState();
102
+
103
+ expect(state.initialized).toBe(true);
104
+ expect(state.feeBasisPoints).toBe(DEFAULT_GLOBAL_STATE.feeBasisPoints);
105
+ expect(state.enableMigrate).toBe(true);
106
+ expect(mockPublicClient.readContract).toHaveBeenCalledWith(
107
+ expect.objectContaining({
108
+ functionName: 'global',
109
+ })
110
+ );
111
+ });
112
+ });
113
+
114
+ describe('getBondingCurve', () => {
115
+ it('should fetch bonding curve for token', async () => {
116
+ const curve = await sdk.getBondingCurve(TEST_ADDRESSES.TOKEN1);
117
+
118
+ expect(curve.virtualTokenReserves).toBe(CURVE_SCENARIOS.FRESH.virtualTokenReserves);
119
+ expect(curve.complete).toBe(false);
120
+ expect(mockPublicClient.readContract).toHaveBeenCalledWith(
121
+ expect.objectContaining({
122
+ functionName: 'getBondingCurve',
123
+ args: [TEST_ADDRESSES.TOKEN1],
124
+ })
125
+ );
126
+ });
127
+ });
128
+
129
+ describe('getBondingCurveInstance', () => {
130
+ it('should return BondingCurve class instance', async () => {
131
+ const curve = await sdk.getBondingCurveInstance(TEST_ADDRESSES.TOKEN1);
132
+
133
+ expect(curve).toBeDefined();
134
+ expect(curve.getState()).toEqual(CURVE_SCENARIOS.FRESH);
135
+ expect(curve.getCurrentPrice()).toBeGreaterThan(0n);
136
+ });
137
+ });
138
+
139
+ describe('getTokenInfo', () => {
140
+ it('should return comprehensive token info', async () => {
141
+ const info = await sdk.getTokenInfo(TEST_ADDRESSES.TOKEN1);
142
+
143
+ expect(info.address).toBe(TEST_ADDRESSES.TOKEN1);
144
+ expect(info.bondingCurve).toEqual(CURVE_SCENARIOS.FRESH);
145
+ expect(info.currentPrice).toBeGreaterThan(0n);
146
+ expect(info.marketCapEth).toBeGreaterThan(0n);
147
+ expect(info.migrationProgress).toBe(0);
148
+ });
149
+ });
150
+
151
+ describe('getBuyQuote', () => {
152
+ it('should fetch buy quote from contract', async () => {
153
+ const quote = await sdk.getBuyQuote(TEST_ADDRESSES.TOKEN1, parseEther('0.1'));
154
+
155
+ expect(quote).toBeGreaterThan(0n);
156
+ expect(mockPublicClient.readContract).toHaveBeenCalledWith(
157
+ expect.objectContaining({
158
+ functionName: 'getBuyQuote',
159
+ args: [TEST_ADDRESSES.TOKEN1, parseEther('0.1')],
160
+ })
161
+ );
162
+ });
163
+ });
164
+
165
+ describe('getSellQuote', () => {
166
+ it('should fetch sell quote from contract', async () => {
167
+ const quote = await sdk.getSellQuote(TEST_ADDRESSES.TOKEN1, parseEther('1000000'));
168
+
169
+ expect(quote).toBeGreaterThan(0n);
170
+ expect(mockPublicClient.readContract).toHaveBeenCalledWith(
171
+ expect.objectContaining({
172
+ functionName: 'getSellQuote',
173
+ args: [TEST_ADDRESSES.TOKEN1, parseEther('1000000')],
174
+ })
175
+ );
176
+ });
177
+ });
178
+
179
+ describe('getFee', () => {
180
+ it('should calculate fee for amount', async () => {
181
+ const fee = await sdk.getFee(parseEther('1'));
182
+
183
+ expect(fee).toBe(parseEther('0.01')); // 1% of 1 ETH
184
+ expect(mockPublicClient.readContract).toHaveBeenCalledWith(
185
+ expect.objectContaining({
186
+ functionName: 'getFee',
187
+ args: [parseEther('1')],
188
+ })
189
+ );
190
+ });
191
+ });
192
+
193
+ describe('getUserVolume', () => {
194
+ it('should fetch user volume', async () => {
195
+ const volume = await sdk.getUserVolume(TEST_ADDRESSES.USER1);
196
+
197
+ expect(volume).toBe(0n);
198
+ expect(mockPublicClient.readContract).toHaveBeenCalledWith(
199
+ expect.objectContaining({
200
+ functionName: 'userVolume',
201
+ args: [TEST_ADDRESSES.USER1],
202
+ })
203
+ );
204
+ });
205
+ });
206
+
207
+ describe('isComplete', () => {
208
+ it('should return false for fresh curve', async () => {
209
+ const complete = await sdk.isComplete(TEST_ADDRESSES.TOKEN1);
210
+ expect(complete).toBe(false);
211
+ });
212
+
213
+ it('should return true for complete curve', async () => {
214
+ const completeMock = createMockPublicClient({
215
+ bondingCurve: CURVE_SCENARIOS.COMPLETE,
216
+ });
217
+ sdk.setPublicClient(completeMock);
218
+
219
+ const complete = await sdk.isComplete(TEST_ADDRESSES.TOKEN1);
220
+ expect(complete).toBe(true);
221
+ });
222
+ });
223
+
224
+ describe('isMigrated', () => {
225
+ it('should return false for active curve', async () => {
226
+ const migrated = await sdk.isMigrated(TEST_ADDRESSES.TOKEN1);
227
+ expect(migrated).toBe(false);
228
+ });
229
+
230
+ it('should return true for migrated curve', async () => {
231
+ const migratedMock = createMockPublicClient({
232
+ bondingCurve: CURVE_SCENARIOS.MIGRATED,
233
+ });
234
+ sdk.setPublicClient(migratedMock);
235
+
236
+ const migrated = await sdk.isMigrated(TEST_ADDRESSES.TOKEN1);
237
+ expect(migrated).toBe(true);
238
+ });
239
+ });
240
+ });
241
+
242
+ describe('Write Methods', () => {
243
+ let mockPublicClient: ReturnType<typeof createMockPublicClient>;
244
+ let mockWalletClient: ReturnType<typeof createMockWalletClient>;
245
+
246
+ beforeEach(() => {
247
+ mockPublicClient = createMockPublicClient({
248
+ bondingCurve: CURVE_SCENARIOS.FRESH,
249
+ globalState: DEFAULT_GLOBAL_STATE,
250
+ });
251
+ mockWalletClient = createMockWalletClient({ address: TEST_ADDRESSES.USER1 });
252
+ sdk.setPublicClient(mockPublicClient);
253
+ sdk.setWalletClient(mockWalletClient);
254
+ });
255
+
256
+ describe('create', () => {
257
+ it('should throw without wallet connected', async () => {
258
+ const noWalletSdk = new ManiaSDK({
259
+ factoryAddress: TEST_ADDRESSES.FACTORY,
260
+ rpcUrl: 'https://example.com/rpc',
261
+ });
262
+ noWalletSdk.setPublicClient(mockPublicClient);
263
+
264
+ await expect(
265
+ noWalletSdk.create({
266
+ name: 'Test',
267
+ symbol: 'TEST',
268
+ uri: 'https://example.com',
269
+ creator: TEST_ADDRESSES.USER1,
270
+ })
271
+ ).rejects.toThrow('Wallet not connected');
272
+ });
273
+
274
+ it('should call writeContract with correct params', async () => {
275
+ const result = await sdk.create({
276
+ name: 'Test Token',
277
+ symbol: 'TEST',
278
+ uri: 'https://example.com/token.json',
279
+ creator: TEST_ADDRESSES.USER1,
280
+ });
281
+
282
+ expect(result.hash).toBe(MOCK_TX_HASH);
283
+ expect(result.success).toBe(true);
284
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
285
+ expect.objectContaining({
286
+ functionName: 'create',
287
+ args: ['Test Token', 'TEST', 'https://example.com/token.json', TEST_ADDRESSES.USER1],
288
+ })
289
+ );
290
+ });
291
+ });
292
+
293
+ describe('createAndBuy', () => {
294
+ it('should include ETH value in transaction', async () => {
295
+ const result = await sdk.createAndBuy({
296
+ name: 'Test Token',
297
+ symbol: 'TEST',
298
+ uri: 'https://example.com/token.json',
299
+ creator: TEST_ADDRESSES.USER1,
300
+ buyAmountEth: parseEther('0.1'),
301
+ minTokensOut: 0n,
302
+ });
303
+
304
+ expect(result.hash).toBe(MOCK_TX_HASH);
305
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
306
+ expect.objectContaining({
307
+ functionName: 'createAndBuy',
308
+ value: parseEther('0.1'),
309
+ })
310
+ );
311
+ });
312
+ });
313
+
314
+ describe('buy', () => {
315
+ it('should call writeContract with correct params', async () => {
316
+ const result = await sdk.buy({
317
+ token: TEST_ADDRESSES.TOKEN1,
318
+ amountEth: parseEther('0.1'),
319
+ minTokensOut: parseEther('1000'),
320
+ });
321
+
322
+ expect(result.hash).toBe(MOCK_TX_HASH);
323
+ expect(result.success).toBe(true);
324
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
325
+ expect.objectContaining({
326
+ functionName: 'buy',
327
+ args: [TEST_ADDRESSES.TOKEN1, parseEther('1000'), TEST_ADDRESSES.USER1],
328
+ value: parseEther('0.1'),
329
+ })
330
+ );
331
+ });
332
+
333
+ it('should use provided recipient', async () => {
334
+ await sdk.buy({
335
+ token: TEST_ADDRESSES.TOKEN1,
336
+ amountEth: parseEther('0.1'),
337
+ minTokensOut: parseEther('1000'),
338
+ recipient: TEST_ADDRESSES.USER2,
339
+ });
340
+
341
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
342
+ expect.objectContaining({
343
+ args: [TEST_ADDRESSES.TOKEN1, parseEther('1000'), TEST_ADDRESSES.USER2],
344
+ })
345
+ );
346
+ });
347
+ });
348
+
349
+ describe('buyWithSlippage', () => {
350
+ it('should calculate minTokensOut automatically', async () => {
351
+ const result = await sdk.buyWithSlippage(
352
+ TEST_ADDRESSES.TOKEN1,
353
+ parseEther('0.1'),
354
+ 100 // 1% slippage
355
+ );
356
+
357
+ expect(result.hash).toBe(MOCK_TX_HASH);
358
+ // The mock will have calculated slippage
359
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
360
+ expect.objectContaining({
361
+ functionName: 'buy',
362
+ })
363
+ );
364
+ });
365
+ });
366
+
367
+ describe('sell', () => {
368
+ it('should call writeContract with correct params', async () => {
369
+ const result = await sdk.sell({
370
+ token: TEST_ADDRESSES.TOKEN1,
371
+ amountTokens: parseEther('1000000'),
372
+ minEthOut: parseEther('0.01'),
373
+ });
374
+
375
+ expect(result.hash).toBe(MOCK_TX_HASH);
376
+ expect(result.success).toBe(true);
377
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
378
+ expect.objectContaining({
379
+ functionName: 'sell',
380
+ args: [TEST_ADDRESSES.TOKEN1, parseEther('1000000'), parseEther('0.01')],
381
+ })
382
+ );
383
+ });
384
+ });
385
+
386
+ describe('sellWithSlippage', () => {
387
+ it('should calculate minEthOut automatically', async () => {
388
+ const result = await sdk.sellWithSlippage(
389
+ TEST_ADDRESSES.TOKEN1,
390
+ parseEther('1000000'),
391
+ 100 // 1% slippage
392
+ );
393
+
394
+ expect(result.hash).toBe(MOCK_TX_HASH);
395
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
396
+ expect.objectContaining({
397
+ functionName: 'sell',
398
+ })
399
+ );
400
+ });
401
+ });
402
+
403
+ describe('migrate', () => {
404
+ it('should include pool migration fee', async () => {
405
+ const result = await sdk.migrate({
406
+ token: TEST_ADDRESSES.TOKEN1,
407
+ });
408
+
409
+ expect(result.hash).toBe(MOCK_TX_HASH);
410
+ expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
411
+ expect.objectContaining({
412
+ functionName: 'migrate',
413
+ args: [TEST_ADDRESSES.TOKEN1],
414
+ value: DEFAULT_GLOBAL_STATE.poolMigrationFee,
415
+ })
416
+ );
417
+ });
418
+ });
419
+ });
420
+
421
+ describe('Event Watching', () => {
422
+ let mockPublicClient: ReturnType<typeof createMockPublicClient>;
423
+
424
+ beforeEach(() => {
425
+ mockPublicClient = createMockPublicClient();
426
+ sdk.setPublicClient(mockPublicClient);
427
+ });
428
+
429
+ describe('watchCreateEvents', () => {
430
+ it('should set up event watcher', () => {
431
+ const callback = vi.fn();
432
+ const unwatch = sdk.watchCreateEvents(callback);
433
+
434
+ expect(mockPublicClient.watchContractEvent).toHaveBeenCalledWith(
435
+ expect.objectContaining({
436
+ eventName: 'CreateEvent',
437
+ })
438
+ );
439
+ expect(typeof unwatch).toBe('function');
440
+ });
441
+ });
442
+
443
+ describe('watchTradeEvents', () => {
444
+ it('should watch all tokens when no filter provided', () => {
445
+ const callback = vi.fn();
446
+ sdk.watchTradeEvents(undefined, callback);
447
+
448
+ expect(mockPublicClient.watchContractEvent).toHaveBeenCalledWith(
449
+ expect.objectContaining({
450
+ eventName: 'TradeEvent',
451
+ args: undefined,
452
+ })
453
+ );
454
+ });
455
+
456
+ it('should filter by token when provided', () => {
457
+ const callback = vi.fn();
458
+ sdk.watchTradeEvents(TEST_ADDRESSES.TOKEN1, callback);
459
+
460
+ expect(mockPublicClient.watchContractEvent).toHaveBeenCalledWith(
461
+ expect.objectContaining({
462
+ eventName: 'TradeEvent',
463
+ args: { mint: TEST_ADDRESSES.TOKEN1 },
464
+ })
465
+ );
466
+ });
467
+ });
468
+
469
+ describe('watchCompleteEvents', () => {
470
+ it('should set up event watcher', () => {
471
+ const callback = vi.fn();
472
+ sdk.watchCompleteEvents(callback);
473
+
474
+ expect(mockPublicClient.watchContractEvent).toHaveBeenCalledWith(
475
+ expect.objectContaining({
476
+ eventName: 'CompleteEvent',
477
+ })
478
+ );
479
+ });
480
+ });
481
+
482
+ describe('watchMigrationEvents', () => {
483
+ it('should set up event watcher', () => {
484
+ const callback = vi.fn();
485
+ sdk.watchMigrationEvents(callback);
486
+
487
+ expect(mockPublicClient.watchContractEvent).toHaveBeenCalledWith(
488
+ expect.objectContaining({
489
+ eventName: 'CompleteManiaAmmMigrationEvent',
490
+ })
491
+ );
492
+ });
493
+ });
494
+ });
495
+ });