@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,184 @@
1
+ import { vi } from 'vitest';
2
+ import type { Address, PublicClient, WalletClient, Hash } from 'viem';
3
+ import type { BondingCurveState, GlobalState } from '../../types.js';
4
+ import {
5
+ CURVE_SCENARIOS,
6
+ DEFAULT_GLOBAL_STATE,
7
+ TEST_ADDRESSES,
8
+ } from './fixtures.js';
9
+
10
+ /**
11
+ * Create a mock bonding curve state with optional overrides
12
+ */
13
+ export function createMockBondingCurveState(
14
+ overrides: Partial<BondingCurveState> = {}
15
+ ): BondingCurveState {
16
+ return {
17
+ ...CURVE_SCENARIOS.FRESH,
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Create a mock global state with optional overrides
24
+ */
25
+ export function createMockGlobalState(
26
+ overrides: Partial<GlobalState> = {}
27
+ ): GlobalState {
28
+ return {
29
+ ...DEFAULT_GLOBAL_STATE,
30
+ ...overrides,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Mock transaction hash
36
+ */
37
+ export const MOCK_TX_HASH =
38
+ '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash;
39
+
40
+ /**
41
+ * Create a mock public client with configurable responses
42
+ */
43
+ export function createMockPublicClient(config: {
44
+ bondingCurve?: BondingCurveState;
45
+ globalState?: GlobalState;
46
+ buyQuote?: bigint;
47
+ sellQuote?: bigint;
48
+ fee?: bigint;
49
+ userVolume?: bigint;
50
+ } = {}): PublicClient {
51
+ const mockClient = {
52
+ readContract: vi.fn().mockImplementation(({ functionName, args }) => {
53
+ switch (functionName) {
54
+ case 'global':
55
+ return Promise.resolve([
56
+ config.globalState?.initialized ?? DEFAULT_GLOBAL_STATE.initialized,
57
+ config.globalState?.authority ?? DEFAULT_GLOBAL_STATE.authority,
58
+ config.globalState?.feeRecipient ?? DEFAULT_GLOBAL_STATE.feeRecipient,
59
+ config.globalState?.initialVirtualTokenReserves ??
60
+ DEFAULT_GLOBAL_STATE.initialVirtualTokenReserves,
61
+ config.globalState?.initialVirtualEthReserves ??
62
+ DEFAULT_GLOBAL_STATE.initialVirtualEthReserves,
63
+ config.globalState?.initialRealTokenReserves ??
64
+ DEFAULT_GLOBAL_STATE.initialRealTokenReserves,
65
+ config.globalState?.tokenTotalSupply ??
66
+ DEFAULT_GLOBAL_STATE.tokenTotalSupply,
67
+ config.globalState?.feeBasisPoints ??
68
+ DEFAULT_GLOBAL_STATE.feeBasisPoints,
69
+ config.globalState?.withdrawAuthority ??
70
+ DEFAULT_GLOBAL_STATE.withdrawAuthority,
71
+ config.globalState?.enableMigrate ??
72
+ DEFAULT_GLOBAL_STATE.enableMigrate,
73
+ config.globalState?.poolMigrationFee ??
74
+ DEFAULT_GLOBAL_STATE.poolMigrationFee,
75
+ ]);
76
+
77
+ case 'getBondingCurve': {
78
+ const curve = config.bondingCurve ?? CURVE_SCENARIOS.FRESH;
79
+ return Promise.resolve({
80
+ virtualTokenReserves: curve.virtualTokenReserves,
81
+ virtualEthReserves: curve.virtualEthReserves,
82
+ realTokenReserves: curve.realTokenReserves,
83
+ realEthReserves: curve.realEthReserves,
84
+ tokenTotalSupply: curve.tokenTotalSupply,
85
+ complete: curve.complete,
86
+ trackVolume: curve.trackVolume,
87
+ });
88
+ }
89
+
90
+ case 'getBuyQuote':
91
+ return Promise.resolve(config.buyQuote ?? 1000000000000000000000n);
92
+
93
+ case 'getSellQuote':
94
+ return Promise.resolve(config.sellQuote ?? 10000000000000000n);
95
+
96
+ case 'getFee':
97
+ return Promise.resolve(config.fee ?? (args[0] as bigint) / 100n);
98
+
99
+ case 'userVolume':
100
+ return Promise.resolve(config.userVolume ?? 0n);
101
+
102
+ default:
103
+ return Promise.resolve(undefined);
104
+ }
105
+ }),
106
+
107
+ watchContractEvent: vi.fn().mockReturnValue(() => {}),
108
+
109
+ waitForTransactionReceipt: vi.fn().mockResolvedValue({
110
+ status: 'success',
111
+ logs: [],
112
+ }),
113
+
114
+ getBalance: vi.fn().mockResolvedValue(10000000000000000000n), // 10 ETH
115
+ } as unknown as PublicClient;
116
+
117
+ return mockClient;
118
+ }
119
+
120
+ /**
121
+ * Create a mock wallet client
122
+ */
123
+ export function createMockWalletClient(config: {
124
+ address?: Address;
125
+ txHash?: Hash;
126
+ } = {}): WalletClient {
127
+ const address = config.address ?? TEST_ADDRESSES.USER1;
128
+ const txHash = config.txHash ?? MOCK_TX_HASH;
129
+
130
+ const mockClient = {
131
+ account: {
132
+ address,
133
+ },
134
+ writeContract: vi.fn().mockResolvedValue(txHash),
135
+ } as unknown as WalletClient;
136
+
137
+ return mockClient;
138
+ }
139
+
140
+ /**
141
+ * Create mock event logs for CreateEvent
142
+ */
143
+ export function createMockCreateEventLogs(tokenAddress: Address) {
144
+ return [
145
+ {
146
+ args: {
147
+ name: 'Test Token',
148
+ symbol: 'TEST',
149
+ uri: 'https://example.com/metadata.json',
150
+ mint: tokenAddress,
151
+ user: TEST_ADDRESSES.USER1,
152
+ creator: TEST_ADDRESSES.USER1,
153
+ timestamp: BigInt(Date.now()),
154
+ },
155
+ },
156
+ ];
157
+ }
158
+
159
+ /**
160
+ * Create mock event logs for TradeEvent
161
+ */
162
+ export function createMockTradeEventLogs(config: {
163
+ token: Address;
164
+ ethAmount: bigint;
165
+ tokenAmount: bigint;
166
+ isBuy: boolean;
167
+ }) {
168
+ return [
169
+ {
170
+ args: {
171
+ mint: config.token,
172
+ ethAmount: config.ethAmount,
173
+ tokenAmount: config.tokenAmount,
174
+ isBuy: config.isBuy,
175
+ user: TEST_ADDRESSES.USER1,
176
+ timestamp: BigInt(Date.now()),
177
+ virtualEthReserves: 1000000000000000000n,
178
+ virtualTokenReserves: 1000000000000000000000000000n,
179
+ realEthReserves: 100000000000000000n,
180
+ realTokenReserves: 700000000000000000000000000n,
181
+ },
182
+ },
183
+ ];
184
+ }
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { parseEther, type Address } from 'viem';
3
+ import { ManiaSDK } from '../../mania.js';
4
+ import { createTestSDK, withTestRetry } from '../helpers/integrationHelpers.js';
5
+ import { RUN_INTEGRATION_TESTS } from '../setup.js';
6
+
7
+ describe.skipIf(!RUN_INTEGRATION_TESTS)('SDK Read Methods - Integration', () => {
8
+ let sdk: ManiaSDK;
9
+ let walletAddress: Address;
10
+ let testTokenAddress: Address | undefined;
11
+
12
+ beforeAll(async () => {
13
+ const testSetup = createTestSDK();
14
+ sdk = testSetup.sdk;
15
+ walletAddress = testSetup.walletAddress;
16
+
17
+ // Use existing test token if provided in environment
18
+ testTokenAddress = process.env.TEST_TOKEN_ADDRESS as Address | undefined;
19
+
20
+ console.log(`Running integration tests with wallet: ${walletAddress}`);
21
+ if (testTokenAddress) {
22
+ console.log(`Using test token: ${testTokenAddress}`);
23
+ }
24
+ });
25
+
26
+ describe('getGlobalState', () => {
27
+ it('should return valid global state', async () => {
28
+ const state = await withTestRetry(() => sdk.getGlobalState());
29
+
30
+ expect(state.initialized).toBe(true);
31
+ expect(state.authority).toMatch(/^0x[a-fA-F0-9]{40}$/);
32
+ expect(state.feeRecipient).toMatch(/^0x[a-fA-F0-9]{40}$/);
33
+ expect(state.feeBasisPoints).toBeGreaterThan(0n);
34
+ expect(state.feeBasisPoints).toBeLessThanOrEqual(1000n); // Max 10%
35
+ });
36
+
37
+ it('should have valid reserve configurations', async () => {
38
+ const state = await withTestRetry(() => sdk.getGlobalState());
39
+
40
+ expect(state.initialVirtualTokenReserves).toBeGreaterThan(0n);
41
+ expect(state.initialVirtualEthReserves).toBeGreaterThan(0n);
42
+ expect(state.initialRealTokenReserves).toBeGreaterThan(0n);
43
+ expect(state.tokenTotalSupply).toBeGreaterThan(0n);
44
+ });
45
+
46
+ it('should have migration enabled', async () => {
47
+ const state = await withTestRetry(() => sdk.getGlobalState());
48
+
49
+ expect(state.enableMigrate).toBe(true);
50
+ expect(state.poolMigrationFee).toBeGreaterThanOrEqual(0n);
51
+ });
52
+ });
53
+
54
+ describe('getFee', () => {
55
+ it('should calculate fee correctly', async () => {
56
+ const amount = parseEther('1');
57
+ const fee = await withTestRetry(() => sdk.getFee(amount));
58
+
59
+ expect(fee).toBeGreaterThan(0n);
60
+ expect(fee).toBeLessThan(amount);
61
+ });
62
+
63
+ it('should scale proportionally with amount', async () => {
64
+ const smallAmount = parseEther('0.1');
65
+ const largeAmount = parseEther('1');
66
+
67
+ const [smallFee, largeFee] = await Promise.all([
68
+ withTestRetry(() => sdk.getFee(smallAmount)),
69
+ withTestRetry(() => sdk.getFee(largeAmount)),
70
+ ]);
71
+
72
+ // Large fee should be approximately 10x small fee
73
+ const ratio = Number(largeFee) / Number(smallFee);
74
+ expect(ratio).toBeCloseTo(10, 1);
75
+ });
76
+ });
77
+
78
+ describe('getUserVolume', () => {
79
+ it('should return bigint >= 0', async () => {
80
+ const volume = await withTestRetry(() => sdk.getUserVolume(walletAddress));
81
+ expect(volume).toBeGreaterThanOrEqual(0n);
82
+ });
83
+ });
84
+
85
+ describe.skipIf(!testTokenAddress)('Token-specific read methods', () => {
86
+ it('should get bonding curve state', async () => {
87
+ const curve = await withTestRetry(() => sdk.getBondingCurve(testTokenAddress!));
88
+
89
+ expect(curve.virtualTokenReserves).toBeGreaterThanOrEqual(0n);
90
+ expect(curve.virtualEthReserves).toBeGreaterThanOrEqual(0n);
91
+ expect(curve.realTokenReserves).toBeGreaterThanOrEqual(0n);
92
+ expect(curve.realEthReserves).toBeGreaterThanOrEqual(0n);
93
+ expect(curve.tokenTotalSupply).toBeGreaterThan(0n);
94
+ expect(typeof curve.complete).toBe('boolean');
95
+ expect(typeof curve.trackVolume).toBe('boolean');
96
+ });
97
+
98
+ it('should get bonding curve instance', async () => {
99
+ const curve = await withTestRetry(() => sdk.getBondingCurveInstance(testTokenAddress!));
100
+
101
+ expect(curve).toBeDefined();
102
+ expect(curve.getCurrentPrice()).toBeGreaterThanOrEqual(0n);
103
+ expect(curve.getMigrationProgress()).toBeGreaterThanOrEqual(0);
104
+ expect(curve.getMigrationProgress()).toBeLessThanOrEqual(100);
105
+ });
106
+
107
+ it('should get comprehensive token info', async () => {
108
+ const info = await withTestRetry(() => sdk.getTokenInfo(testTokenAddress!));
109
+
110
+ expect(info.address).toBe(testTokenAddress);
111
+ expect(info.bondingCurve).toBeDefined();
112
+ expect(info.currentPrice).toBeGreaterThanOrEqual(0n);
113
+ expect(info.marketCapEth).toBeGreaterThanOrEqual(0n);
114
+ expect(info.migrationProgress).toBeGreaterThanOrEqual(0);
115
+ expect(info.migrationProgress).toBeLessThanOrEqual(100);
116
+ });
117
+
118
+ it('should get buy quote', async () => {
119
+ const ethAmount = parseEther('0.01');
120
+ const quote = await withTestRetry(() => sdk.getBuyQuote(testTokenAddress!, ethAmount));
121
+
122
+ // Quote should be > 0 unless curve is migrated
123
+ const isMigrated = await sdk.isMigrated(testTokenAddress!);
124
+ if (!isMigrated) {
125
+ expect(quote).toBeGreaterThan(0n);
126
+ }
127
+ });
128
+
129
+ it('should get sell quote', async () => {
130
+ const tokenAmount = parseEther('1000');
131
+ const quote = await withTestRetry(() => sdk.getSellQuote(testTokenAddress!, tokenAmount));
132
+
133
+ // Quote should be >= 0
134
+ expect(quote).toBeGreaterThanOrEqual(0n);
135
+ });
136
+
137
+ it('should return valid complete status', async () => {
138
+ const complete = await withTestRetry(() => sdk.isComplete(testTokenAddress!));
139
+ expect(typeof complete).toBe('boolean');
140
+ });
141
+
142
+ it('should return valid migrated status', async () => {
143
+ const migrated = await withTestRetry(() => sdk.isMigrated(testTokenAddress!));
144
+ expect(typeof migrated).toBe('boolean');
145
+ });
146
+
147
+ it('migrated should imply complete', async () => {
148
+ const [complete, migrated] = await Promise.all([
149
+ withTestRetry(() => sdk.isComplete(testTokenAddress!)),
150
+ withTestRetry(() => sdk.isMigrated(testTokenAddress!)),
151
+ ]);
152
+
153
+ if (migrated) {
154
+ expect(complete).toBe(true);
155
+ }
156
+ });
157
+ });
158
+
159
+ describe('Quote consistency', () => {
160
+ it.skipIf(!testTokenAddress)('should return more tokens for larger ETH amounts', async () => {
161
+ const isMigrated = await sdk.isMigrated(testTokenAddress!);
162
+ if (isMigrated) return;
163
+
164
+ const [smallQuote, largeQuote] = await Promise.all([
165
+ withTestRetry(() => sdk.getBuyQuote(testTokenAddress!, parseEther('0.01'))),
166
+ withTestRetry(() => sdk.getBuyQuote(testTokenAddress!, parseEther('0.1'))),
167
+ ]);
168
+
169
+ expect(largeQuote).toBeGreaterThan(smallQuote);
170
+ });
171
+ });
172
+
173
+ describe('Factory address', () => {
174
+ it('should return correct factory address', () => {
175
+ const factoryAddress = sdk.getFactoryAddress();
176
+ expect(factoryAddress).toMatch(/^0x[a-fA-F0-9]{40}$/);
177
+ });
178
+ });
179
+
180
+ describe('Client accessors', () => {
181
+ it('should return public client', () => {
182
+ const publicClient = sdk.getPublicClient();
183
+ expect(publicClient).toBeDefined();
184
+ });
185
+
186
+ it('should return wallet client when connected', () => {
187
+ const walletClient = sdk.getWalletClient();
188
+ expect(walletClient).toBeDefined();
189
+ });
190
+
191
+ it('should return wallet address', () => {
192
+ const address = sdk.getWalletAddress();
193
+ expect(address).toBe(walletAddress);
194
+ });
195
+ });
196
+ });