@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/src/index.ts ADDED
@@ -0,0 +1,71 @@
1
+ // Main SDK class
2
+ export { ManiaSDK } from "./mania.js";
3
+
4
+ // Bonding curve utilities
5
+ export {
6
+ BondingCurve,
7
+ calculateBuyAmount,
8
+ calculateSellAmount,
9
+ } from "./bondingCurve.js";
10
+
11
+ // Types
12
+ export type {
13
+ BondingCurveState,
14
+ GlobalState,
15
+ SetParamsInput,
16
+ CreateTokenParams,
17
+ CreateAndBuyParams,
18
+ BuyParams,
19
+ SellParams,
20
+ MigrateParams,
21
+ BuyQuote,
22
+ SellQuote,
23
+ TokenInfo,
24
+ TransactionResult,
25
+ CreateEvent,
26
+ TradeEvent,
27
+ CompleteEvent,
28
+ MigrationEvent,
29
+ ManiaSDKConfig,
30
+ SlippageConfig,
31
+ } from "./types.js";
32
+
33
+ // Constants
34
+ export {
35
+ PROTOCOL_FEE_BASIS_POINTS,
36
+ CREATOR_FEE_BASIS_POINTS,
37
+ TOTAL_FEE_BASIS_POINTS,
38
+ MAX_FEE_BASIS_POINTS,
39
+ MIGRATION_THRESHOLD,
40
+ TOKENS_FOR_LP,
41
+ MAX_MIGRATE_FEES,
42
+ UNISWAP_FEE_TIER,
43
+ TICK_LOWER,
44
+ TICK_UPPER,
45
+ DEFAULT_SLIPPAGE_BPS,
46
+ BPS_DENOMINATOR,
47
+ CHAIN_CONFIGS,
48
+ getChainConfig,
49
+ type ChainConfig,
50
+ } from "./constants.js";
51
+
52
+ // Utility functions
53
+ export {
54
+ formatEthValue,
55
+ formatTokenAmount,
56
+ parseEthValue,
57
+ calculateWithSlippage,
58
+ calculateMigrationProgress,
59
+ formatPrice,
60
+ formatMarketCap,
61
+ isValidAddress,
62
+ truncateAddress,
63
+ bpsToPercent,
64
+ percentToBps,
65
+ calculatePriceImpact,
66
+ sleep,
67
+ withRetry,
68
+ } from "./utils.js";
69
+
70
+ // ABIs
71
+ export { MANIA_FACTORY_ABI, ERC20_ABI } from "./abi.js";
package/src/mania.ts ADDED
@@ -0,0 +1,652 @@
1
+ import {
2
+ createPublicClient,
3
+ createWalletClient,
4
+ http,
5
+ type Address,
6
+ type PublicClient,
7
+ type WalletClient,
8
+ type Chain,
9
+ parseEventLogs,
10
+ } from "viem";
11
+ import { privateKeyToAccount } from "viem/accounts";
12
+ import type {
13
+ BondingCurveState,
14
+ GlobalState,
15
+ CreateTokenParams,
16
+ CreateAndBuyParams,
17
+ BuyParams,
18
+ SellParams,
19
+ MigrateParams,
20
+ TokenInfo,
21
+ TransactionResult,
22
+ CreateEvent,
23
+ TradeEvent,
24
+ CompleteEvent,
25
+ MigrationEvent,
26
+ ManiaSDKConfig,
27
+ } from "./types.js";
28
+ import { BondingCurve } from "./bondingCurve.js";
29
+ import {
30
+ DEFAULT_SLIPPAGE_BPS,
31
+ getChainConfig,
32
+ } from "./constants.js";
33
+ import { MANIA_FACTORY_ABI } from "./abi.js";
34
+
35
+ /**
36
+ * ManiaSDK - Official SDK for interacting with the Mania Protocol
37
+ *
38
+ * Mania is an EVM-based token launchpad using bonding curves. Tokens are created
39
+ * with an initial bonding curve that allows trading until a migration threshold
40
+ * (4 ETH) is reached. Once complete, liquidity is migrated to Uniswap V3.
41
+ */
42
+ export class ManiaSDK {
43
+ public publicClient: PublicClient;
44
+ public walletClient: WalletClient | null = null;
45
+ public readonly factoryAddress: Address;
46
+ public readonly chainId: number;
47
+
48
+ /**
49
+ * Create a new ManiaSDK instance
50
+ *
51
+ * @param config - SDK configuration
52
+ * @param config.factoryAddress - ManiaFactory contract address
53
+ * @param config.rpcUrl - RPC URL for the chain
54
+ * @param config.chainId - Chain ID (optional, will be fetched if not provided)
55
+ */
56
+ constructor(config: ManiaSDKConfig) {
57
+ this.factoryAddress = config.factoryAddress;
58
+ this.chainId = config.chainId ?? 1;
59
+
60
+ this.publicClient = createPublicClient({
61
+ transport: http(config.rpcUrl),
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Create SDK instance from chain ID using default configuration
67
+ *
68
+ * @param chainId - Chain ID
69
+ * @param rpcUrl - RPC URL (optional, uses public RPC if not provided)
70
+ * @returns ManiaSDK instance
71
+ */
72
+ static fromChainId(chainId: number, rpcUrl?: string): ManiaSDK {
73
+ const chainConfig = getChainConfig(chainId);
74
+ if (!chainConfig) {
75
+ throw new Error(`Unsupported chain ID: ${chainId}`);
76
+ }
77
+
78
+ return new ManiaSDK({
79
+ factoryAddress: chainConfig.factoryAddress,
80
+ rpcUrl,
81
+ chainId,
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Connect a wallet for signing transactions
87
+ *
88
+ * @param privateKey - Private key (with or without 0x prefix)
89
+ * @param chain - Viem chain object
90
+ */
91
+ connectWallet(privateKey: string, chain: Chain): void {
92
+ const account = privateKeyToAccount(
93
+ privateKey.startsWith("0x") ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`)
94
+ );
95
+
96
+ this.walletClient = createWalletClient({
97
+ account,
98
+ chain,
99
+ transport: http(),
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Connect an existing wallet client
105
+ *
106
+ * @param walletClient - Viem wallet client
107
+ */
108
+ setWalletClient(walletClient: WalletClient): void {
109
+ this.walletClient = walletClient;
110
+ }
111
+
112
+ /**
113
+ * Set a custom public client
114
+ *
115
+ * @param publicClient - Viem public client
116
+ */
117
+ setPublicClient(publicClient: PublicClient): void {
118
+ this.publicClient = publicClient;
119
+ }
120
+
121
+ /**
122
+ * Get the connected wallet address
123
+ */
124
+ getWalletAddress(): Address | undefined {
125
+ return this.walletClient?.account?.address;
126
+ }
127
+
128
+ // ========== READ METHODS ==========
129
+
130
+ /**
131
+ * Get global protocol configuration
132
+ */
133
+ async getGlobalState(): Promise<GlobalState> {
134
+ const result = await this.publicClient.readContract({
135
+ address: this.factoryAddress,
136
+ abi: MANIA_FACTORY_ABI,
137
+ functionName: "global",
138
+ });
139
+
140
+ const [
141
+ initialized,
142
+ authority,
143
+ feeRecipient,
144
+ initialVirtualTokenReserves,
145
+ initialVirtualEthReserves,
146
+ initialRealTokenReserves,
147
+ tokenTotalSupply,
148
+ feeBasisPoints,
149
+ withdrawAuthority,
150
+ enableMigrate,
151
+ poolMigrationFee,
152
+ ] = result as [boolean, Address, Address, bigint, bigint, bigint, bigint, bigint, Address, boolean, bigint];
153
+
154
+ return {
155
+ initialized,
156
+ authority,
157
+ feeRecipient,
158
+ initialVirtualTokenReserves,
159
+ initialVirtualEthReserves,
160
+ initialRealTokenReserves,
161
+ tokenTotalSupply,
162
+ feeBasisPoints,
163
+ withdrawAuthority,
164
+ enableMigrate,
165
+ poolMigrationFee,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Get bonding curve state for a token
171
+ *
172
+ * @param token - Token address
173
+ */
174
+ async getBondingCurve(token: Address): Promise<BondingCurveState> {
175
+ const result = await this.publicClient.readContract({
176
+ address: this.factoryAddress,
177
+ abi: MANIA_FACTORY_ABI,
178
+ functionName: "getBondingCurve",
179
+ args: [token],
180
+ });
181
+
182
+ const curve = result as {
183
+ virtualTokenReserves: bigint;
184
+ virtualEthReserves: bigint;
185
+ realTokenReserves: bigint;
186
+ realEthReserves: bigint;
187
+ tokenTotalSupply: bigint;
188
+ complete: boolean;
189
+ trackVolume: boolean;
190
+ };
191
+
192
+ return {
193
+ virtualTokenReserves: curve.virtualTokenReserves,
194
+ virtualEthReserves: curve.virtualEthReserves,
195
+ realTokenReserves: curve.realTokenReserves,
196
+ realEthReserves: curve.realEthReserves,
197
+ tokenTotalSupply: curve.tokenTotalSupply,
198
+ complete: curve.complete,
199
+ trackVolume: curve.trackVolume,
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Get a BondingCurve instance for calculations
205
+ *
206
+ * @param token - Token address
207
+ */
208
+ async getBondingCurveInstance(token: Address): Promise<BondingCurve> {
209
+ const [curveState, globalState] = await Promise.all([
210
+ this.getBondingCurve(token),
211
+ this.getGlobalState(),
212
+ ]);
213
+
214
+ return new BondingCurve(curveState, globalState.feeBasisPoints);
215
+ }
216
+
217
+ /**
218
+ * Get comprehensive token information
219
+ *
220
+ * @param token - Token address
221
+ */
222
+ async getTokenInfo(token: Address): Promise<TokenInfo> {
223
+ const curve = await this.getBondingCurveInstance(token);
224
+ const state = curve.getState();
225
+
226
+ return {
227
+ address: token,
228
+ bondingCurve: state,
229
+ currentPrice: curve.getCurrentPrice(),
230
+ marketCapEth: curve.getMarketCapEth(),
231
+ migrationProgress: curve.getMigrationProgress(),
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Get buy quote from contract
237
+ *
238
+ * @param token - Token address
239
+ * @param ethAmount - ETH amount in wei
240
+ */
241
+ async getBuyQuote(token: Address, ethAmount: bigint): Promise<bigint> {
242
+ const result = await this.publicClient.readContract({
243
+ address: this.factoryAddress,
244
+ abi: MANIA_FACTORY_ABI,
245
+ functionName: "getBuyQuote",
246
+ args: [token, ethAmount],
247
+ });
248
+
249
+ return result as bigint;
250
+ }
251
+
252
+ /**
253
+ * Get sell quote from contract
254
+ *
255
+ * @param token - Token address
256
+ * @param tokenAmount - Token amount
257
+ */
258
+ async getSellQuote(token: Address, tokenAmount: bigint): Promise<bigint> {
259
+ const result = await this.publicClient.readContract({
260
+ address: this.factoryAddress,
261
+ abi: MANIA_FACTORY_ABI,
262
+ functionName: "getSellQuote",
263
+ args: [token, tokenAmount],
264
+ });
265
+
266
+ return result as bigint;
267
+ }
268
+
269
+ /**
270
+ * Get fee for a given amount
271
+ *
272
+ * @param amount - Amount in wei
273
+ */
274
+ async getFee(amount: bigint): Promise<bigint> {
275
+ const result = await this.publicClient.readContract({
276
+ address: this.factoryAddress,
277
+ abi: MANIA_FACTORY_ABI,
278
+ functionName: "getFee",
279
+ args: [amount],
280
+ });
281
+
282
+ return result as bigint;
283
+ }
284
+
285
+ /**
286
+ * Get user volume
287
+ *
288
+ * @param user - User address
289
+ */
290
+ async getUserVolume(user: Address): Promise<bigint> {
291
+ const result = await this.publicClient.readContract({
292
+ address: this.factoryAddress,
293
+ abi: MANIA_FACTORY_ABI,
294
+ functionName: "userVolume",
295
+ args: [user],
296
+ });
297
+
298
+ return result as bigint;
299
+ }
300
+
301
+ // ========== WRITE METHODS ==========
302
+
303
+ private getConnectedWallet(): WalletClient {
304
+ if (!this.walletClient?.account) {
305
+ throw new Error("Wallet not connected. Call connectWallet() first.");
306
+ }
307
+ return this.walletClient;
308
+ }
309
+
310
+ /**
311
+ * Create a new token with bonding curve
312
+ *
313
+ * @param params - Token creation parameters
314
+ * @returns Transaction result with token address
315
+ */
316
+ async create(params: CreateTokenParams): Promise<TransactionResult & { tokenAddress?: Address }> {
317
+ const wallet = this.getConnectedWallet();
318
+
319
+ const hash = await wallet.writeContract({
320
+ chain: null,
321
+ account: null,
322
+ address: this.factoryAddress,
323
+ abi: MANIA_FACTORY_ABI,
324
+ functionName: "create",
325
+ args: [params.name, params.symbol, params.uri, params.creator],
326
+ });
327
+
328
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
329
+
330
+ // Parse CreateEvent to get token address
331
+ const logs = parseEventLogs({
332
+ abi: MANIA_FACTORY_ABI,
333
+ logs: receipt.logs,
334
+ eventName: "CreateEvent",
335
+ });
336
+
337
+ const tokenAddress = logs[0]?.args?.mint as Address | undefined;
338
+
339
+ return {
340
+ hash,
341
+ success: receipt.status === "success",
342
+ tokenAddress,
343
+ };
344
+ }
345
+
346
+ /**
347
+ * Create a new token and buy in a single transaction
348
+ *
349
+ * @param params - Creation and buy parameters
350
+ * @returns Transaction result with token address
351
+ */
352
+ async createAndBuy(params: CreateAndBuyParams): Promise<TransactionResult & { tokenAddress?: Address }> {
353
+ const wallet = this.getConnectedWallet();
354
+
355
+ const hash = await wallet.writeContract({
356
+ chain: null,
357
+ account: null,
358
+ address: this.factoryAddress,
359
+ abi: MANIA_FACTORY_ABI,
360
+ functionName: "createAndBuy",
361
+ args: [params.name, params.symbol, params.uri, params.creator, params.minTokensOut],
362
+ value: params.buyAmountEth,
363
+ });
364
+
365
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
366
+
367
+ // Parse CreateEvent to get token address
368
+ const logs = parseEventLogs({
369
+ abi: MANIA_FACTORY_ABI,
370
+ logs: receipt.logs,
371
+ eventName: "CreateEvent",
372
+ });
373
+
374
+ const tokenAddress = logs[0]?.args?.mint as Address | undefined;
375
+
376
+ return {
377
+ hash,
378
+ success: receipt.status === "success",
379
+ tokenAddress,
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Buy tokens from bonding curve
385
+ *
386
+ * @param params - Buy parameters
387
+ * @returns Transaction result
388
+ */
389
+ async buy(params: BuyParams): Promise<TransactionResult> {
390
+ const wallet = this.getConnectedWallet();
391
+
392
+ const recipient = params.recipient ?? wallet.account!.address;
393
+
394
+ const hash = await wallet.writeContract({
395
+ chain: null,
396
+ account: null,
397
+ address: this.factoryAddress,
398
+ abi: MANIA_FACTORY_ABI,
399
+ functionName: "buy",
400
+ args: [params.token, params.minTokensOut, recipient],
401
+ value: params.amountEth,
402
+ });
403
+
404
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
405
+
406
+ return {
407
+ hash,
408
+ success: receipt.status === "success",
409
+ };
410
+ }
411
+
412
+ /**
413
+ * Buy tokens with automatic slippage calculation
414
+ *
415
+ * @param token - Token address
416
+ * @param ethAmount - ETH amount to spend
417
+ * @param slippageBps - Slippage tolerance in basis points (default: 100 = 1%)
418
+ */
419
+ async buyWithSlippage(
420
+ token: Address,
421
+ ethAmount: bigint,
422
+ slippageBps: number = DEFAULT_SLIPPAGE_BPS
423
+ ): Promise<TransactionResult> {
424
+ const curve = await this.getBondingCurveInstance(token);
425
+ const minTokensOut = curve.calculateMinTokensOut(ethAmount, slippageBps);
426
+
427
+ return this.buy({
428
+ token,
429
+ amountEth: ethAmount,
430
+ minTokensOut,
431
+ });
432
+ }
433
+
434
+ /**
435
+ * Sell tokens to bonding curve
436
+ *
437
+ * @param params - Sell parameters
438
+ * @returns Transaction result
439
+ */
440
+ async sell(params: SellParams): Promise<TransactionResult> {
441
+ const wallet = this.getConnectedWallet();
442
+
443
+ const hash = await wallet.writeContract({
444
+ chain: null,
445
+ account: null,
446
+ address: this.factoryAddress,
447
+ abi: MANIA_FACTORY_ABI,
448
+ functionName: "sell",
449
+ args: [params.token, params.amountTokens, params.minEthOut],
450
+ });
451
+
452
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
453
+
454
+ return {
455
+ hash,
456
+ success: receipt.status === "success",
457
+ };
458
+ }
459
+
460
+ /**
461
+ * Sell tokens with automatic slippage calculation
462
+ *
463
+ * @param token - Token address
464
+ * @param tokenAmount - Token amount to sell
465
+ * @param slippageBps - Slippage tolerance in basis points (default: 100 = 1%)
466
+ */
467
+ async sellWithSlippage(
468
+ token: Address,
469
+ tokenAmount: bigint,
470
+ slippageBps: number = DEFAULT_SLIPPAGE_BPS
471
+ ): Promise<TransactionResult> {
472
+ const curve = await this.getBondingCurveInstance(token);
473
+ const minEthOut = curve.calculateMinEthOut(tokenAmount, slippageBps);
474
+
475
+ return this.sell({
476
+ token,
477
+ amountTokens: tokenAmount,
478
+ minEthOut,
479
+ });
480
+ }
481
+
482
+ /**
483
+ * Migrate liquidity to Uniswap V3
484
+ *
485
+ * @param params - Migration parameters
486
+ * @returns Transaction result with pool address
487
+ */
488
+ async migrate(params: MigrateParams): Promise<TransactionResult & { poolAddress?: Address }> {
489
+ const wallet = this.getConnectedWallet();
490
+
491
+ const globalState = await this.getGlobalState();
492
+
493
+ const hash = await wallet.writeContract({
494
+ chain: null,
495
+ account: null,
496
+ address: this.factoryAddress,
497
+ abi: MANIA_FACTORY_ABI,
498
+ functionName: "migrate",
499
+ args: [params.token],
500
+ value: globalState.poolMigrationFee,
501
+ });
502
+
503
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
504
+
505
+ // Parse migration event to get pool address
506
+ const logs = parseEventLogs({
507
+ abi: MANIA_FACTORY_ABI,
508
+ logs: receipt.logs,
509
+ eventName: "CompleteManiaAmmMigrationEvent",
510
+ });
511
+
512
+ const poolAddress = logs[0]?.args?.pool as Address | undefined;
513
+
514
+ return {
515
+ hash,
516
+ success: receipt.status === "success",
517
+ poolAddress,
518
+ };
519
+ }
520
+
521
+ // ========== EVENT WATCHING ==========
522
+
523
+ /**
524
+ * Watch for new token creation events
525
+ *
526
+ * @param callback - Callback function for each event
527
+ * @returns Unwatch function
528
+ */
529
+ watchCreateEvents(callback: (event: CreateEvent) => void): () => void {
530
+ return this.publicClient.watchContractEvent({
531
+ address: this.factoryAddress,
532
+ abi: MANIA_FACTORY_ABI,
533
+ eventName: "CreateEvent",
534
+ onLogs: (logs) => {
535
+ for (const log of logs) {
536
+ const args = log.args as unknown as CreateEvent;
537
+ callback(args);
538
+ }
539
+ },
540
+ });
541
+ }
542
+
543
+ /**
544
+ * Watch for trade events on a specific token
545
+ *
546
+ * @param token - Token address (optional, watches all if not provided)
547
+ * @param callback - Callback function for each event
548
+ * @returns Unwatch function
549
+ */
550
+ watchTradeEvents(token: Address | undefined, callback: (event: TradeEvent) => void): () => void {
551
+ return this.publicClient.watchContractEvent({
552
+ address: this.factoryAddress,
553
+ abi: MANIA_FACTORY_ABI,
554
+ eventName: "TradeEvent",
555
+ args: token ? { mint: token } : undefined,
556
+ onLogs: (logs) => {
557
+ for (const log of logs) {
558
+ const args = log.args as unknown as TradeEvent;
559
+ callback(args);
560
+ }
561
+ },
562
+ });
563
+ }
564
+
565
+ /**
566
+ * Watch for bonding curve completion events
567
+ *
568
+ * @param callback - Callback function for each event
569
+ * @returns Unwatch function
570
+ */
571
+ watchCompleteEvents(callback: (event: CompleteEvent) => void): () => void {
572
+ return this.publicClient.watchContractEvent({
573
+ address: this.factoryAddress,
574
+ abi: MANIA_FACTORY_ABI,
575
+ eventName: "CompleteEvent",
576
+ onLogs: (logs) => {
577
+ for (const log of logs) {
578
+ const args = log.args as unknown as CompleteEvent;
579
+ callback(args);
580
+ }
581
+ },
582
+ });
583
+ }
584
+
585
+ /**
586
+ * Watch for migration events
587
+ *
588
+ * @param callback - Callback function for each event
589
+ * @returns Unwatch function
590
+ */
591
+ watchMigrationEvents(callback: (event: MigrationEvent) => void): () => void {
592
+ return this.publicClient.watchContractEvent({
593
+ address: this.factoryAddress,
594
+ abi: MANIA_FACTORY_ABI,
595
+ eventName: "CompleteManiaAmmMigrationEvent",
596
+ onLogs: (logs) => {
597
+ for (const log of logs) {
598
+ const args = log.args as unknown as MigrationEvent;
599
+ callback(args);
600
+ }
601
+ },
602
+ });
603
+ }
604
+
605
+ // ========== UTILITY METHODS ==========
606
+
607
+ /**
608
+ * Check if a token's bonding curve is complete
609
+ *
610
+ * @param token - Token address
611
+ */
612
+ async isComplete(token: Address): Promise<boolean> {
613
+ const curve = await this.getBondingCurve(token);
614
+ return curve.complete;
615
+ }
616
+
617
+ /**
618
+ * Check if a token has been migrated
619
+ *
620
+ * @param token - Token address
621
+ */
622
+ async isMigrated(token: Address): Promise<boolean> {
623
+ const curve = await this.getBondingCurve(token);
624
+ return (
625
+ curve.realEthReserves === 0n &&
626
+ curve.virtualEthReserves === 0n &&
627
+ curve.realTokenReserves === 0n &&
628
+ curve.virtualTokenReserves === 0n
629
+ );
630
+ }
631
+
632
+ /**
633
+ * Get the factory contract address
634
+ */
635
+ getFactoryAddress(): Address {
636
+ return this.factoryAddress;
637
+ }
638
+
639
+ /**
640
+ * Get the public client
641
+ */
642
+ getPublicClient(): PublicClient {
643
+ return this.publicClient;
644
+ }
645
+
646
+ /**
647
+ * Get the wallet client
648
+ */
649
+ getWalletClient(): WalletClient | null {
650
+ return this.walletClient;
651
+ }
652
+ }