@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 CHANGED
@@ -76,8 +76,8 @@ var DEFAULT_SLIPPAGE_BPS = 100;
76
76
  var BPS_DENOMINATOR = 10000n;
77
77
  var CHAIN_CONFIGS = {
78
78
  // Mega Eth Testnet
79
- 6543: {
80
- chainId: 6543,
79
+ 6343: {
80
+ chainId: 6343,
81
81
  name: "Mega Eth Testnet",
82
82
  factoryAddress: "0x0d593cE47EBA2d15a77ddbAc41BdE6d03CC9241b",
83
83
  wethAddress: "0x4200000000000000000000000000000000000006",
@@ -815,12 +815,13 @@ var ManiaSDK = class _ManiaSDK {
815
815
  async create(params) {
816
816
  const wallet = this.getConnectedWallet();
817
817
  const hash = await wallet.writeContract({
818
- chain: null,
819
- account: null,
818
+ chain: wallet.chain,
819
+ account: wallet.account,
820
820
  address: this.factoryAddress,
821
821
  abi: MANIA_FACTORY_ABI,
822
822
  functionName: "create",
823
- args: [params.name, params.symbol, params.uri, params.creator]
823
+ args: [params.name, params.symbol, params.uri, params.creator],
824
+ gas: 300000000n
824
825
  });
825
826
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
826
827
  const logs = (0, import_viem.parseEventLogs)({
@@ -844,13 +845,14 @@ var ManiaSDK = class _ManiaSDK {
844
845
  async createAndBuy(params) {
845
846
  const wallet = this.getConnectedWallet();
846
847
  const hash = await wallet.writeContract({
847
- chain: null,
848
- account: null,
848
+ chain: wallet.chain,
849
+ account: wallet.account,
849
850
  address: this.factoryAddress,
850
851
  abi: MANIA_FACTORY_ABI,
851
852
  functionName: "createAndBuy",
852
853
  args: [params.name, params.symbol, params.uri, params.creator, params.minTokensOut],
853
- value: params.buyAmountEth
854
+ value: params.buyAmountEth,
855
+ gas: 300000000n
854
856
  });
855
857
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
856
858
  const logs = (0, import_viem.parseEventLogs)({
@@ -875,13 +877,14 @@ var ManiaSDK = class _ManiaSDK {
875
877
  const wallet = this.getConnectedWallet();
876
878
  const recipient = params.recipient ?? wallet.account.address;
877
879
  const hash = await wallet.writeContract({
878
- chain: null,
879
- account: null,
880
+ chain: wallet.chain,
881
+ account: wallet.account,
880
882
  address: this.factoryAddress,
881
883
  abi: MANIA_FACTORY_ABI,
882
884
  functionName: "buy",
883
885
  args: [params.token, params.minTokensOut, recipient],
884
- value: params.amountEth
886
+ value: params.amountEth,
887
+ gas: 300000000n
885
888
  });
886
889
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
887
890
  return {
@@ -914,12 +917,13 @@ var ManiaSDK = class _ManiaSDK {
914
917
  async sell(params) {
915
918
  const wallet = this.getConnectedWallet();
916
919
  const hash = await wallet.writeContract({
917
- chain: null,
918
- account: null,
920
+ chain: wallet.chain,
921
+ account: wallet.account,
919
922
  address: this.factoryAddress,
920
923
  abi: MANIA_FACTORY_ABI,
921
924
  functionName: "sell",
922
- args: [params.token, params.amountTokens, params.minEthOut]
925
+ args: [params.token, params.amountTokens, params.minEthOut],
926
+ gas: 300000000n
923
927
  });
924
928
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
925
929
  return {
@@ -953,13 +957,14 @@ var ManiaSDK = class _ManiaSDK {
953
957
  const wallet = this.getConnectedWallet();
954
958
  const globalState = await this.getGlobalState();
955
959
  const hash = await wallet.writeContract({
956
- chain: null,
957
- account: null,
960
+ chain: wallet.chain,
961
+ account: wallet.account,
958
962
  address: this.factoryAddress,
959
963
  abi: MANIA_FACTORY_ABI,
960
964
  functionName: "migrate",
961
965
  args: [params.token],
962
- value: globalState.poolMigrationFee
966
+ value: globalState.poolMigrationFee,
967
+ gas: 300000000n
963
968
  });
964
969
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
965
970
  const logs = (0, import_viem.parseEventLogs)({
package/dist/index.mjs CHANGED
@@ -22,8 +22,8 @@ var DEFAULT_SLIPPAGE_BPS = 100;
22
22
  var BPS_DENOMINATOR = 10000n;
23
23
  var CHAIN_CONFIGS = {
24
24
  // Mega Eth Testnet
25
- 6543: {
26
- chainId: 6543,
25
+ 6343: {
26
+ chainId: 6343,
27
27
  name: "Mega Eth Testnet",
28
28
  factoryAddress: "0x0d593cE47EBA2d15a77ddbAc41BdE6d03CC9241b",
29
29
  wethAddress: "0x4200000000000000000000000000000000000006",
@@ -761,12 +761,13 @@ var ManiaSDK = class _ManiaSDK {
761
761
  async create(params) {
762
762
  const wallet = this.getConnectedWallet();
763
763
  const hash = await wallet.writeContract({
764
- chain: null,
765
- account: null,
764
+ chain: wallet.chain,
765
+ account: wallet.account,
766
766
  address: this.factoryAddress,
767
767
  abi: MANIA_FACTORY_ABI,
768
768
  functionName: "create",
769
- args: [params.name, params.symbol, params.uri, params.creator]
769
+ args: [params.name, params.symbol, params.uri, params.creator],
770
+ gas: 300000000n
770
771
  });
771
772
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
772
773
  const logs = parseEventLogs({
@@ -790,13 +791,14 @@ var ManiaSDK = class _ManiaSDK {
790
791
  async createAndBuy(params) {
791
792
  const wallet = this.getConnectedWallet();
792
793
  const hash = await wallet.writeContract({
793
- chain: null,
794
- account: null,
794
+ chain: wallet.chain,
795
+ account: wallet.account,
795
796
  address: this.factoryAddress,
796
797
  abi: MANIA_FACTORY_ABI,
797
798
  functionName: "createAndBuy",
798
799
  args: [params.name, params.symbol, params.uri, params.creator, params.minTokensOut],
799
- value: params.buyAmountEth
800
+ value: params.buyAmountEth,
801
+ gas: 300000000n
800
802
  });
801
803
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
802
804
  const logs = parseEventLogs({
@@ -821,13 +823,14 @@ var ManiaSDK = class _ManiaSDK {
821
823
  const wallet = this.getConnectedWallet();
822
824
  const recipient = params.recipient ?? wallet.account.address;
823
825
  const hash = await wallet.writeContract({
824
- chain: null,
825
- account: null,
826
+ chain: wallet.chain,
827
+ account: wallet.account,
826
828
  address: this.factoryAddress,
827
829
  abi: MANIA_FACTORY_ABI,
828
830
  functionName: "buy",
829
831
  args: [params.token, params.minTokensOut, recipient],
830
- value: params.amountEth
832
+ value: params.amountEth,
833
+ gas: 300000000n
831
834
  });
832
835
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
833
836
  return {
@@ -860,12 +863,13 @@ var ManiaSDK = class _ManiaSDK {
860
863
  async sell(params) {
861
864
  const wallet = this.getConnectedWallet();
862
865
  const hash = await wallet.writeContract({
863
- chain: null,
864
- account: null,
866
+ chain: wallet.chain,
867
+ account: wallet.account,
865
868
  address: this.factoryAddress,
866
869
  abi: MANIA_FACTORY_ABI,
867
870
  functionName: "sell",
868
- args: [params.token, params.amountTokens, params.minEthOut]
871
+ args: [params.token, params.amountTokens, params.minEthOut],
872
+ gas: 300000000n
869
873
  });
870
874
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
871
875
  return {
@@ -899,13 +903,14 @@ var ManiaSDK = class _ManiaSDK {
899
903
  const wallet = this.getConnectedWallet();
900
904
  const globalState = await this.getGlobalState();
901
905
  const hash = await wallet.writeContract({
902
- chain: null,
903
- account: null,
906
+ chain: wallet.chain,
907
+ account: wallet.account,
904
908
  address: this.factoryAddress,
905
909
  abi: MANIA_FACTORY_ABI,
906
910
  functionName: "migrate",
907
911
  args: [params.token],
908
- value: globalState.poolMigrationFee
912
+ value: globalState.poolMigrationFee,
913
+ gas: 300000000n
909
914
  });
910
915
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
911
916
  const logs = parseEventLogs({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mania-labs/mania-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Official SDK for interacting with the Mania Protocol - EVM Bonding Curve Token Launchpad",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,6 +24,10 @@
24
24
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
25
25
  "lint": "eslint src --ext .ts",
26
26
  "test": "vitest",
27
+ "test:unit": "vitest run --dir src/__tests__/unit",
28
+ "test:integration": "vitest run --dir src/__tests__/integration",
29
+ "test:coverage": "vitest run --coverage",
30
+ "test:watch": "vitest watch",
27
31
  "prepublishOnly": "npm run build"
28
32
  },
29
33
  "keywords": [
@@ -43,6 +47,8 @@
43
47
  },
44
48
  "devDependencies": {
45
49
  "@types/node": "^24.10.1",
50
+ "@vitest/coverage-v8": "^2.1.0",
51
+ "dotenv": "^16.3.1",
46
52
  "tsup": "^8.5.1",
47
53
  "typescript": "^5.9.3",
48
54
  "vitest": "^2.1.0"
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "WebFetch(domain:www.npmjs.com)"
4
+ "WebFetch(domain:www.npmjs.com)",
5
+ "Bash(cat:*)"
5
6
  ],
6
7
  "deny": [],
7
8
  "ask": []
@@ -0,0 +1,136 @@
1
+ import type { Address } from 'viem';
2
+ import { parseEther } from 'viem';
3
+ import type { BondingCurveState, GlobalState } from '../../types.js';
4
+
5
+ /**
6
+ * Test addresses
7
+ */
8
+ export const TEST_ADDRESSES = {
9
+ FACTORY: '0x0d593cE47EBA2d15a77ddbAc41BdE6d03CC9241b' as Address,
10
+ WETH: '0x4200000000000000000000000000000000000006' as Address,
11
+ ZERO: '0x0000000000000000000000000000000000000000' as Address,
12
+ USER1: '0x1111111111111111111111111111111111111111' as Address,
13
+ USER2: '0x2222222222222222222222222222222222222222' as Address,
14
+ TOKEN1: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as Address,
15
+ TOKEN2: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' as Address,
16
+ };
17
+
18
+ /**
19
+ * Common test amounts
20
+ */
21
+ export const TEST_AMOUNTS = {
22
+ SMALL_ETH: parseEther('0.01'),
23
+ MEDIUM_ETH: parseEther('0.1'),
24
+ LARGE_ETH: parseEther('1'),
25
+ MIGRATION_THRESHOLD: parseEther('4'),
26
+ SMALL_TOKENS: parseEther('1000'),
27
+ MEDIUM_TOKENS: parseEther('1000000'),
28
+ LARGE_TOKENS: parseEther('100000000'),
29
+ };
30
+
31
+ /**
32
+ * Default token creation parameters
33
+ */
34
+ export const TEST_TOKEN_PARAMS = {
35
+ name: 'Test Token',
36
+ symbol: 'TEST',
37
+ uri: 'https://example.com/metadata.json',
38
+ creator: TEST_ADDRESSES.USER1,
39
+ };
40
+
41
+ /**
42
+ * Initial virtual reserves (matching contract defaults)
43
+ */
44
+ export const INITIAL_RESERVES = {
45
+ virtualTokenReserves: 1_073_000_000_000_000_000_000_000_000n, // 1.073B tokens
46
+ virtualEthReserves: 30_000_000_000_000_000n, // 0.03 ETH
47
+ realTokenReserves: 793_100_000_000_000_000_000_000_000n, // 793.1M tokens
48
+ tokenTotalSupply: 1_000_000_000_000_000_000_000_000_000n, // 1B tokens
49
+ };
50
+
51
+ /**
52
+ * Pre-configured bonding curve scenarios
53
+ */
54
+ export const CURVE_SCENARIOS = {
55
+ /**
56
+ * Fresh curve - no trades yet
57
+ */
58
+ FRESH: {
59
+ virtualTokenReserves: INITIAL_RESERVES.virtualTokenReserves,
60
+ virtualEthReserves: INITIAL_RESERVES.virtualEthReserves,
61
+ realTokenReserves: INITIAL_RESERVES.realTokenReserves,
62
+ realEthReserves: 0n,
63
+ tokenTotalSupply: INITIAL_RESERVES.tokenTotalSupply,
64
+ complete: false,
65
+ trackVolume: true,
66
+ } as BondingCurveState,
67
+
68
+ /**
69
+ * Half-filled curve - 2 ETH real reserves (50% to migration)
70
+ */
71
+ HALF_FILLED: {
72
+ virtualTokenReserves: 800_000_000_000_000_000_000_000_000n,
73
+ virtualEthReserves: 2_030_000_000_000_000_000n, // 2.03 ETH
74
+ realTokenReserves: 520_000_000_000_000_000_000_000_000n,
75
+ realEthReserves: 2_000_000_000_000_000_000n, // 2 ETH
76
+ tokenTotalSupply: INITIAL_RESERVES.tokenTotalSupply,
77
+ complete: false,
78
+ trackVolume: true,
79
+ } as BondingCurveState,
80
+
81
+ /**
82
+ * Near-complete curve - 3.9 ETH real reserves
83
+ */
84
+ NEAR_COMPLETE: {
85
+ virtualTokenReserves: 300_000_000_000_000_000_000_000_000n,
86
+ virtualEthReserves: 3_930_000_000_000_000_000n,
87
+ realTokenReserves: 27_000_000_000_000_000_000_000_000n,
88
+ realEthReserves: 3_900_000_000_000_000_000n, // 3.9 ETH
89
+ tokenTotalSupply: INITIAL_RESERVES.tokenTotalSupply,
90
+ complete: false,
91
+ trackVolume: true,
92
+ } as BondingCurveState,
93
+
94
+ /**
95
+ * Complete curve - reached 4 ETH threshold
96
+ */
97
+ COMPLETE: {
98
+ virtualTokenReserves: 280_000_000_000_000_000_000_000_000n,
99
+ virtualEthReserves: 4_030_000_000_000_000_000n,
100
+ realTokenReserves: 7_000_000_000_000_000_000_000_000n,
101
+ realEthReserves: 4_000_000_000_000_000_000n, // 4 ETH
102
+ tokenTotalSupply: INITIAL_RESERVES.tokenTotalSupply,
103
+ complete: true,
104
+ trackVolume: true,
105
+ } as BondingCurveState,
106
+
107
+ /**
108
+ * Migrated curve - all reserves zeroed out
109
+ */
110
+ MIGRATED: {
111
+ virtualTokenReserves: 0n,
112
+ virtualEthReserves: 0n,
113
+ realTokenReserves: 0n,
114
+ realEthReserves: 0n,
115
+ tokenTotalSupply: INITIAL_RESERVES.tokenTotalSupply,
116
+ complete: true,
117
+ trackVolume: true,
118
+ } as BondingCurveState,
119
+ };
120
+
121
+ /**
122
+ * Default global state
123
+ */
124
+ export const DEFAULT_GLOBAL_STATE: GlobalState = {
125
+ initialized: true,
126
+ authority: TEST_ADDRESSES.USER1,
127
+ feeRecipient: TEST_ADDRESSES.USER1,
128
+ initialVirtualTokenReserves: INITIAL_RESERVES.virtualTokenReserves,
129
+ initialVirtualEthReserves: INITIAL_RESERVES.virtualEthReserves,
130
+ initialRealTokenReserves: INITIAL_RESERVES.realTokenReserves,
131
+ tokenTotalSupply: INITIAL_RESERVES.tokenTotalSupply,
132
+ feeBasisPoints: 100n, // 1%
133
+ withdrawAuthority: TEST_ADDRESSES.USER1,
134
+ enableMigrate: true,
135
+ poolMigrationFee: 300_000_000_000_000n, // 0.0003 ETH
136
+ };
@@ -0,0 +1,258 @@
1
+ import {
2
+ createPublicClient,
3
+ createWalletClient,
4
+ http,
5
+ type Address,
6
+ type PublicClient,
7
+ type WalletClient,
8
+ type Hash,
9
+ formatEther,
10
+ parseEther,
11
+ } from 'viem';
12
+ import { privateKeyToAccount } from 'viem/accounts';
13
+ import { ManiaSDK } from '../../mania.js';
14
+ import { CHAIN_CONFIGS } from '../../constants.js';
15
+ import { ERC20_ABI } from '../../abi.js';
16
+
17
+ // Mega ETH Testnet chain definition
18
+ const megaEthTestnet = {
19
+ id: 6343,
20
+ name: 'Mega ETH Testnet',
21
+ nativeCurrency: {
22
+ decimals: 18,
23
+ name: 'Ether',
24
+ symbol: 'ETH',
25
+ },
26
+ rpcUrls: {
27
+ default: {
28
+ http: [process.env.MEGA_ETH_RPC_URL || 'https://timothy.megaeth.com/rpc'],
29
+ },
30
+ },
31
+ blockExplorers: {
32
+ default: {
33
+ name: 'Blockscout',
34
+ url: 'https://megaeth-testnet-v2.blockscout.com',
35
+ },
36
+ },
37
+ } as const;
38
+
39
+ /**
40
+ * Balance snapshot for tracking changes
41
+ */
42
+ export interface BalanceSnapshot {
43
+ eth: bigint;
44
+ token?: bigint;
45
+ tokenAddress?: Address;
46
+ }
47
+
48
+ /**
49
+ * Balance comparison result
50
+ */
51
+ export interface BalanceComparison {
52
+ ethDiff: bigint;
53
+ tokenDiff?: bigint;
54
+ ethDiffFormatted: string;
55
+ tokenDiffFormatted?: string;
56
+ }
57
+
58
+ /**
59
+ * Create SDK instance for integration tests
60
+ */
61
+ export function createTestSDK(): {
62
+ sdk: ManiaSDK;
63
+ walletAddress: Address;
64
+ publicClient: PublicClient;
65
+ walletClient: WalletClient;
66
+ } {
67
+ const privateKey = process.env.TEST_PRIVATE_KEY;
68
+ if (!privateKey) {
69
+ throw new Error('TEST_PRIVATE_KEY environment variable is required');
70
+ }
71
+
72
+ const rpcUrl = process.env.MEGA_ETH_RPC_URL || 'https://timothy.megaeth.com/rpc';
73
+ const chainConfig = CHAIN_CONFIGS[6343];
74
+
75
+ if (!chainConfig) {
76
+ throw new Error('Mega ETH Testnet chain config not found');
77
+ }
78
+
79
+ const account = privateKeyToAccount(
80
+ privateKey.startsWith('0x')
81
+ ? (privateKey as `0x${string}`)
82
+ : (`0x${privateKey}` as `0x${string}`)
83
+ );
84
+
85
+ const publicClient = createPublicClient({
86
+ chain: megaEthTestnet,
87
+ transport: http(rpcUrl),
88
+ });
89
+
90
+ const walletClient = createWalletClient({
91
+ account,
92
+ chain: megaEthTestnet,
93
+ transport: http(rpcUrl),
94
+ });
95
+
96
+ const sdk = new ManiaSDK({
97
+ factoryAddress: chainConfig.factoryAddress,
98
+ rpcUrl,
99
+ chainId: 6343,
100
+ });
101
+
102
+ sdk.setPublicClient(publicClient as unknown as PublicClient);
103
+ sdk.setWalletClient(walletClient as unknown as WalletClient);
104
+
105
+ return {
106
+ sdk,
107
+ walletAddress: account.address,
108
+ publicClient: publicClient as unknown as PublicClient,
109
+ walletClient: walletClient as unknown as WalletClient,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Get ETH and optional token balance snapshot
115
+ */
116
+ export async function getBalanceSnapshot(
117
+ publicClient: PublicClient,
118
+ walletAddress: Address,
119
+ tokenAddress?: Address
120
+ ): Promise<BalanceSnapshot> {
121
+ const eth = await publicClient.getBalance({ address: walletAddress });
122
+
123
+ let token: bigint | undefined;
124
+ if (tokenAddress) {
125
+ token = await getTokenBalance(publicClient, tokenAddress, walletAddress);
126
+ }
127
+
128
+ return {
129
+ eth,
130
+ token,
131
+ tokenAddress,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Compare two balance snapshots
137
+ */
138
+ export function compareBalances(
139
+ before: BalanceSnapshot,
140
+ after: BalanceSnapshot
141
+ ): BalanceComparison {
142
+ const ethDiff = after.eth - before.eth;
143
+
144
+ let tokenDiff: bigint | undefined;
145
+ let tokenDiffFormatted: string | undefined;
146
+
147
+ if (before.token !== undefined && after.token !== undefined) {
148
+ tokenDiff = after.token - before.token;
149
+ tokenDiffFormatted = formatEther(tokenDiff);
150
+ }
151
+
152
+ return {
153
+ ethDiff,
154
+ tokenDiff,
155
+ ethDiffFormatted: formatEther(ethDiff),
156
+ tokenDiffFormatted,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Get ERC20 token balance
162
+ */
163
+ export async function getTokenBalance(
164
+ publicClient: PublicClient,
165
+ tokenAddress: Address,
166
+ walletAddress: Address
167
+ ): Promise<bigint> {
168
+ const balance = await publicClient.readContract({
169
+ address: tokenAddress,
170
+ abi: ERC20_ABI,
171
+ functionName: 'balanceOf',
172
+ args: [walletAddress],
173
+ });
174
+
175
+ return balance as bigint;
176
+ }
177
+
178
+ /**
179
+ * Verify a transaction was successful
180
+ */
181
+ export async function verifyTransactionSuccess(
182
+ publicClient: PublicClient,
183
+ hash: Hash
184
+ ): Promise<boolean> {
185
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
186
+ return receipt.status === 'success';
187
+ }
188
+
189
+ /**
190
+ * Check if wallet has enough ETH
191
+ */
192
+ export async function hasEnoughETH(
193
+ publicClient: PublicClient,
194
+ walletAddress: Address,
195
+ requiredAmount: bigint,
196
+ buffer: bigint = parseEther('0.01') // Buffer for gas
197
+ ): Promise<boolean> {
198
+ const balance = await publicClient.getBalance({ address: walletAddress });
199
+ return balance >= requiredAmount + buffer;
200
+ }
201
+
202
+ /**
203
+ * Generate unique token parameters for each test
204
+ */
205
+ export function generateUniqueTokenParams() {
206
+ const timestamp = Date.now();
207
+ const random = Math.floor(Math.random() * 10000);
208
+
209
+ return {
210
+ name: `Test Token ${timestamp}-${random}`,
211
+ symbol: `T${random}`,
212
+ uri: `https://example.com/token/${timestamp}-${random}.json`,
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Retry wrapper for flaky network operations
218
+ */
219
+ export async function withTestRetry<T>(
220
+ fn: () => Promise<T>,
221
+ maxRetries: number = 3,
222
+ delayMs: number = 1000
223
+ ): Promise<T> {
224
+ let lastError: Error | undefined;
225
+
226
+ for (let i = 0; i < maxRetries; i++) {
227
+ try {
228
+ return await fn();
229
+ } catch (error) {
230
+ lastError = error instanceof Error ? error : new Error(String(error));
231
+ if (i < maxRetries - 1) {
232
+ await new Promise((resolve) => setTimeout(resolve, delayMs * (i + 1)));
233
+ }
234
+ }
235
+ }
236
+
237
+ throw lastError;
238
+ }
239
+
240
+ /**
241
+ * Wait for a condition with timeout
242
+ */
243
+ export async function waitForCondition(
244
+ condition: () => Promise<boolean>,
245
+ timeoutMs: number = 30000,
246
+ intervalMs: number = 1000
247
+ ): Promise<boolean> {
248
+ const startTime = Date.now();
249
+
250
+ while (Date.now() - startTime < timeoutMs) {
251
+ if (await condition()) {
252
+ return true;
253
+ }
254
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
255
+ }
256
+
257
+ return false;
258
+ }