@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.
- package/dist/index.js +22 -17
- package/dist/index.mjs +22 -17
- package/package.json +7 -1
- package/src/.claude/settings.local.json +2 -1
- package/src/__tests__/helpers/fixtures.ts +136 -0
- package/src/__tests__/helpers/integrationHelpers.ts +258 -0
- package/src/__tests__/helpers/mocks.ts +184 -0
- package/src/__tests__/integration/sdk-read.test.ts +196 -0
- package/src/__tests__/integration/sdk-write.test.ts +383 -0
- package/src/__tests__/setup.ts +34 -0
- package/src/__tests__/unit/bondingCurve.test.ts +357 -0
- package/src/__tests__/unit/constants.test.ts +136 -0
- package/src/__tests__/unit/mania.test.ts +495 -0
- package/src/__tests__/unit/utils.test.ts +328 -0
- package/src/constants.ts +2 -2
- package/src/mania.ts +15 -10
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { parseEther } from 'viem';
|
|
3
|
+
import {
|
|
4
|
+
formatEthValue,
|
|
5
|
+
formatTokenAmount,
|
|
6
|
+
parseEthValue,
|
|
7
|
+
calculateWithSlippage,
|
|
8
|
+
calculateMigrationProgress,
|
|
9
|
+
formatPrice,
|
|
10
|
+
formatMarketCap,
|
|
11
|
+
isValidAddress,
|
|
12
|
+
truncateAddress,
|
|
13
|
+
bpsToPercent,
|
|
14
|
+
percentToBps,
|
|
15
|
+
calculatePriceImpact,
|
|
16
|
+
sleep,
|
|
17
|
+
withRetry,
|
|
18
|
+
} from '../../utils.js';
|
|
19
|
+
|
|
20
|
+
describe('formatEthValue', () => {
|
|
21
|
+
it('should format ETH value with default 4 decimals', () => {
|
|
22
|
+
const wei = parseEther('1.23456789');
|
|
23
|
+
expect(formatEthValue(wei)).toBe('1.2346');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should format ETH value with custom decimals', () => {
|
|
27
|
+
const wei = parseEther('1.23456789');
|
|
28
|
+
expect(formatEthValue(wei, 2)).toBe('1.23');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should handle zero value', () => {
|
|
32
|
+
expect(formatEthValue(0n)).toBe('0.0000');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle small amounts', () => {
|
|
36
|
+
const wei = parseEther('0.0001');
|
|
37
|
+
expect(formatEthValue(wei, 4)).toBe('0.0001');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle large amounts', () => {
|
|
41
|
+
const wei = parseEther('1000000');
|
|
42
|
+
expect(formatEthValue(wei)).toBe('1000000.0000');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('formatTokenAmount', () => {
|
|
47
|
+
it('should format billions with B suffix', () => {
|
|
48
|
+
const amount = parseEther('1500000000'); // 1.5B
|
|
49
|
+
expect(formatTokenAmount(amount)).toBe('1.50B');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should format millions with M suffix', () => {
|
|
53
|
+
const amount = parseEther('1500000'); // 1.5M
|
|
54
|
+
expect(formatTokenAmount(amount)).toBe('1.50M');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should format thousands with K suffix', () => {
|
|
58
|
+
const amount = parseEther('1500'); // 1.5K
|
|
59
|
+
expect(formatTokenAmount(amount)).toBe('1.50K');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should format small amounts without suffix', () => {
|
|
63
|
+
const amount = parseEther('500');
|
|
64
|
+
expect(formatTokenAmount(amount)).toBe('500.00');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should use custom decimals', () => {
|
|
68
|
+
const amount = parseEther('1234567890');
|
|
69
|
+
expect(formatTokenAmount(amount, 3)).toBe('1.235B');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('parseEthValue', () => {
|
|
74
|
+
it('should parse decimal string to wei', () => {
|
|
75
|
+
expect(parseEthValue('1.5')).toBe(parseEther('1.5'));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should parse integer string to wei', () => {
|
|
79
|
+
expect(parseEthValue('10')).toBe(parseEther('10'));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should parse zero', () => {
|
|
83
|
+
expect(parseEthValue('0')).toBe(0n);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('calculateWithSlippage', () => {
|
|
88
|
+
it('should apply 1% slippage (100 bps)', () => {
|
|
89
|
+
const amount = parseEther('1');
|
|
90
|
+
const result = calculateWithSlippage(amount, 100);
|
|
91
|
+
expect(result).toBe(parseEther('0.99'));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should apply 5% slippage (500 bps)', () => {
|
|
95
|
+
const amount = parseEther('1');
|
|
96
|
+
const result = calculateWithSlippage(amount, 500);
|
|
97
|
+
expect(result).toBe(parseEther('0.95'));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle 0% slippage', () => {
|
|
101
|
+
const amount = parseEther('1');
|
|
102
|
+
const result = calculateWithSlippage(amount, 0);
|
|
103
|
+
expect(result).toBe(amount);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle 10% slippage (1000 bps)', () => {
|
|
107
|
+
const amount = parseEther('1');
|
|
108
|
+
const result = calculateWithSlippage(amount, 1000);
|
|
109
|
+
expect(result).toBe(parseEther('0.9'));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('calculateMigrationProgress', () => {
|
|
114
|
+
it('should return 0 for zero reserves', () => {
|
|
115
|
+
expect(calculateMigrationProgress(0n)).toBe(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should return 50 for half-filled (2 ETH)', () => {
|
|
119
|
+
const twoEth = parseEther('2');
|
|
120
|
+
expect(calculateMigrationProgress(twoEth)).toBe(50);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should return 100 for threshold reached (4 ETH)', () => {
|
|
124
|
+
const fourEth = parseEther('4');
|
|
125
|
+
expect(calculateMigrationProgress(fourEth)).toBe(100);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should return 100 for over threshold', () => {
|
|
129
|
+
const fiveEth = parseEther('5');
|
|
130
|
+
expect(calculateMigrationProgress(fiveEth)).toBe(100);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should return 25 for 1 ETH', () => {
|
|
134
|
+
const oneEth = parseEther('1');
|
|
135
|
+
expect(calculateMigrationProgress(oneEth)).toBe(25);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('formatPrice', () => {
|
|
140
|
+
it('should format very small prices with exponential notation', () => {
|
|
141
|
+
const price = 100000000000n; // 0.0000001 ETH
|
|
142
|
+
const result = formatPrice(price);
|
|
143
|
+
expect(result).toMatch(/e-/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should format small prices with 8 decimals', () => {
|
|
147
|
+
const price = 50000000000000n; // 0.00005 ETH
|
|
148
|
+
const result = formatPrice(price);
|
|
149
|
+
expect(result).toBe('0.00005000');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should format medium prices with 6 decimals', () => {
|
|
153
|
+
const price = 5000000000000000n; // 0.005 ETH
|
|
154
|
+
const result = formatPrice(price);
|
|
155
|
+
expect(result).toBe('0.005000');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should format normal prices with 4 decimals', () => {
|
|
159
|
+
const price = 50000000000000000n; // 0.05 ETH
|
|
160
|
+
const result = formatPrice(price);
|
|
161
|
+
expect(result).toBe('0.0500');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('formatMarketCap', () => {
|
|
166
|
+
it('should format large market caps with K suffix', () => {
|
|
167
|
+
const marketCap = parseEther('1500');
|
|
168
|
+
expect(formatMarketCap(marketCap)).toBe('1.50K ETH');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should format small market caps without suffix', () => {
|
|
172
|
+
const marketCap = parseEther('500');
|
|
173
|
+
expect(formatMarketCap(marketCap)).toBe('500.00 ETH');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should format zero market cap', () => {
|
|
177
|
+
expect(formatMarketCap(0n)).toBe('0.00 ETH');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('isValidAddress', () => {
|
|
182
|
+
it('should return true for valid address', () => {
|
|
183
|
+
expect(isValidAddress('0x1234567890abcdef1234567890abcdef12345678')).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should return true for checksummed address', () => {
|
|
187
|
+
expect(isValidAddress('0xABCDEF1234567890abcdef1234567890ABCDEF12')).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should return false for invalid hex', () => {
|
|
191
|
+
expect(isValidAddress('0xGHIJ567890abcdef1234567890abcdef12345678')).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should return false for wrong length', () => {
|
|
195
|
+
expect(isValidAddress('0x1234')).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should return false for missing prefix', () => {
|
|
199
|
+
expect(isValidAddress('1234567890abcdef1234567890abcdef12345678')).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('truncateAddress', () => {
|
|
204
|
+
const address = '0x1234567890abcdef1234567890abcdef12345678' as `0x${string}`;
|
|
205
|
+
|
|
206
|
+
it('should truncate with default 4 chars', () => {
|
|
207
|
+
expect(truncateAddress(address)).toBe('0x1234...5678');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should truncate with custom length', () => {
|
|
211
|
+
expect(truncateAddress(address, 6)).toBe('0x123456...345678');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle 2 chars', () => {
|
|
215
|
+
expect(truncateAddress(address, 2)).toBe('0x12...78');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('bpsToPercent', () => {
|
|
220
|
+
it('should convert 100 bps to 1%', () => {
|
|
221
|
+
expect(bpsToPercent(100)).toBe(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should convert 50 bps to 0.5%', () => {
|
|
225
|
+
expect(bpsToPercent(50)).toBe(0.5);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should convert 1000 bps to 10%', () => {
|
|
229
|
+
expect(bpsToPercent(1000)).toBe(10);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should handle bigint input', () => {
|
|
233
|
+
expect(bpsToPercent(100n)).toBe(1);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('percentToBps', () => {
|
|
238
|
+
it('should convert 1% to 100 bps', () => {
|
|
239
|
+
expect(percentToBps(1)).toBe(100);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should convert 0.5% to 50 bps', () => {
|
|
243
|
+
expect(percentToBps(0.5)).toBe(50);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should convert 10% to 1000 bps', () => {
|
|
247
|
+
expect(percentToBps(10)).toBe(1000);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should round fractional bps', () => {
|
|
251
|
+
expect(percentToBps(1.234)).toBe(123);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('calculatePriceImpact', () => {
|
|
256
|
+
it('should calculate positive price impact', () => {
|
|
257
|
+
const current = parseEther('0.01');
|
|
258
|
+
const newPrice = parseEther('0.011');
|
|
259
|
+
expect(calculatePriceImpact(current, newPrice)).toBe(10);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should calculate negative price impact', () => {
|
|
263
|
+
const current = parseEther('0.01');
|
|
264
|
+
const newPrice = parseEther('0.009');
|
|
265
|
+
expect(calculatePriceImpact(current, newPrice)).toBe(-10);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should return 0 for zero current price', () => {
|
|
269
|
+
expect(calculatePriceImpact(0n, parseEther('0.01'))).toBe(0);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should return 0 for same price', () => {
|
|
273
|
+
const price = parseEther('0.01');
|
|
274
|
+
expect(calculatePriceImpact(price, price)).toBe(0);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('sleep', () => {
|
|
279
|
+
it('should delay for specified milliseconds', async () => {
|
|
280
|
+
const start = Date.now();
|
|
281
|
+
await sleep(50);
|
|
282
|
+
const elapsed = Date.now() - start;
|
|
283
|
+
expect(elapsed).toBeGreaterThanOrEqual(45);
|
|
284
|
+
expect(elapsed).toBeLessThan(100);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('withRetry', () => {
|
|
289
|
+
it('should succeed on first try', async () => {
|
|
290
|
+
const fn = vi.fn().mockResolvedValue('success');
|
|
291
|
+
const result = await withRetry(fn, 3, 10);
|
|
292
|
+
expect(result).toBe('success');
|
|
293
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should retry on failure and eventually succeed', async () => {
|
|
297
|
+
const fn = vi
|
|
298
|
+
.fn()
|
|
299
|
+
.mockRejectedValueOnce(new Error('fail'))
|
|
300
|
+
.mockRejectedValueOnce(new Error('fail'))
|
|
301
|
+
.mockResolvedValue('success');
|
|
302
|
+
|
|
303
|
+
const result = await withRetry(fn, 3, 10);
|
|
304
|
+
expect(result).toBe('success');
|
|
305
|
+
expect(fn).toHaveBeenCalledTimes(3);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should throw after max retries exhausted', async () => {
|
|
309
|
+
const fn = vi.fn().mockRejectedValue(new Error('always fails'));
|
|
310
|
+
|
|
311
|
+
await expect(withRetry(fn, 3, 10)).rejects.toThrow('always fails');
|
|
312
|
+
expect(fn).toHaveBeenCalledTimes(3);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should use exponential backoff', async () => {
|
|
316
|
+
const fn = vi
|
|
317
|
+
.fn()
|
|
318
|
+
.mockRejectedValueOnce(new Error('fail'))
|
|
319
|
+
.mockResolvedValue('success');
|
|
320
|
+
|
|
321
|
+
const start = Date.now();
|
|
322
|
+
await withRetry(fn, 3, 50);
|
|
323
|
+
const elapsed = Date.now() - start;
|
|
324
|
+
|
|
325
|
+
// First retry should be at 50ms (base delay)
|
|
326
|
+
expect(elapsed).toBeGreaterThanOrEqual(45);
|
|
327
|
+
});
|
|
328
|
+
});
|
package/src/constants.ts
CHANGED
|
@@ -52,8 +52,8 @@ export interface ChainConfig {
|
|
|
52
52
|
*/
|
|
53
53
|
export const CHAIN_CONFIGS: Record<number, ChainConfig> = {
|
|
54
54
|
// Mega Eth Testnet
|
|
55
|
-
|
|
56
|
-
chainId:
|
|
55
|
+
6343: {
|
|
56
|
+
chainId: 6343,
|
|
57
57
|
name: "Mega Eth Testnet",
|
|
58
58
|
factoryAddress: "0x0d593cE47EBA2d15a77ddbAc41BdE6d03CC9241b" as Address,
|
|
59
59
|
wethAddress: "0x4200000000000000000000000000000000000006" as Address,
|
package/src/mania.ts
CHANGED
|
@@ -317,12 +317,13 @@ export class ManiaSDK {
|
|
|
317
317
|
const wallet = this.getConnectedWallet();
|
|
318
318
|
|
|
319
319
|
const hash = await wallet.writeContract({
|
|
320
|
-
chain:
|
|
321
|
-
account:
|
|
320
|
+
chain: wallet.chain,
|
|
321
|
+
account: wallet.account!,
|
|
322
322
|
address: this.factoryAddress,
|
|
323
323
|
abi: MANIA_FACTORY_ABI,
|
|
324
324
|
functionName: "create",
|
|
325
325
|
args: [params.name, params.symbol, params.uri, params.creator],
|
|
326
|
+
gas: 300_000_000n,
|
|
326
327
|
});
|
|
327
328
|
|
|
328
329
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
@@ -353,13 +354,14 @@ export class ManiaSDK {
|
|
|
353
354
|
const wallet = this.getConnectedWallet();
|
|
354
355
|
|
|
355
356
|
const hash = await wallet.writeContract({
|
|
356
|
-
chain:
|
|
357
|
-
account:
|
|
357
|
+
chain: wallet.chain,
|
|
358
|
+
account: wallet.account!,
|
|
358
359
|
address: this.factoryAddress,
|
|
359
360
|
abi: MANIA_FACTORY_ABI,
|
|
360
361
|
functionName: "createAndBuy",
|
|
361
362
|
args: [params.name, params.symbol, params.uri, params.creator, params.minTokensOut],
|
|
362
363
|
value: params.buyAmountEth,
|
|
364
|
+
gas: 300_000_000n,
|
|
363
365
|
});
|
|
364
366
|
|
|
365
367
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
@@ -392,13 +394,14 @@ export class ManiaSDK {
|
|
|
392
394
|
const recipient = params.recipient ?? wallet.account!.address;
|
|
393
395
|
|
|
394
396
|
const hash = await wallet.writeContract({
|
|
395
|
-
chain:
|
|
396
|
-
account:
|
|
397
|
+
chain: wallet.chain,
|
|
398
|
+
account: wallet.account!,
|
|
397
399
|
address: this.factoryAddress,
|
|
398
400
|
abi: MANIA_FACTORY_ABI,
|
|
399
401
|
functionName: "buy",
|
|
400
402
|
args: [params.token, params.minTokensOut, recipient],
|
|
401
403
|
value: params.amountEth,
|
|
404
|
+
gas: 300_000_000n,
|
|
402
405
|
});
|
|
403
406
|
|
|
404
407
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
@@ -441,12 +444,13 @@ export class ManiaSDK {
|
|
|
441
444
|
const wallet = this.getConnectedWallet();
|
|
442
445
|
|
|
443
446
|
const hash = await wallet.writeContract({
|
|
444
|
-
chain:
|
|
445
|
-
account:
|
|
447
|
+
chain: wallet.chain,
|
|
448
|
+
account: wallet.account!,
|
|
446
449
|
address: this.factoryAddress,
|
|
447
450
|
abi: MANIA_FACTORY_ABI,
|
|
448
451
|
functionName: "sell",
|
|
449
452
|
args: [params.token, params.amountTokens, params.minEthOut],
|
|
453
|
+
gas: 300_000_000n,
|
|
450
454
|
});
|
|
451
455
|
|
|
452
456
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
@@ -491,13 +495,14 @@ export class ManiaSDK {
|
|
|
491
495
|
const globalState = await this.getGlobalState();
|
|
492
496
|
|
|
493
497
|
const hash = await wallet.writeContract({
|
|
494
|
-
chain:
|
|
495
|
-
account:
|
|
498
|
+
chain: wallet.chain,
|
|
499
|
+
account: wallet.account!,
|
|
496
500
|
address: this.factoryAddress,
|
|
497
501
|
abi: MANIA_FACTORY_ABI,
|
|
498
502
|
functionName: "migrate",
|
|
499
503
|
args: [params.token],
|
|
500
504
|
value: globalState.poolMigrationFee,
|
|
505
|
+
gas: 300_000_000n,
|
|
501
506
|
});
|
|
502
507
|
|
|
503
508
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|