@mania-labs/mania-sdk 1.0.0

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.mjs ADDED
@@ -0,0 +1,1165 @@
1
+ // src/mania.ts
2
+ import {
3
+ createPublicClient,
4
+ createWalletClient,
5
+ http,
6
+ parseEventLogs
7
+ } from "viem";
8
+ import { privateKeyToAccount } from "viem/accounts";
9
+
10
+ // src/constants.ts
11
+ var PROTOCOL_FEE_BASIS_POINTS = 70n;
12
+ var CREATOR_FEE_BASIS_POINTS = 30n;
13
+ var TOTAL_FEE_BASIS_POINTS = PROTOCOL_FEE_BASIS_POINTS + CREATOR_FEE_BASIS_POINTS;
14
+ var MAX_FEE_BASIS_POINTS = 1000n;
15
+ var MIGRATION_THRESHOLD = 4000000000000000000n;
16
+ var TOKENS_FOR_LP = 206900000000000000000000000n;
17
+ var MAX_MIGRATE_FEES = 300000000000000n;
18
+ var UNISWAP_FEE_TIER = 3e3;
19
+ var TICK_LOWER = -887220;
20
+ var TICK_UPPER = 887220;
21
+ var DEFAULT_SLIPPAGE_BPS = 100;
22
+ var BPS_DENOMINATOR = 10000n;
23
+ var CHAIN_CONFIGS = {
24
+ // Mega Eth Testnet
25
+ 6543: {
26
+ chainId: 6543,
27
+ name: "Mega Eth Testnet",
28
+ factoryAddress: "0x0d593cE47EBA2d15a77ddbAc41BdE6d03CC9241b",
29
+ wethAddress: "0x4200000000000000000000000000000000000006",
30
+ nonfungiblePositionManager: "0xa204A97EF8Bd2E3198f19EB5a804680467BD85f5",
31
+ uniswapV3Factory: "0x619fb6C12c36b57a8bAb05e98F42C43745DCf69f",
32
+ blockExplorer: "https://megaeth-testnet-v2.blockscout.com"
33
+ }
34
+ };
35
+ function getChainConfig(chainId) {
36
+ return CHAIN_CONFIGS[chainId];
37
+ }
38
+
39
+ // src/bondingCurve.ts
40
+ var BondingCurve = class _BondingCurve {
41
+ state;
42
+ feeBasisPoints;
43
+ constructor(state, feeBasisPoints) {
44
+ this.state = state;
45
+ this.feeBasisPoints = feeBasisPoints;
46
+ }
47
+ /**
48
+ * Get the current bonding curve state
49
+ */
50
+ getState() {
51
+ return { ...this.state };
52
+ }
53
+ /**
54
+ * Check if the bonding curve is complete (reached migration threshold)
55
+ */
56
+ isComplete() {
57
+ return this.state.complete;
58
+ }
59
+ /**
60
+ * Check if the bonding curve has been migrated (all reserves are 0)
61
+ */
62
+ isMigrated() {
63
+ return this.state.realEthReserves === 0n && this.state.virtualEthReserves === 0n && this.state.realTokenReserves === 0n && this.state.virtualTokenReserves === 0n;
64
+ }
65
+ /**
66
+ * Get the current token price in ETH (wei per token)
67
+ *
68
+ * Price is calculated as: virtualEthReserves / virtualTokenReserves
69
+ */
70
+ getCurrentPrice() {
71
+ if (this.state.virtualTokenReserves === 0n) {
72
+ return 0n;
73
+ }
74
+ return this.state.virtualEthReserves * 10n ** 18n / this.state.virtualTokenReserves;
75
+ }
76
+ /**
77
+ * Get the market cap in ETH
78
+ *
79
+ * Market cap = current price * total supply
80
+ */
81
+ getMarketCapEth() {
82
+ const price = this.getCurrentPrice();
83
+ return price * this.state.tokenTotalSupply / 10n ** 18n;
84
+ }
85
+ /**
86
+ * Get the progress towards migration threshold (0-100)
87
+ */
88
+ getMigrationProgress() {
89
+ if (this.state.complete) {
90
+ return 100;
91
+ }
92
+ const progress = this.state.realEthReserves * 100n / MIGRATION_THRESHOLD;
93
+ return Number(progress);
94
+ }
95
+ /**
96
+ * Calculate ETH remaining until migration
97
+ */
98
+ getEthUntilMigration() {
99
+ if (this.state.realEthReserves >= MIGRATION_THRESHOLD) {
100
+ return 0n;
101
+ }
102
+ return MIGRATION_THRESHOLD - this.state.realEthReserves;
103
+ }
104
+ /**
105
+ * Get a quote for buying tokens with a specific ETH amount
106
+ *
107
+ * @param ethAmount - Amount of ETH to spend (in wei)
108
+ * @returns Quote with tokens out, fees, and price impact
109
+ */
110
+ getBuyQuote(ethAmount) {
111
+ const fee = ethAmount * this.feeBasisPoints / BPS_DENOMINATOR;
112
+ const netEth = ethAmount - fee;
113
+ const tokensOut = this.state.virtualTokenReserves * netEth / (this.state.virtualEthReserves + netEth);
114
+ const cappedTokensOut = tokensOut > this.state.realTokenReserves ? this.state.realTokenReserves : tokensOut;
115
+ const pricePerToken = cappedTokensOut > 0n ? netEth * 10n ** 18n / cappedTokensOut : 0n;
116
+ return {
117
+ tokensOut: cappedTokensOut,
118
+ fee,
119
+ netEth,
120
+ pricePerToken
121
+ };
122
+ }
123
+ /**
124
+ * Get a quote for selling tokens
125
+ *
126
+ * @param tokenAmount - Amount of tokens to sell
127
+ * @returns Quote with ETH out, fees, and price impact
128
+ */
129
+ getSellQuote(tokenAmount) {
130
+ const ethOutGross = tokenAmount * this.state.virtualEthReserves / (this.state.virtualTokenReserves + tokenAmount);
131
+ const fee = ethOutGross * this.feeBasisPoints / BPS_DENOMINATOR;
132
+ const ethOutNet = ethOutGross - fee;
133
+ const pricePerToken = tokenAmount > 0n ? ethOutNet * 10n ** 18n / tokenAmount : 0n;
134
+ return {
135
+ ethOutGross,
136
+ fee,
137
+ ethOutNet,
138
+ pricePerToken
139
+ };
140
+ }
141
+ /**
142
+ * Calculate minimum tokens out with slippage tolerance
143
+ *
144
+ * @param ethAmount - Amount of ETH to spend
145
+ * @param slippageBps - Slippage tolerance in basis points (e.g., 100 = 1%)
146
+ * @returns Minimum tokens to receive
147
+ */
148
+ calculateMinTokensOut(ethAmount, slippageBps) {
149
+ const quote = this.getBuyQuote(ethAmount);
150
+ const slippageMultiplier = BPS_DENOMINATOR - BigInt(slippageBps);
151
+ return quote.tokensOut * slippageMultiplier / BPS_DENOMINATOR;
152
+ }
153
+ /**
154
+ * Calculate minimum ETH out with slippage tolerance
155
+ *
156
+ * @param tokenAmount - Amount of tokens to sell
157
+ * @param slippageBps - Slippage tolerance in basis points (e.g., 100 = 1%)
158
+ * @returns Minimum ETH to receive
159
+ */
160
+ calculateMinEthOut(tokenAmount, slippageBps) {
161
+ const quote = this.getSellQuote(tokenAmount);
162
+ const slippageMultiplier = BPS_DENOMINATOR - BigInt(slippageBps);
163
+ return quote.ethOutNet * slippageMultiplier / BPS_DENOMINATOR;
164
+ }
165
+ /**
166
+ * Calculate price impact for a buy order
167
+ *
168
+ * @param ethAmount - Amount of ETH to spend
169
+ * @returns Price impact as a percentage (e.g., 2.5 = 2.5%)
170
+ */
171
+ calculateBuyPriceImpact(ethAmount) {
172
+ const currentPrice = this.getCurrentPrice();
173
+ if (currentPrice === 0n) return 0;
174
+ const quote = this.getBuyQuote(ethAmount);
175
+ if (quote.tokensOut === 0n) return 0;
176
+ const newVirtualEth = this.state.virtualEthReserves + quote.netEth;
177
+ const newVirtualTokens = this.state.virtualTokenReserves - quote.tokensOut;
178
+ const newPrice = newVirtualEth * 10n ** 18n / newVirtualTokens;
179
+ const impact = Number((newPrice - currentPrice) * 10000n / currentPrice) / 100;
180
+ return impact;
181
+ }
182
+ /**
183
+ * Calculate price impact for a sell order
184
+ *
185
+ * @param tokenAmount - Amount of tokens to sell
186
+ * @returns Price impact as a percentage (negative for sells)
187
+ */
188
+ calculateSellPriceImpact(tokenAmount) {
189
+ const currentPrice = this.getCurrentPrice();
190
+ if (currentPrice === 0n) return 0;
191
+ const quote = this.getSellQuote(tokenAmount);
192
+ const newVirtualEth = this.state.virtualEthReserves - quote.ethOutGross;
193
+ const newVirtualTokens = this.state.virtualTokenReserves + tokenAmount;
194
+ if (newVirtualTokens === 0n) return -100;
195
+ const newPrice = newVirtualEth * 10n ** 18n / newVirtualTokens;
196
+ const impact = Number((newPrice - currentPrice) * 10000n / currentPrice) / 100;
197
+ return impact;
198
+ }
199
+ /**
200
+ * Check if a buy would exceed the migration threshold
201
+ *
202
+ * @param ethAmount - Amount of ETH to spend
203
+ * @returns True if the buy would exceed threshold
204
+ */
205
+ wouldExceedMigrationThreshold(ethAmount) {
206
+ const fee = ethAmount * this.feeBasisPoints / BPS_DENOMINATOR;
207
+ const netEth = ethAmount - fee;
208
+ return this.state.realEthReserves + netEth > MIGRATION_THRESHOLD;
209
+ }
210
+ /**
211
+ * Calculate maximum ETH that can be spent before hitting migration threshold
212
+ *
213
+ * @returns Maximum ETH amount (in wei)
214
+ */
215
+ getMaxBuyAmount() {
216
+ if (this.state.complete) {
217
+ return 0n;
218
+ }
219
+ const remainingEth = MIGRATION_THRESHOLD - this.state.realEthReserves;
220
+ const maxEthWithFees = remainingEth * BPS_DENOMINATOR / (BPS_DENOMINATOR - this.feeBasisPoints);
221
+ return maxEthWithFees;
222
+ }
223
+ /**
224
+ * Update the internal state (used after trades)
225
+ */
226
+ updateState(newState) {
227
+ this.state = { ...newState };
228
+ }
229
+ /**
230
+ * Create a BondingCurve instance from raw contract data
231
+ */
232
+ static fromContractData(data, feeBasisPoints) {
233
+ const state = {
234
+ virtualTokenReserves: data[0],
235
+ virtualEthReserves: data[1],
236
+ realTokenReserves: data[2],
237
+ realEthReserves: data[3],
238
+ tokenTotalSupply: data[4],
239
+ complete: data[5],
240
+ trackVolume: data[6]
241
+ };
242
+ return new _BondingCurve(state, feeBasisPoints);
243
+ }
244
+ };
245
+ function calculateBuyAmount(virtualTokenReserves, virtualEthReserves, ethAmount, feeBasisPoints) {
246
+ const fee = ethAmount * feeBasisPoints / BPS_DENOMINATOR;
247
+ const netEth = ethAmount - fee;
248
+ return virtualTokenReserves * netEth / (virtualEthReserves + netEth);
249
+ }
250
+ function calculateSellAmount(virtualTokenReserves, virtualEthReserves, tokenAmount, feeBasisPoints) {
251
+ const ethOutGross = tokenAmount * virtualEthReserves / (virtualTokenReserves + tokenAmount);
252
+ const fee = ethOutGross * feeBasisPoints / BPS_DENOMINATOR;
253
+ return ethOutGross - fee;
254
+ }
255
+
256
+ // src/abi.ts
257
+ var MANIA_FACTORY_ABI = [
258
+ {
259
+ type: "function",
260
+ name: "bondingCurves",
261
+ inputs: [{ name: "mint", type: "address" }],
262
+ outputs: [
263
+ { name: "virtualTokenReserves", type: "uint256" },
264
+ { name: "virtualEthReserves", type: "uint64" },
265
+ { name: "realTokenReserves", type: "uint256" },
266
+ { name: "realEthReserves", type: "uint64" },
267
+ { name: "tokenTotalSupply", type: "uint256" },
268
+ { name: "complete", type: "bool" },
269
+ { name: "trackVolume", type: "bool" }
270
+ ],
271
+ stateMutability: "view"
272
+ },
273
+ {
274
+ type: "function",
275
+ name: "buy",
276
+ inputs: [
277
+ { name: "token", type: "address" },
278
+ { name: "minTokensOut", type: "uint256" },
279
+ { name: "recipient", type: "address" }
280
+ ],
281
+ outputs: [],
282
+ stateMutability: "payable"
283
+ },
284
+ {
285
+ type: "function",
286
+ name: "create",
287
+ inputs: [
288
+ { name: "name", type: "string" },
289
+ { name: "symbol", type: "string" },
290
+ { name: "uri", type: "string" },
291
+ { name: "creator", type: "address" }
292
+ ],
293
+ outputs: [{ name: "token", type: "address" }],
294
+ stateMutability: "nonpayable"
295
+ },
296
+ {
297
+ type: "function",
298
+ name: "createAndBuy",
299
+ inputs: [
300
+ { name: "name", type: "string" },
301
+ { name: "symbol", type: "string" },
302
+ { name: "uri", type: "string" },
303
+ { name: "creator", type: "address" },
304
+ { name: "minTokensOut", type: "uint256" }
305
+ ],
306
+ outputs: [{ name: "token", type: "address" }],
307
+ stateMutability: "payable"
308
+ },
309
+ {
310
+ type: "function",
311
+ name: "getBondingCurve",
312
+ inputs: [{ name: "token", type: "address" }],
313
+ outputs: [
314
+ {
315
+ name: "curve",
316
+ type: "tuple",
317
+ components: [
318
+ { name: "virtualTokenReserves", type: "uint256" },
319
+ { name: "virtualEthReserves", type: "uint64" },
320
+ { name: "realTokenReserves", type: "uint256" },
321
+ { name: "realEthReserves", type: "uint64" },
322
+ { name: "tokenTotalSupply", type: "uint256" },
323
+ { name: "complete", type: "bool" },
324
+ { name: "trackVolume", type: "bool" }
325
+ ]
326
+ }
327
+ ],
328
+ stateMutability: "view"
329
+ },
330
+ {
331
+ type: "function",
332
+ name: "getBuyQuote",
333
+ inputs: [
334
+ { name: "token", type: "address" },
335
+ { name: "ethAmount", type: "uint64" }
336
+ ],
337
+ outputs: [{ name: "tokenAmount", type: "uint256" }],
338
+ stateMutability: "view"
339
+ },
340
+ {
341
+ type: "function",
342
+ name: "getFee",
343
+ inputs: [{ name: "amount", type: "uint64" }],
344
+ outputs: [{ name: "fee", type: "uint128" }],
345
+ stateMutability: "view"
346
+ },
347
+ {
348
+ type: "function",
349
+ name: "getSellQuote",
350
+ inputs: [
351
+ { name: "token", type: "address" },
352
+ { name: "amount", type: "uint256" }
353
+ ],
354
+ outputs: [{ name: "ethOutput", type: "uint128" }],
355
+ stateMutability: "view"
356
+ },
357
+ {
358
+ type: "function",
359
+ name: "global",
360
+ inputs: [],
361
+ outputs: [
362
+ { name: "initialized", type: "bool" },
363
+ { name: "authority", type: "address" },
364
+ { name: "feeRecipient", type: "address" },
365
+ { name: "initialVirtualTokenReserves", type: "uint256" },
366
+ { name: "initialVirtualEthReserves", type: "uint64" },
367
+ { name: "initialRealTokenReserves", type: "uint256" },
368
+ { name: "tokenTotalSupply", type: "uint256" },
369
+ { name: "feeBasisPoints", type: "uint64" },
370
+ { name: "withdrawAuthority", type: "address" },
371
+ { name: "enableMigrate", type: "bool" },
372
+ { name: "poolMigrationFee", type: "uint64" }
373
+ ],
374
+ stateMutability: "view"
375
+ },
376
+ {
377
+ type: "function",
378
+ name: "migrate",
379
+ inputs: [{ name: "token", type: "address" }],
380
+ outputs: [],
381
+ stateMutability: "payable"
382
+ },
383
+ {
384
+ type: "function",
385
+ name: "sell",
386
+ inputs: [
387
+ { name: "token", type: "address" },
388
+ { name: "amount", type: "uint256" },
389
+ { name: "minEthOutput", type: "uint64" }
390
+ ],
391
+ outputs: [],
392
+ stateMutability: "nonpayable"
393
+ },
394
+ {
395
+ type: "function",
396
+ name: "userVolume",
397
+ inputs: [{ name: "user", type: "address" }],
398
+ outputs: [{ name: "volume", type: "uint256" }],
399
+ stateMutability: "view"
400
+ },
401
+ {
402
+ type: "event",
403
+ name: "CreateEvent",
404
+ inputs: [
405
+ { name: "name", type: "string", indexed: false },
406
+ { name: "symbol", type: "string", indexed: false },
407
+ { name: "uri", type: "string", indexed: false },
408
+ { name: "mint", type: "address", indexed: true },
409
+ { name: "user", type: "address", indexed: true },
410
+ { name: "creator", type: "address", indexed: true },
411
+ { name: "timestamp", type: "uint256", indexed: false }
412
+ ],
413
+ anonymous: false
414
+ },
415
+ {
416
+ type: "event",
417
+ name: "TradeEvent",
418
+ inputs: [
419
+ { name: "mint", type: "address", indexed: true },
420
+ { name: "ethAmount", type: "uint64", indexed: false },
421
+ { name: "tokenAmount", type: "uint256", indexed: false },
422
+ { name: "isBuy", type: "bool", indexed: false },
423
+ { name: "user", type: "address", indexed: true },
424
+ { name: "timestamp", type: "uint256", indexed: false },
425
+ { name: "virtualEthReserves", type: "uint64", indexed: false },
426
+ { name: "virtualTokenReserves", type: "uint256", indexed: false },
427
+ { name: "realEthReserves", type: "uint64", indexed: false },
428
+ { name: "realTokenReserves", type: "uint256", indexed: false }
429
+ ],
430
+ anonymous: false
431
+ },
432
+ {
433
+ type: "event",
434
+ name: "CompleteEvent",
435
+ inputs: [
436
+ { name: "user", type: "address", indexed: true },
437
+ { name: "mint", type: "address", indexed: true },
438
+ { name: "timestamp", type: "uint256", indexed: false }
439
+ ],
440
+ anonymous: false
441
+ },
442
+ {
443
+ type: "event",
444
+ name: "CompleteManiaAmmMigrationEvent",
445
+ inputs: [
446
+ { name: "user", type: "address", indexed: true },
447
+ { name: "mint", type: "address", indexed: true },
448
+ { name: "mintAmount", type: "uint256", indexed: false },
449
+ { name: "ethAmount", type: "uint64", indexed: false },
450
+ { name: "poolMigrationFee", type: "uint64", indexed: false },
451
+ { name: "timestamp", type: "uint256", indexed: false },
452
+ { name: "pool", type: "address", indexed: true }
453
+ ],
454
+ anonymous: false
455
+ }
456
+ ];
457
+ var ERC20_ABI = [
458
+ {
459
+ type: "function",
460
+ name: "name",
461
+ inputs: [],
462
+ outputs: [{ type: "string" }],
463
+ stateMutability: "view"
464
+ },
465
+ {
466
+ type: "function",
467
+ name: "symbol",
468
+ inputs: [],
469
+ outputs: [{ type: "string" }],
470
+ stateMutability: "view"
471
+ },
472
+ {
473
+ type: "function",
474
+ name: "decimals",
475
+ inputs: [],
476
+ outputs: [{ type: "uint8" }],
477
+ stateMutability: "view"
478
+ },
479
+ {
480
+ type: "function",
481
+ name: "totalSupply",
482
+ inputs: [],
483
+ outputs: [{ type: "uint256" }],
484
+ stateMutability: "view"
485
+ },
486
+ {
487
+ type: "function",
488
+ name: "balanceOf",
489
+ inputs: [{ name: "account", type: "address" }],
490
+ outputs: [{ type: "uint256" }],
491
+ stateMutability: "view"
492
+ },
493
+ {
494
+ type: "function",
495
+ name: "allowance",
496
+ inputs: [
497
+ { name: "owner", type: "address" },
498
+ { name: "spender", type: "address" }
499
+ ],
500
+ outputs: [{ type: "uint256" }],
501
+ stateMutability: "view"
502
+ },
503
+ {
504
+ type: "function",
505
+ name: "approve",
506
+ inputs: [
507
+ { name: "spender", type: "address" },
508
+ { name: "amount", type: "uint256" }
509
+ ],
510
+ outputs: [{ type: "bool" }],
511
+ stateMutability: "nonpayable"
512
+ },
513
+ {
514
+ type: "function",
515
+ name: "transfer",
516
+ inputs: [
517
+ { name: "to", type: "address" },
518
+ { name: "amount", type: "uint256" }
519
+ ],
520
+ outputs: [{ type: "bool" }],
521
+ stateMutability: "nonpayable"
522
+ }
523
+ ];
524
+
525
+ // src/mania.ts
526
+ var ManiaSDK = class _ManiaSDK {
527
+ publicClient;
528
+ walletClient = null;
529
+ factoryAddress;
530
+ chainId;
531
+ /**
532
+ * Create a new ManiaSDK instance
533
+ *
534
+ * @param config - SDK configuration
535
+ * @param config.factoryAddress - ManiaFactory contract address
536
+ * @param config.rpcUrl - RPC URL for the chain
537
+ * @param config.chainId - Chain ID (optional, will be fetched if not provided)
538
+ */
539
+ constructor(config) {
540
+ this.factoryAddress = config.factoryAddress;
541
+ this.chainId = config.chainId ?? 1;
542
+ this.publicClient = createPublicClient({
543
+ transport: http(config.rpcUrl)
544
+ });
545
+ }
546
+ /**
547
+ * Create SDK instance from chain ID using default configuration
548
+ *
549
+ * @param chainId - Chain ID
550
+ * @param rpcUrl - RPC URL (optional, uses public RPC if not provided)
551
+ * @returns ManiaSDK instance
552
+ */
553
+ static fromChainId(chainId, rpcUrl) {
554
+ const chainConfig = getChainConfig(chainId);
555
+ if (!chainConfig) {
556
+ throw new Error(`Unsupported chain ID: ${chainId}`);
557
+ }
558
+ return new _ManiaSDK({
559
+ factoryAddress: chainConfig.factoryAddress,
560
+ rpcUrl,
561
+ chainId
562
+ });
563
+ }
564
+ /**
565
+ * Connect a wallet for signing transactions
566
+ *
567
+ * @param privateKey - Private key (with or without 0x prefix)
568
+ * @param chain - Viem chain object
569
+ */
570
+ connectWallet(privateKey, chain) {
571
+ const account = privateKeyToAccount(
572
+ privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`
573
+ );
574
+ this.walletClient = createWalletClient({
575
+ account,
576
+ chain,
577
+ transport: http()
578
+ });
579
+ }
580
+ /**
581
+ * Connect an existing wallet client
582
+ *
583
+ * @param walletClient - Viem wallet client
584
+ */
585
+ setWalletClient(walletClient) {
586
+ this.walletClient = walletClient;
587
+ }
588
+ /**
589
+ * Set a custom public client
590
+ *
591
+ * @param publicClient - Viem public client
592
+ */
593
+ setPublicClient(publicClient) {
594
+ this.publicClient = publicClient;
595
+ }
596
+ /**
597
+ * Get the connected wallet address
598
+ */
599
+ getWalletAddress() {
600
+ return this.walletClient?.account?.address;
601
+ }
602
+ // ========== READ METHODS ==========
603
+ /**
604
+ * Get global protocol configuration
605
+ */
606
+ async getGlobalState() {
607
+ const result = await this.publicClient.readContract({
608
+ address: this.factoryAddress,
609
+ abi: MANIA_FACTORY_ABI,
610
+ functionName: "global"
611
+ });
612
+ const [
613
+ initialized,
614
+ authority,
615
+ feeRecipient,
616
+ initialVirtualTokenReserves,
617
+ initialVirtualEthReserves,
618
+ initialRealTokenReserves,
619
+ tokenTotalSupply,
620
+ feeBasisPoints,
621
+ withdrawAuthority,
622
+ enableMigrate,
623
+ poolMigrationFee
624
+ ] = result;
625
+ return {
626
+ initialized,
627
+ authority,
628
+ feeRecipient,
629
+ initialVirtualTokenReserves,
630
+ initialVirtualEthReserves,
631
+ initialRealTokenReserves,
632
+ tokenTotalSupply,
633
+ feeBasisPoints,
634
+ withdrawAuthority,
635
+ enableMigrate,
636
+ poolMigrationFee
637
+ };
638
+ }
639
+ /**
640
+ * Get bonding curve state for a token
641
+ *
642
+ * @param token - Token address
643
+ */
644
+ async getBondingCurve(token) {
645
+ const result = await this.publicClient.readContract({
646
+ address: this.factoryAddress,
647
+ abi: MANIA_FACTORY_ABI,
648
+ functionName: "getBondingCurve",
649
+ args: [token]
650
+ });
651
+ const curve = result;
652
+ return {
653
+ virtualTokenReserves: curve.virtualTokenReserves,
654
+ virtualEthReserves: curve.virtualEthReserves,
655
+ realTokenReserves: curve.realTokenReserves,
656
+ realEthReserves: curve.realEthReserves,
657
+ tokenTotalSupply: curve.tokenTotalSupply,
658
+ complete: curve.complete,
659
+ trackVolume: curve.trackVolume
660
+ };
661
+ }
662
+ /**
663
+ * Get a BondingCurve instance for calculations
664
+ *
665
+ * @param token - Token address
666
+ */
667
+ async getBondingCurveInstance(token) {
668
+ const [curveState, globalState] = await Promise.all([
669
+ this.getBondingCurve(token),
670
+ this.getGlobalState()
671
+ ]);
672
+ return new BondingCurve(curveState, globalState.feeBasisPoints);
673
+ }
674
+ /**
675
+ * Get comprehensive token information
676
+ *
677
+ * @param token - Token address
678
+ */
679
+ async getTokenInfo(token) {
680
+ const curve = await this.getBondingCurveInstance(token);
681
+ const state = curve.getState();
682
+ return {
683
+ address: token,
684
+ bondingCurve: state,
685
+ currentPrice: curve.getCurrentPrice(),
686
+ marketCapEth: curve.getMarketCapEth(),
687
+ migrationProgress: curve.getMigrationProgress()
688
+ };
689
+ }
690
+ /**
691
+ * Get buy quote from contract
692
+ *
693
+ * @param token - Token address
694
+ * @param ethAmount - ETH amount in wei
695
+ */
696
+ async getBuyQuote(token, ethAmount) {
697
+ const result = await this.publicClient.readContract({
698
+ address: this.factoryAddress,
699
+ abi: MANIA_FACTORY_ABI,
700
+ functionName: "getBuyQuote",
701
+ args: [token, ethAmount]
702
+ });
703
+ return result;
704
+ }
705
+ /**
706
+ * Get sell quote from contract
707
+ *
708
+ * @param token - Token address
709
+ * @param tokenAmount - Token amount
710
+ */
711
+ async getSellQuote(token, tokenAmount) {
712
+ const result = await this.publicClient.readContract({
713
+ address: this.factoryAddress,
714
+ abi: MANIA_FACTORY_ABI,
715
+ functionName: "getSellQuote",
716
+ args: [token, tokenAmount]
717
+ });
718
+ return result;
719
+ }
720
+ /**
721
+ * Get fee for a given amount
722
+ *
723
+ * @param amount - Amount in wei
724
+ */
725
+ async getFee(amount) {
726
+ const result = await this.publicClient.readContract({
727
+ address: this.factoryAddress,
728
+ abi: MANIA_FACTORY_ABI,
729
+ functionName: "getFee",
730
+ args: [amount]
731
+ });
732
+ return result;
733
+ }
734
+ /**
735
+ * Get user volume
736
+ *
737
+ * @param user - User address
738
+ */
739
+ async getUserVolume(user) {
740
+ const result = await this.publicClient.readContract({
741
+ address: this.factoryAddress,
742
+ abi: MANIA_FACTORY_ABI,
743
+ functionName: "userVolume",
744
+ args: [user]
745
+ });
746
+ return result;
747
+ }
748
+ // ========== WRITE METHODS ==========
749
+ getConnectedWallet() {
750
+ if (!this.walletClient?.account) {
751
+ throw new Error("Wallet not connected. Call connectWallet() first.");
752
+ }
753
+ return this.walletClient;
754
+ }
755
+ /**
756
+ * Create a new token with bonding curve
757
+ *
758
+ * @param params - Token creation parameters
759
+ * @returns Transaction result with token address
760
+ */
761
+ async create(params) {
762
+ const wallet = this.getConnectedWallet();
763
+ const hash = await wallet.writeContract({
764
+ chain: null,
765
+ account: null,
766
+ address: this.factoryAddress,
767
+ abi: MANIA_FACTORY_ABI,
768
+ functionName: "create",
769
+ args: [params.name, params.symbol, params.uri, params.creator]
770
+ });
771
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
772
+ const logs = parseEventLogs({
773
+ abi: MANIA_FACTORY_ABI,
774
+ logs: receipt.logs,
775
+ eventName: "CreateEvent"
776
+ });
777
+ const tokenAddress = logs[0]?.args?.mint;
778
+ return {
779
+ hash,
780
+ success: receipt.status === "success",
781
+ tokenAddress
782
+ };
783
+ }
784
+ /**
785
+ * Create a new token and buy in a single transaction
786
+ *
787
+ * @param params - Creation and buy parameters
788
+ * @returns Transaction result with token address
789
+ */
790
+ async createAndBuy(params) {
791
+ const wallet = this.getConnectedWallet();
792
+ const hash = await wallet.writeContract({
793
+ chain: null,
794
+ account: null,
795
+ address: this.factoryAddress,
796
+ abi: MANIA_FACTORY_ABI,
797
+ functionName: "createAndBuy",
798
+ args: [params.name, params.symbol, params.uri, params.creator, params.minTokensOut],
799
+ value: params.buyAmountEth
800
+ });
801
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
802
+ const logs = parseEventLogs({
803
+ abi: MANIA_FACTORY_ABI,
804
+ logs: receipt.logs,
805
+ eventName: "CreateEvent"
806
+ });
807
+ const tokenAddress = logs[0]?.args?.mint;
808
+ return {
809
+ hash,
810
+ success: receipt.status === "success",
811
+ tokenAddress
812
+ };
813
+ }
814
+ /**
815
+ * Buy tokens from bonding curve
816
+ *
817
+ * @param params - Buy parameters
818
+ * @returns Transaction result
819
+ */
820
+ async buy(params) {
821
+ const wallet = this.getConnectedWallet();
822
+ const recipient = params.recipient ?? wallet.account.address;
823
+ const hash = await wallet.writeContract({
824
+ chain: null,
825
+ account: null,
826
+ address: this.factoryAddress,
827
+ abi: MANIA_FACTORY_ABI,
828
+ functionName: "buy",
829
+ args: [params.token, params.minTokensOut, recipient],
830
+ value: params.amountEth
831
+ });
832
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
833
+ return {
834
+ hash,
835
+ success: receipt.status === "success"
836
+ };
837
+ }
838
+ /**
839
+ * Buy tokens with automatic slippage calculation
840
+ *
841
+ * @param token - Token address
842
+ * @param ethAmount - ETH amount to spend
843
+ * @param slippageBps - Slippage tolerance in basis points (default: 100 = 1%)
844
+ */
845
+ async buyWithSlippage(token, ethAmount, slippageBps = DEFAULT_SLIPPAGE_BPS) {
846
+ const curve = await this.getBondingCurveInstance(token);
847
+ const minTokensOut = curve.calculateMinTokensOut(ethAmount, slippageBps);
848
+ return this.buy({
849
+ token,
850
+ amountEth: ethAmount,
851
+ minTokensOut
852
+ });
853
+ }
854
+ /**
855
+ * Sell tokens to bonding curve
856
+ *
857
+ * @param params - Sell parameters
858
+ * @returns Transaction result
859
+ */
860
+ async sell(params) {
861
+ const wallet = this.getConnectedWallet();
862
+ const hash = await wallet.writeContract({
863
+ chain: null,
864
+ account: null,
865
+ address: this.factoryAddress,
866
+ abi: MANIA_FACTORY_ABI,
867
+ functionName: "sell",
868
+ args: [params.token, params.amountTokens, params.minEthOut]
869
+ });
870
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
871
+ return {
872
+ hash,
873
+ success: receipt.status === "success"
874
+ };
875
+ }
876
+ /**
877
+ * Sell tokens with automatic slippage calculation
878
+ *
879
+ * @param token - Token address
880
+ * @param tokenAmount - Token amount to sell
881
+ * @param slippageBps - Slippage tolerance in basis points (default: 100 = 1%)
882
+ */
883
+ async sellWithSlippage(token, tokenAmount, slippageBps = DEFAULT_SLIPPAGE_BPS) {
884
+ const curve = await this.getBondingCurveInstance(token);
885
+ const minEthOut = curve.calculateMinEthOut(tokenAmount, slippageBps);
886
+ return this.sell({
887
+ token,
888
+ amountTokens: tokenAmount,
889
+ minEthOut
890
+ });
891
+ }
892
+ /**
893
+ * Migrate liquidity to Uniswap V3
894
+ *
895
+ * @param params - Migration parameters
896
+ * @returns Transaction result with pool address
897
+ */
898
+ async migrate(params) {
899
+ const wallet = this.getConnectedWallet();
900
+ const globalState = await this.getGlobalState();
901
+ const hash = await wallet.writeContract({
902
+ chain: null,
903
+ account: null,
904
+ address: this.factoryAddress,
905
+ abi: MANIA_FACTORY_ABI,
906
+ functionName: "migrate",
907
+ args: [params.token],
908
+ value: globalState.poolMigrationFee
909
+ });
910
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
911
+ const logs = parseEventLogs({
912
+ abi: MANIA_FACTORY_ABI,
913
+ logs: receipt.logs,
914
+ eventName: "CompleteManiaAmmMigrationEvent"
915
+ });
916
+ const poolAddress = logs[0]?.args?.pool;
917
+ return {
918
+ hash,
919
+ success: receipt.status === "success",
920
+ poolAddress
921
+ };
922
+ }
923
+ // ========== EVENT WATCHING ==========
924
+ /**
925
+ * Watch for new token creation events
926
+ *
927
+ * @param callback - Callback function for each event
928
+ * @returns Unwatch function
929
+ */
930
+ watchCreateEvents(callback) {
931
+ return this.publicClient.watchContractEvent({
932
+ address: this.factoryAddress,
933
+ abi: MANIA_FACTORY_ABI,
934
+ eventName: "CreateEvent",
935
+ onLogs: (logs) => {
936
+ for (const log of logs) {
937
+ const args = log.args;
938
+ callback(args);
939
+ }
940
+ }
941
+ });
942
+ }
943
+ /**
944
+ * Watch for trade events on a specific token
945
+ *
946
+ * @param token - Token address (optional, watches all if not provided)
947
+ * @param callback - Callback function for each event
948
+ * @returns Unwatch function
949
+ */
950
+ watchTradeEvents(token, callback) {
951
+ return this.publicClient.watchContractEvent({
952
+ address: this.factoryAddress,
953
+ abi: MANIA_FACTORY_ABI,
954
+ eventName: "TradeEvent",
955
+ args: token ? { mint: token } : void 0,
956
+ onLogs: (logs) => {
957
+ for (const log of logs) {
958
+ const args = log.args;
959
+ callback(args);
960
+ }
961
+ }
962
+ });
963
+ }
964
+ /**
965
+ * Watch for bonding curve completion events
966
+ *
967
+ * @param callback - Callback function for each event
968
+ * @returns Unwatch function
969
+ */
970
+ watchCompleteEvents(callback) {
971
+ return this.publicClient.watchContractEvent({
972
+ address: this.factoryAddress,
973
+ abi: MANIA_FACTORY_ABI,
974
+ eventName: "CompleteEvent",
975
+ onLogs: (logs) => {
976
+ for (const log of logs) {
977
+ const args = log.args;
978
+ callback(args);
979
+ }
980
+ }
981
+ });
982
+ }
983
+ /**
984
+ * Watch for migration events
985
+ *
986
+ * @param callback - Callback function for each event
987
+ * @returns Unwatch function
988
+ */
989
+ watchMigrationEvents(callback) {
990
+ return this.publicClient.watchContractEvent({
991
+ address: this.factoryAddress,
992
+ abi: MANIA_FACTORY_ABI,
993
+ eventName: "CompleteManiaAmmMigrationEvent",
994
+ onLogs: (logs) => {
995
+ for (const log of logs) {
996
+ const args = log.args;
997
+ callback(args);
998
+ }
999
+ }
1000
+ });
1001
+ }
1002
+ // ========== UTILITY METHODS ==========
1003
+ /**
1004
+ * Check if a token's bonding curve is complete
1005
+ *
1006
+ * @param token - Token address
1007
+ */
1008
+ async isComplete(token) {
1009
+ const curve = await this.getBondingCurve(token);
1010
+ return curve.complete;
1011
+ }
1012
+ /**
1013
+ * Check if a token has been migrated
1014
+ *
1015
+ * @param token - Token address
1016
+ */
1017
+ async isMigrated(token) {
1018
+ const curve = await this.getBondingCurve(token);
1019
+ return curve.realEthReserves === 0n && curve.virtualEthReserves === 0n && curve.realTokenReserves === 0n && curve.virtualTokenReserves === 0n;
1020
+ }
1021
+ /**
1022
+ * Get the factory contract address
1023
+ */
1024
+ getFactoryAddress() {
1025
+ return this.factoryAddress;
1026
+ }
1027
+ /**
1028
+ * Get the public client
1029
+ */
1030
+ getPublicClient() {
1031
+ return this.publicClient;
1032
+ }
1033
+ /**
1034
+ * Get the wallet client
1035
+ */
1036
+ getWalletClient() {
1037
+ return this.walletClient;
1038
+ }
1039
+ };
1040
+
1041
+ // src/utils.ts
1042
+ import { formatEther, parseEther } from "viem";
1043
+ function formatEthValue(wei, decimals = 4) {
1044
+ const ethValue = formatEther(wei);
1045
+ const num = parseFloat(ethValue);
1046
+ return num.toFixed(decimals);
1047
+ }
1048
+ function formatTokenAmount(amount, decimals = 2) {
1049
+ const ethValue = formatEther(amount);
1050
+ const num = parseFloat(ethValue);
1051
+ if (num >= 1e9) {
1052
+ return (num / 1e9).toFixed(decimals) + "B";
1053
+ }
1054
+ if (num >= 1e6) {
1055
+ return (num / 1e6).toFixed(decimals) + "M";
1056
+ }
1057
+ if (num >= 1e3) {
1058
+ return (num / 1e3).toFixed(decimals) + "K";
1059
+ }
1060
+ return num.toFixed(decimals);
1061
+ }
1062
+ function parseEthValue(eth) {
1063
+ return parseEther(eth);
1064
+ }
1065
+ function calculateWithSlippage(amount, slippageBps) {
1066
+ const slippageMultiplier = BPS_DENOMINATOR - BigInt(slippageBps);
1067
+ return amount * slippageMultiplier / BPS_DENOMINATOR;
1068
+ }
1069
+ function calculateMigrationProgress(realEthReserves) {
1070
+ if (realEthReserves >= MIGRATION_THRESHOLD) {
1071
+ return 100;
1072
+ }
1073
+ return Number(realEthReserves * 10000n / MIGRATION_THRESHOLD) / 100;
1074
+ }
1075
+ function formatPrice(priceWei) {
1076
+ const price = Number(priceWei) / 1e18;
1077
+ if (price < 1e-6) {
1078
+ return price.toExponential(4);
1079
+ }
1080
+ if (price < 1e-4) {
1081
+ return price.toFixed(8);
1082
+ }
1083
+ if (price < 0.01) {
1084
+ return price.toFixed(6);
1085
+ }
1086
+ return price.toFixed(4);
1087
+ }
1088
+ function formatMarketCap(marketCapWei) {
1089
+ const ethValue = Number(formatEther(marketCapWei));
1090
+ if (ethValue >= 1e3) {
1091
+ return (ethValue / 1e3).toFixed(2) + "K ETH";
1092
+ }
1093
+ return ethValue.toFixed(2) + " ETH";
1094
+ }
1095
+ function isValidAddress(address) {
1096
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
1097
+ }
1098
+ function truncateAddress(address, chars = 4) {
1099
+ const start = address.slice(0, chars + 2);
1100
+ const end = address.slice(-chars);
1101
+ return start + "..." + end;
1102
+ }
1103
+ function bpsToPercent(bps) {
1104
+ return Number(bps) / 100;
1105
+ }
1106
+ function percentToBps(percent) {
1107
+ return Math.round(percent * 100);
1108
+ }
1109
+ function calculatePriceImpact(currentPrice, newPrice) {
1110
+ if (currentPrice === 0n) return 0;
1111
+ return Number((newPrice - currentPrice) * 10000n / currentPrice) / 100;
1112
+ }
1113
+ function sleep(ms) {
1114
+ return new Promise((resolve) => setTimeout(resolve, ms));
1115
+ }
1116
+ async function withRetry(fn, maxRetries = 3, baseDelayMs = 1e3) {
1117
+ let lastError;
1118
+ for (let i = 0; i < maxRetries; i++) {
1119
+ try {
1120
+ return await fn();
1121
+ } catch (error) {
1122
+ lastError = error instanceof Error ? error : new Error(String(error));
1123
+ if (i < maxRetries - 1) {
1124
+ await sleep(baseDelayMs * Math.pow(2, i));
1125
+ }
1126
+ }
1127
+ }
1128
+ throw lastError;
1129
+ }
1130
+ export {
1131
+ BPS_DENOMINATOR,
1132
+ BondingCurve,
1133
+ CHAIN_CONFIGS,
1134
+ CREATOR_FEE_BASIS_POINTS,
1135
+ DEFAULT_SLIPPAGE_BPS,
1136
+ ERC20_ABI,
1137
+ MANIA_FACTORY_ABI,
1138
+ MAX_FEE_BASIS_POINTS,
1139
+ MAX_MIGRATE_FEES,
1140
+ MIGRATION_THRESHOLD,
1141
+ ManiaSDK,
1142
+ PROTOCOL_FEE_BASIS_POINTS,
1143
+ TICK_LOWER,
1144
+ TICK_UPPER,
1145
+ TOKENS_FOR_LP,
1146
+ TOTAL_FEE_BASIS_POINTS,
1147
+ UNISWAP_FEE_TIER,
1148
+ bpsToPercent,
1149
+ calculateBuyAmount,
1150
+ calculateMigrationProgress,
1151
+ calculatePriceImpact,
1152
+ calculateSellAmount,
1153
+ calculateWithSlippage,
1154
+ formatEthValue,
1155
+ formatMarketCap,
1156
+ formatPrice,
1157
+ formatTokenAmount,
1158
+ getChainConfig,
1159
+ isValidAddress,
1160
+ parseEthValue,
1161
+ percentToBps,
1162
+ sleep,
1163
+ truncateAddress,
1164
+ withRetry
1165
+ };