@mania-labs/mania-sdk 1.0.0 → 1.0.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,383 @@
1
+ import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
2
+ import { parseEther, formatEther, type Address } from 'viem';
3
+ import { ManiaSDK } from '../../mania.js';
4
+ import {
5
+ createTestSDK,
6
+ getBalanceSnapshot,
7
+ compareBalances,
8
+ getTokenBalance,
9
+ hasEnoughETH,
10
+ generateUniqueTokenParams,
11
+ withTestRetry,
12
+ type BalanceSnapshot,
13
+ } from '../helpers/integrationHelpers.js';
14
+ import { RUN_INTEGRATION_TESTS } from '../setup.js';
15
+
16
+ describe.skipIf(!RUN_INTEGRATION_TESTS)('SDK Write Methods - Integration', () => {
17
+ let sdk: ManiaSDK;
18
+ let walletAddress: Address;
19
+ let publicClient: ReturnType<typeof createTestSDK>['publicClient'];
20
+
21
+ // Token created during tests
22
+ let createdTokenAddress: Address | undefined;
23
+
24
+ // Minimum ETH required for tests
25
+ const MIN_ETH_REQUIRED = parseEther('0.5');
26
+
27
+ beforeAll(async () => {
28
+ const testSetup = createTestSDK();
29
+ sdk = testSetup.sdk;
30
+ walletAddress = testSetup.walletAddress;
31
+ publicClient = testSetup.publicClient;
32
+
33
+ console.log(`Running write integration tests with wallet: ${walletAddress}`);
34
+
35
+ // Check wallet has enough ETH
36
+ const hasEnough = await hasEnoughETH(publicClient, walletAddress, MIN_ETH_REQUIRED);
37
+ if (!hasEnough) {
38
+ const balance = await publicClient.getBalance({ address: walletAddress });
39
+ console.warn(
40
+ `Warning: Wallet balance (${formatEther(balance)} ETH) may be insufficient for all tests. ` +
41
+ `Recommended: ${formatEther(MIN_ETH_REQUIRED)} ETH`
42
+ );
43
+ }
44
+ });
45
+
46
+ describe('create', () => {
47
+ it('should create a new token', async () => {
48
+ const params = generateUniqueTokenParams();
49
+ const balanceBefore = await getBalanceSnapshot(publicClient, walletAddress);
50
+
51
+ const result = await withTestRetry(() =>
52
+ sdk.create({
53
+ ...params,
54
+ creator: walletAddress,
55
+ })
56
+ );
57
+
58
+ expect(result.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
59
+ expect(result.success).toBe(true);
60
+ expect(result.tokenAddress).toMatch(/^0x[a-fA-F0-9]{40}$/);
61
+
62
+ createdTokenAddress = result.tokenAddress;
63
+
64
+ // Verify bonding curve exists
65
+ const curve = await sdk.getBondingCurve(createdTokenAddress!);
66
+ expect(curve.tokenTotalSupply).toBeGreaterThan(0n);
67
+ expect(curve.complete).toBe(false);
68
+
69
+ // Verify ETH was spent (gas only)
70
+ const balanceAfter = await getBalanceSnapshot(publicClient, walletAddress);
71
+ const diff = compareBalances(balanceBefore, balanceAfter);
72
+ expect(diff.ethDiff).toBeLessThan(0n); // Spent gas
73
+ });
74
+ });
75
+
76
+ describe('createAndBuy', () => {
77
+ it('should create token and buy in one transaction', async () => {
78
+ const params = generateUniqueTokenParams();
79
+ const buyAmount = parseEther('0.01');
80
+ const balanceBefore = await getBalanceSnapshot(publicClient, walletAddress);
81
+
82
+ const result = await withTestRetry(() =>
83
+ sdk.createAndBuy({
84
+ ...params,
85
+ creator: walletAddress,
86
+ buyAmountEth: buyAmount,
87
+ minTokensOut: 0n, // Accept any amount for test
88
+ })
89
+ );
90
+
91
+ expect(result.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
92
+ expect(result.success).toBe(true);
93
+ expect(result.tokenAddress).toMatch(/^0x[a-fA-F0-9]{40}$/);
94
+
95
+ const tokenAddress = result.tokenAddress!;
96
+
97
+ // Verify token balance > 0
98
+ const tokenBalance = await getTokenBalance(publicClient, tokenAddress, walletAddress);
99
+ expect(tokenBalance).toBeGreaterThan(0n);
100
+ console.log(`Bought ${formatEther(tokenBalance)} tokens`);
101
+
102
+ // Verify bonding curve has real ETH reserves
103
+ const curve = await sdk.getBondingCurve(tokenAddress);
104
+ expect(curve.realEthReserves).toBeGreaterThan(0n);
105
+
106
+ // Verify ETH was spent (buyAmount + gas)
107
+ const balanceAfter = await getBalanceSnapshot(publicClient, walletAddress);
108
+ const diff = compareBalances(balanceBefore, balanceAfter);
109
+ expect(diff.ethDiff).toBeLessThan(-buyAmount / 2n); // At least half of buy amount spent
110
+ });
111
+ });
112
+
113
+ describe('buy', () => {
114
+ it('should buy tokens and increase balance', async () => {
115
+ // Create a fresh token for this test
116
+ const params = generateUniqueTokenParams();
117
+ const createResult = await withTestRetry(() =>
118
+ sdk.create({
119
+ ...params,
120
+ creator: walletAddress,
121
+ })
122
+ );
123
+
124
+ if (!createResult.tokenAddress) {
125
+ throw new Error('Token creation failed');
126
+ }
127
+
128
+ const tokenAddress = createResult.tokenAddress;
129
+ const tokenBalanceBefore = await getTokenBalance(publicClient, tokenAddress, walletAddress);
130
+ const ethBalanceBefore = await publicClient.getBalance({ address: walletAddress });
131
+
132
+ const buyAmount = parseEther('0.01');
133
+
134
+ const result = await withTestRetry(() =>
135
+ sdk.buy({
136
+ token: tokenAddress,
137
+ amountEth: buyAmount,
138
+ minTokensOut: 0n,
139
+ })
140
+ );
141
+
142
+ expect(result.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
143
+ expect(result.success).toBe(true);
144
+
145
+ // Verify token balance increased
146
+ const tokenBalanceAfter = await getTokenBalance(publicClient, tokenAddress, walletAddress);
147
+ expect(tokenBalanceAfter).toBeGreaterThan(tokenBalanceBefore);
148
+ console.log(
149
+ `Token balance: ${formatEther(tokenBalanceBefore)} -> ${formatEther(tokenBalanceAfter)}`
150
+ );
151
+
152
+ // Verify ETH balance decreased
153
+ const ethBalanceAfter = await publicClient.getBalance({ address: walletAddress });
154
+ expect(ethBalanceAfter).toBeLessThan(ethBalanceBefore);
155
+ });
156
+
157
+ it('should fail with unrealistic minTokensOut', async () => {
158
+ // Create a fresh token for this test
159
+ const params = generateUniqueTokenParams();
160
+ const createResult = await withTestRetry(() =>
161
+ sdk.create({
162
+ ...params,
163
+ creator: walletAddress,
164
+ })
165
+ );
166
+
167
+ if (!createResult.tokenAddress) {
168
+ throw new Error('Token creation failed');
169
+ }
170
+
171
+ const tokenAddress = createResult.tokenAddress;
172
+ const buyAmount = parseEther('0.001');
173
+ const unrealisticMinTokens = parseEther('999999999999'); // Way too high
174
+
175
+ await expect(
176
+ sdk.buy({
177
+ token: tokenAddress,
178
+ amountEth: buyAmount,
179
+ minTokensOut: unrealisticMinTokens,
180
+ })
181
+ ).rejects.toThrow();
182
+ });
183
+ });
184
+
185
+ describe('buyWithSlippage', () => {
186
+ it('should buy with automatic slippage calculation', async () => {
187
+ // Create a fresh token for this test
188
+ const params = generateUniqueTokenParams();
189
+ const createResult = await withTestRetry(() =>
190
+ sdk.create({
191
+ ...params,
192
+ creator: walletAddress,
193
+ })
194
+ );
195
+
196
+ if (!createResult.tokenAddress) {
197
+ throw new Error('Token creation failed');
198
+ }
199
+
200
+ const tokenAddress = createResult.tokenAddress;
201
+ const buyAmount = parseEther('0.01');
202
+
203
+ const result = await withTestRetry(() =>
204
+ sdk.buyWithSlippage(tokenAddress, buyAmount, 500) // 5% slippage
205
+ );
206
+
207
+ expect(result.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
208
+ expect(result.success).toBe(true);
209
+
210
+ // Verify tokens received
211
+ const tokenBalance = await getTokenBalance(publicClient, tokenAddress, walletAddress);
212
+ expect(tokenBalance).toBeGreaterThan(0n);
213
+ });
214
+ });
215
+
216
+ describe('sell', () => {
217
+ it('should sell tokens and receive ETH', async () => {
218
+ // Create and buy tokens first
219
+ const params = generateUniqueTokenParams();
220
+ const createResult = await withTestRetry(() =>
221
+ sdk.createAndBuy({
222
+ ...params,
223
+ creator: walletAddress,
224
+ buyAmountEth: parseEther('0.02'),
225
+ minTokensOut: 0n,
226
+ })
227
+ );
228
+
229
+ if (!createResult.tokenAddress) {
230
+ throw new Error('Token creation failed');
231
+ }
232
+
233
+ const tokenAddress = createResult.tokenAddress;
234
+
235
+ // Get balances before sell
236
+ const tokenBalanceBefore = await getTokenBalance(publicClient, tokenAddress, walletAddress);
237
+ const ethBalanceBefore = await publicClient.getBalance({ address: walletAddress });
238
+
239
+ // Sell half of tokens
240
+ const sellAmount = tokenBalanceBefore / 2n;
241
+
242
+ const result = await withTestRetry(() =>
243
+ sdk.sell({
244
+ token: tokenAddress,
245
+ amountTokens: sellAmount,
246
+ minEthOut: 0n, // Accept any amount for test
247
+ })
248
+ );
249
+
250
+ expect(result.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
251
+ expect(result.success).toBe(true);
252
+
253
+ // Verify token balance decreased
254
+ const tokenBalanceAfter = await getTokenBalance(publicClient, tokenAddress, walletAddress);
255
+ expect(tokenBalanceAfter).toBeLessThan(tokenBalanceBefore);
256
+ expect(tokenBalanceAfter).toBe(tokenBalanceBefore - sellAmount);
257
+ console.log(
258
+ `Token balance: ${formatEther(tokenBalanceBefore)} -> ${formatEther(tokenBalanceAfter)}`
259
+ );
260
+
261
+ // Verify ETH balance increased (accounting for gas)
262
+ const ethBalanceAfter = await publicClient.getBalance({ address: walletAddress });
263
+ // Note: ETH might slightly decrease due to gas, but should be close
264
+ const ethChange = ethBalanceAfter - ethBalanceBefore;
265
+ console.log(`ETH change: ${formatEther(ethChange)}`);
266
+ });
267
+ });
268
+
269
+ describe('sellWithSlippage', () => {
270
+ it('should sell with automatic slippage calculation', async () => {
271
+ // Create and buy tokens first
272
+ const params = generateUniqueTokenParams();
273
+ const createResult = await withTestRetry(() =>
274
+ sdk.createAndBuy({
275
+ ...params,
276
+ creator: walletAddress,
277
+ buyAmountEth: parseEther('0.02'),
278
+ minTokensOut: 0n,
279
+ })
280
+ );
281
+
282
+ if (!createResult.tokenAddress) {
283
+ throw new Error('Token creation failed');
284
+ }
285
+
286
+ const tokenAddress = createResult.tokenAddress;
287
+ const tokenBalance = await getTokenBalance(publicClient, tokenAddress, walletAddress);
288
+
289
+ // Sell 25% of tokens
290
+ const sellAmount = tokenBalance / 4n;
291
+
292
+ const result = await withTestRetry(() =>
293
+ sdk.sellWithSlippage(tokenAddress, sellAmount, 500) // 5% slippage
294
+ );
295
+
296
+ expect(result.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
297
+ expect(result.success).toBe(true);
298
+
299
+ // Verify tokens were sold
300
+ const tokenBalanceAfter = await getTokenBalance(publicClient, tokenAddress, walletAddress);
301
+ expect(tokenBalanceAfter).toBeLessThan(tokenBalance);
302
+ });
303
+ });
304
+
305
+ describe('Balance verification summary', () => {
306
+ it('should track full buy/sell cycle balances', async () => {
307
+ // Create fresh token
308
+ const params = generateUniqueTokenParams();
309
+
310
+ // Initial balances
311
+ const initialEth = await publicClient.getBalance({ address: walletAddress });
312
+ console.log(`\n=== Balance Tracking Test ===`);
313
+ console.log(`Initial ETH: ${formatEther(initialEth)}`);
314
+
315
+ // Create and buy
316
+ const buyAmount = parseEther('0.02');
317
+ const createResult = await withTestRetry(() =>
318
+ sdk.createAndBuy({
319
+ ...params,
320
+ creator: walletAddress,
321
+ buyAmountEth: buyAmount,
322
+ minTokensOut: 0n,
323
+ })
324
+ );
325
+
326
+ const tokenAddress = createResult.tokenAddress!;
327
+ const tokensReceived = await getTokenBalance(publicClient, tokenAddress, walletAddress);
328
+ const ethAfterBuy = await publicClient.getBalance({ address: walletAddress });
329
+
330
+ console.log(`After createAndBuy:`);
331
+ console.log(` Tokens received: ${formatEther(tokensReceived)}`);
332
+ console.log(` ETH spent: ${formatEther(initialEth - ethAfterBuy)}`);
333
+
334
+ // Sell all tokens
335
+ const sellResult = await withTestRetry(() =>
336
+ sdk.sell({
337
+ token: tokenAddress,
338
+ amountTokens: tokensReceived,
339
+ minEthOut: 0n,
340
+ })
341
+ );
342
+
343
+ const tokensAfterSell = await getTokenBalance(publicClient, tokenAddress, walletAddress);
344
+ const ethAfterSell = await publicClient.getBalance({ address: walletAddress });
345
+
346
+ console.log(`After sell:`);
347
+ console.log(` Tokens remaining: ${formatEther(tokensAfterSell)}`);
348
+ console.log(` ETH recovered: ${formatEther(ethAfterSell - ethAfterBuy)}`);
349
+ console.log(` Total ETH lost (fees + gas): ${formatEther(initialEth - ethAfterSell)}`);
350
+
351
+ // Verify all tokens were sold
352
+ expect(tokensAfterSell).toBe(0n);
353
+
354
+ // Verify we lost some ETH to fees (but not all)
355
+ expect(ethAfterSell).toBeLessThan(initialEth);
356
+ expect(ethAfterSell).toBeGreaterThan(initialEth - buyAmount);
357
+ });
358
+ });
359
+
360
+ // Migration test - skip by default as it requires a complete curve
361
+ describe.skip('migrate', () => {
362
+ it('should migrate complete curve to Uniswap', async () => {
363
+ const completedToken = process.env.TEST_COMPLETED_TOKEN_ADDRESS as Address | undefined;
364
+ if (!completedToken) {
365
+ console.log('Skipping migration test - no completed token provided');
366
+ return;
367
+ }
368
+
369
+ // Verify curve is complete
370
+ const isComplete = await sdk.isComplete(completedToken);
371
+ expect(isComplete).toBe(true);
372
+
373
+ const result = await sdk.migrate({ token: completedToken });
374
+
375
+ expect(result.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
376
+ expect(result.success).toBe(true);
377
+
378
+ // Verify token is now migrated
379
+ const isMigrated = await sdk.isMigrated(completedToken);
380
+ expect(isMigrated).toBe(true);
381
+ });
382
+ });
383
+ });
@@ -0,0 +1,34 @@
1
+ import { config } from 'dotenv';
2
+ import { resolve } from 'path';
3
+
4
+ // Load test environment variables
5
+ config({ path: resolve(process.cwd(), '.env.test') });
6
+
7
+ // Validate required environment variables for integration tests
8
+ export function validateIntegrationEnv(): boolean {
9
+ const required = ['TEST_PRIVATE_KEY', 'MEGA_ETH_RPC_URL'];
10
+ const missing = required.filter((key) => !process.env[key]);
11
+
12
+ if (missing.length > 0) {
13
+ console.warn(
14
+ `Missing environment variables for integration tests: ${missing.join(', ')}`
15
+ );
16
+ console.warn('Integration tests will be skipped.');
17
+ return false;
18
+ }
19
+
20
+ return true;
21
+ }
22
+
23
+ // Check if integration tests should run
24
+ export const RUN_INTEGRATION_TESTS =
25
+ process.env.RUN_INTEGRATION_TESTS === 'true' && validateIntegrationEnv();
26
+
27
+ // Global test setup
28
+ beforeAll(() => {
29
+ // Any global setup can go here
30
+ });
31
+
32
+ afterAll(() => {
33
+ // Any global cleanup can go here
34
+ });