@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.
package/README.md CHANGED
@@ -280,12 +280,12 @@ console.log(UNISWAP_FEE_TIER); // 3000
280
280
  import { getChainConfig, CHAIN_CONFIGS } from "@mania-labs/mania-sdk";
281
281
 
282
282
  // Get config for a specific chain
283
- const baseConfig = getChainConfig(8453);
283
+ const baseConfig = getChainConfig(4326);
284
284
  console.log(baseConfig?.factoryAddress);
285
285
  console.log(baseConfig?.wethAddress);
286
286
 
287
287
  // Or create SDK from chain ID
288
- const sdk = ManiaSDK.fromChainId(8453, "https://mainnet.base.org");
288
+ const sdk = ManiaSDK.fromChainId(4326, "https://megaeth.blockscout.com");
289
289
  ```
290
290
 
291
291
  ## Advanced Usage
@@ -299,18 +299,18 @@ import { privateKeyToAccount } from "viem/accounts";
299
299
 
300
300
  const publicClient = createPublicClient({
301
301
  chain: base,
302
- transport: http("https://mainnet.base.org"),
302
+ transport: http("https://megaeth.blockscout.com"),
303
303
  });
304
304
 
305
305
  const walletClient = createWalletClient({
306
306
  account: privateKeyToAccount("0x..."),
307
307
  chain: base,
308
- transport: http("https://mainnet.base.org"),
308
+ transport: http("https://megaeth.blockscout.com"),
309
309
  });
310
310
 
311
311
  const sdk = new ManiaSDK({
312
312
  factoryAddress: "0x...",
313
- chainId: 8453,
313
+ chainId: 4326,
314
314
  });
315
315
 
316
316
  sdk.setPublicClient(publicClient);
package/dist/index.js CHANGED
@@ -76,14 +76,24 @@ 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",
84
84
  nonfungiblePositionManager: "0xa204A97EF8Bd2E3198f19EB5a804680467BD85f5",
85
85
  uniswapV3Factory: "0x619fb6C12c36b57a8bAb05e98F42C43745DCf69f",
86
86
  blockExplorer: "https://megaeth-testnet-v2.blockscout.com"
87
+ },
88
+ // Mega Eth Mainnet
89
+ 4326: {
90
+ chainId: 4326,
91
+ name: "Mega Eth Mainnet",
92
+ factoryAddress: "0xb2f6344Cb0Ee13e027759166770198f4d8B3106d",
93
+ wethAddress: "0x4200000000000000000000000000000000000006",
94
+ nonfungiblePositionManager: "0x2b781C57e6358f64864Ff8EC464a03Fdaf9974bA",
95
+ uniswapV3Factory: "0x68b34591f662508076927803c567Cc8006988a09",
96
+ blockExplorer: "https://megaeth.blockscout.com/"
87
97
  }
88
98
  };
89
99
  function getChainConfig(chainId) {
@@ -815,12 +825,13 @@ var ManiaSDK = class _ManiaSDK {
815
825
  async create(params) {
816
826
  const wallet = this.getConnectedWallet();
817
827
  const hash = await wallet.writeContract({
818
- chain: null,
819
- account: null,
828
+ chain: wallet.chain,
829
+ account: wallet.account,
820
830
  address: this.factoryAddress,
821
831
  abi: MANIA_FACTORY_ABI,
822
832
  functionName: "create",
823
- args: [params.name, params.symbol, params.uri, params.creator]
833
+ args: [params.name, params.symbol, params.uri, params.creator],
834
+ gas: 300000000n
824
835
  });
825
836
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
826
837
  const logs = (0, import_viem.parseEventLogs)({
@@ -844,13 +855,14 @@ var ManiaSDK = class _ManiaSDK {
844
855
  async createAndBuy(params) {
845
856
  const wallet = this.getConnectedWallet();
846
857
  const hash = await wallet.writeContract({
847
- chain: null,
848
- account: null,
858
+ chain: wallet.chain,
859
+ account: wallet.account,
849
860
  address: this.factoryAddress,
850
861
  abi: MANIA_FACTORY_ABI,
851
862
  functionName: "createAndBuy",
852
863
  args: [params.name, params.symbol, params.uri, params.creator, params.minTokensOut],
853
- value: params.buyAmountEth
864
+ value: params.buyAmountEth,
865
+ gas: 300000000n
854
866
  });
855
867
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
856
868
  const logs = (0, import_viem.parseEventLogs)({
@@ -875,13 +887,14 @@ var ManiaSDK = class _ManiaSDK {
875
887
  const wallet = this.getConnectedWallet();
876
888
  const recipient = params.recipient ?? wallet.account.address;
877
889
  const hash = await wallet.writeContract({
878
- chain: null,
879
- account: null,
890
+ chain: wallet.chain,
891
+ account: wallet.account,
880
892
  address: this.factoryAddress,
881
893
  abi: MANIA_FACTORY_ABI,
882
894
  functionName: "buy",
883
895
  args: [params.token, params.minTokensOut, recipient],
884
- value: params.amountEth
896
+ value: params.amountEth,
897
+ gas: 300000000n
885
898
  });
886
899
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
887
900
  return {
@@ -914,12 +927,13 @@ var ManiaSDK = class _ManiaSDK {
914
927
  async sell(params) {
915
928
  const wallet = this.getConnectedWallet();
916
929
  const hash = await wallet.writeContract({
917
- chain: null,
918
- account: null,
930
+ chain: wallet.chain,
931
+ account: wallet.account,
919
932
  address: this.factoryAddress,
920
933
  abi: MANIA_FACTORY_ABI,
921
934
  functionName: "sell",
922
- args: [params.token, params.amountTokens, params.minEthOut]
935
+ args: [params.token, params.amountTokens, params.minEthOut],
936
+ gas: 300000000n
923
937
  });
924
938
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
925
939
  return {
@@ -953,13 +967,14 @@ var ManiaSDK = class _ManiaSDK {
953
967
  const wallet = this.getConnectedWallet();
954
968
  const globalState = await this.getGlobalState();
955
969
  const hash = await wallet.writeContract({
956
- chain: null,
957
- account: null,
970
+ chain: wallet.chain,
971
+ account: wallet.account,
958
972
  address: this.factoryAddress,
959
973
  abi: MANIA_FACTORY_ABI,
960
974
  functionName: "migrate",
961
975
  args: [params.token],
962
- value: globalState.poolMigrationFee
976
+ value: globalState.poolMigrationFee,
977
+ gas: 300000000n
963
978
  });
964
979
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
965
980
  const logs = (0, import_viem.parseEventLogs)({
package/dist/index.mjs CHANGED
@@ -22,14 +22,24 @@ 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",
30
30
  nonfungiblePositionManager: "0xa204A97EF8Bd2E3198f19EB5a804680467BD85f5",
31
31
  uniswapV3Factory: "0x619fb6C12c36b57a8bAb05e98F42C43745DCf69f",
32
32
  blockExplorer: "https://megaeth-testnet-v2.blockscout.com"
33
+ },
34
+ // Mega Eth Mainnet
35
+ 4326: {
36
+ chainId: 4326,
37
+ name: "Mega Eth Mainnet",
38
+ factoryAddress: "0xb2f6344Cb0Ee13e027759166770198f4d8B3106d",
39
+ wethAddress: "0x4200000000000000000000000000000000000006",
40
+ nonfungiblePositionManager: "0x2b781C57e6358f64864Ff8EC464a03Fdaf9974bA",
41
+ uniswapV3Factory: "0x68b34591f662508076927803c567Cc8006988a09",
42
+ blockExplorer: "https://megaeth.blockscout.com/"
33
43
  }
34
44
  };
35
45
  function getChainConfig(chainId) {
@@ -761,12 +771,13 @@ var ManiaSDK = class _ManiaSDK {
761
771
  async create(params) {
762
772
  const wallet = this.getConnectedWallet();
763
773
  const hash = await wallet.writeContract({
764
- chain: null,
765
- account: null,
774
+ chain: wallet.chain,
775
+ account: wallet.account,
766
776
  address: this.factoryAddress,
767
777
  abi: MANIA_FACTORY_ABI,
768
778
  functionName: "create",
769
- args: [params.name, params.symbol, params.uri, params.creator]
779
+ args: [params.name, params.symbol, params.uri, params.creator],
780
+ gas: 300000000n
770
781
  });
771
782
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
772
783
  const logs = parseEventLogs({
@@ -790,13 +801,14 @@ var ManiaSDK = class _ManiaSDK {
790
801
  async createAndBuy(params) {
791
802
  const wallet = this.getConnectedWallet();
792
803
  const hash = await wallet.writeContract({
793
- chain: null,
794
- account: null,
804
+ chain: wallet.chain,
805
+ account: wallet.account,
795
806
  address: this.factoryAddress,
796
807
  abi: MANIA_FACTORY_ABI,
797
808
  functionName: "createAndBuy",
798
809
  args: [params.name, params.symbol, params.uri, params.creator, params.minTokensOut],
799
- value: params.buyAmountEth
810
+ value: params.buyAmountEth,
811
+ gas: 300000000n
800
812
  });
801
813
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
802
814
  const logs = parseEventLogs({
@@ -821,13 +833,14 @@ var ManiaSDK = class _ManiaSDK {
821
833
  const wallet = this.getConnectedWallet();
822
834
  const recipient = params.recipient ?? wallet.account.address;
823
835
  const hash = await wallet.writeContract({
824
- chain: null,
825
- account: null,
836
+ chain: wallet.chain,
837
+ account: wallet.account,
826
838
  address: this.factoryAddress,
827
839
  abi: MANIA_FACTORY_ABI,
828
840
  functionName: "buy",
829
841
  args: [params.token, params.minTokensOut, recipient],
830
- value: params.amountEth
842
+ value: params.amountEth,
843
+ gas: 300000000n
831
844
  });
832
845
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
833
846
  return {
@@ -860,12 +873,13 @@ var ManiaSDK = class _ManiaSDK {
860
873
  async sell(params) {
861
874
  const wallet = this.getConnectedWallet();
862
875
  const hash = await wallet.writeContract({
863
- chain: null,
864
- account: null,
876
+ chain: wallet.chain,
877
+ account: wallet.account,
865
878
  address: this.factoryAddress,
866
879
  abi: MANIA_FACTORY_ABI,
867
880
  functionName: "sell",
868
- args: [params.token, params.amountTokens, params.minEthOut]
881
+ args: [params.token, params.amountTokens, params.minEthOut],
882
+ gas: 300000000n
869
883
  });
870
884
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
871
885
  return {
@@ -899,13 +913,14 @@ var ManiaSDK = class _ManiaSDK {
899
913
  const wallet = this.getConnectedWallet();
900
914
  const globalState = await this.getGlobalState();
901
915
  const hash = await wallet.writeContract({
902
- chain: null,
903
- account: null,
916
+ chain: wallet.chain,
917
+ account: wallet.account,
904
918
  address: this.factoryAddress,
905
919
  abi: MANIA_FACTORY_ABI,
906
920
  functionName: "migrate",
907
921
  args: [params.token],
908
- value: globalState.poolMigrationFee
922
+ value: globalState.poolMigrationFee,
923
+ gas: 300000000n
909
924
  });
910
925
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
911
926
  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.2",
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
+ }