quantumswap 0.0.1 → 0.0.3

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.
Files changed (47) hide show
  1. package/README.md +73 -53
  2. package/examples/README.md +46 -0
  3. package/examples/deploy-IERC20.js +5 -3
  4. package/examples/deploy-QuantumSwapV2ERC20.js +5 -3
  5. package/examples/deploy-QuantumSwapV2Factory.js +5 -3
  6. package/examples/deploy-QuantumSwapV2Pair.js +5 -3
  7. package/examples/deploy-QuantumSwapV2Router02.js +5 -3
  8. package/examples/deploy-WQ.js +5 -3
  9. package/examples/events-IERC20.js +5 -3
  10. package/examples/events-QuantumSwapV2ERC20.js +5 -3
  11. package/examples/events-QuantumSwapV2Factory.js +5 -3
  12. package/examples/events-QuantumSwapV2Pair.js +5 -3
  13. package/examples/events-QuantumSwapV2Router02.js +5 -3
  14. package/examples/events-WQ.js +5 -3
  15. package/examples/offline-signing-IERC20.js +5 -3
  16. package/examples/offline-signing-QuantumSwapV2ERC20.js +5 -3
  17. package/examples/offline-signing-QuantumSwapV2Factory.js +5 -3
  18. package/examples/offline-signing-QuantumSwapV2Pair.js +5 -3
  19. package/examples/offline-signing-QuantumSwapV2Router02.js +5 -3
  20. package/examples/offline-signing-WQ.js +5 -3
  21. package/examples/package-lock.json +62 -0
  22. package/examples/package.json +14 -0
  23. package/examples/read-operations-IERC20.js +5 -3
  24. package/examples/read-operations-QuantumSwapV2ERC20.js +5 -3
  25. package/examples/read-operations-QuantumSwapV2Factory.js +5 -3
  26. package/examples/read-operations-QuantumSwapV2Pair.js +5 -3
  27. package/examples/read-operations-QuantumSwapV2Router02.js +5 -3
  28. package/examples/read-operations-WQ.js +5 -3
  29. package/examples/run-dex-flow-custom.js +493 -0
  30. package/examples/run-dex-flow-custom.ts +556 -0
  31. package/examples/write-operations-IERC20.js +5 -3
  32. package/examples/write-operations-QuantumSwapV2ERC20.js +5 -3
  33. package/examples/write-operations-QuantumSwapV2Factory.js +5 -3
  34. package/examples/write-operations-QuantumSwapV2Pair.js +5 -3
  35. package/examples/write-operations-QuantumSwapV2Router02.js +5 -3
  36. package/examples/write-operations-WQ.js +5 -3
  37. package/package.json +8 -9
  38. package/test/e2e/IERC20.e2e.test.js +2 -2
  39. package/test/e2e/QuantumSwapV2ERC20.e2e.test.js +2 -2
  40. package/test/e2e/QuantumSwapV2Factory.e2e.test.js +2 -2
  41. package/test/e2e/QuantumSwapV2Pair.e2e.test.js +2 -2
  42. package/test/e2e/QuantumSwapV2Router02.e2e.test.js +2 -2
  43. package/test/e2e/WQ.e2e.test.js +2 -2
  44. package/test/e2e/all-contracts.e2e.test.js +103 -72
  45. package/test/e2e/dex-full-flow.e2e.test.js +276 -47
  46. package/examples/walkthrough-dex-full-flow.js +0 -226
  47. package/examples/walkthrough-dex-full-flow.ts +0 -231
@@ -3,7 +3,7 @@
3
3
  * @blockchainRequired write
4
4
  * @description QuantumSwap V2 DEX full flow: deploy WQ/Factory/Router, two ERC20s,
5
5
  * create pair, add liquidity (when tokens have supply), quote, token↔token and ETH↔token swaps,
6
- * balance checks. Uses quantumcoin.js SDK: isAddress, getAddress, parseUnits, formatUnits,
6
+ * send tokens to another wallet, balance checks. Uses quantumcoin.js SDK: isAddress, getAddress, parseUnits, formatUnits,
7
7
  * Interface.parseLog for event decoding.
8
8
  *
9
9
  * WARNING: Uses a hardcoded test wallet; broadcasts real transactions.
@@ -15,9 +15,8 @@ const path = require("node:path");
15
15
 
16
16
  const { Initialize } = require("quantumcoin/config");
17
17
  const {
18
- JsonRpcProvider,
18
+ getProvider,
19
19
  Wallet,
20
- Contract,
21
20
  ContractFactory,
22
21
  getCreateAddress,
23
22
  isAddress,
@@ -57,16 +56,66 @@ const TEST_WALLET_ENCRYPTED_JSON =
57
56
  "{\"address\":\"1a846abe71c8b989e8337c55d608be81c28ab3b2e40c83eaa2a68d516049aec6\",\"crypto\":{\"cipher\":\"aes-256-ctr\",\"ciphertext\":\"ab7e620dd66cb55ac201b9c6796de92bbb06f3681b5932eabe099871f1f7d79acabe30921a39ad13bfe74f42c515734882b6723760142aa3e26e011df514a534ae47bd15d86badd9c6f17c48d4c892711d54d441ee3a0ee0e5b060f816e79c7badd13ff4c235934b1986774223ecf6e8761388969bb239c759b54c8c70e6a2e27c93a4b70129c8159f461d271ae8f3573414c78b88e4d0abfa6365ed45456636d4ed971c7a0c6b84e6f0c2621e819268b135e2bcc169a54d1847b39e6ba2ae8ec969b69f330b7db9e785ed02204d5a1185915ae5338b0f40ef2a7f4d5aaf7563d502135e57f4eb89d5ec1efa5c77e374969d6cd85be625a2ed1225d68ecdd84067bfc69adb83ecd5c6050472eca28a5a646fcdd28077165c629975bec8a79fe1457cb53389b788b25e1f8eff8b2ca326d7dfcaba3f8839225a08057c018a458891fd2caa0d2b27632cffd80f592147ccec9a10dc8a08a48fb55047bff5cf85cda39eb089096bef63842fc3686412f298a54a9e4b0bf4ad36907ba373cbd6d32e7ac494af371da5aa9d38a3463220865114c4adc5e4ac258ba9c6af9fa2ddfd1aec2e16887e4b3977c69561df8599ac9d411c9dd2a4d57f92ea4e5c02aae3f49fb3bc83e16673e6c2dbe96bb181c8dfd0f9757ade2e4ff27215a836058c5ffeab042f6f97c7c02339f76a6284680e01b4bb733690eb3347fbfcc26614b8bf755f9dfce3fea9d4e4d15b164983201732c2e87593a86bca6da6972e128490338f76ae68135888070f4e59e90db54d23834769bdbda9769213faf5357f9167a224523975a946367b68f0cec98658575609f58bfd329e420a921c06713326e4cb20a0df1d77f37e78a320a637a96c604ca3fa89e24beb42313751b8f09b14f9c14c77e4fd13fc6382505d27c771bca0d821ec7c3765acffa99d83c50140a56b0b28101c762bd682fe55cb6f23cbeb3f421d7b36021010e45ac27160dd7ead99c864a1b550c7edb1246950fe32dcc049799f9085287f0a747a6ef7a023df46a23a22f3e833bbf8d404f84344870492658256ee1dfc40fda33bb8d48fc72d4520ba9fc820c9123104a045206809037709f2a5f6723fa77d6bac5a573823d4ec3a7f1cb786a52ee2697e622e5d75962fa554d1024a6c355e21f33a63b2b72e6c4742a8b1c373aa532b40518c38c90b5373c2eb8c9d7be2a9e16047a3ee09dc9a6849deac5183ace6cfe91a9bef2ffc0a7df6ccebfd4c858c84b0e0355650d7466971e66f1e3883013e5ad1be33199b1d110b79070ac1b745ccb14cf63a08f8cca3a21c9525e626ff5f0c34746e10750fb742ad51f11f2acae3676c2111853d7250d01b77821a6ba9e04400ba2c543ca9f2d701ae6f47bfad14ffe3039ee9e71f7b2401359ade9938750ddb9c5a8b018a7929ed8d0e717ff1861446ce17535e9b17c187711190aae3388bd9490837a636c25ed4d42d7079ad1a51e13292c683d5d012abcf46965c534b83ab53f2c1f0cf5830ef7582e06863a33c19a70511df632885d63245965047ea96b56f1af5b3b94a54999f784fb9574fdfcd7c1230e07a2aaa04acd3097b2b9f8ddba05ae9734491deb5c1a513c76ed276cb78bbf4839dae3156d76af444a5805129d5df791167a9c8576a1d7f760b2d2797c4658669608706fbd0ace1be2346f74862dfc9ef518e55632e43c043186e5d070deb34d12fb9e5aba84e5cb50213dc88efd39cc35bf42455aa82d5e3b707b3140be3b8623b34fdd81d08615c188ae8438a13881fdf6bf32f2cb9ff5fa625561040c6b71d4b8eccc90bc3b99650d28dd1ee63773e49664e3d48c484996b290943635a6f2eb1ce9796d3fa144a3f00ef82faaa32d6a413668f7b521517cb68b2b017fcf56c79326fa5e4060e643631ca3f0a0dc0ed718798b6f46b130d437c33f64039e887324b6f5e604b1669d613923794edbf04b1b3caea54793b52b44b170173a4f25c7ecef3b71e2aad76e556b1cb9f1d637ec52ececfa950dd31dbb6a60828a3ad34c1beffe09eb4785786d63bad10a0b0f66ea88c57380f38ea85f018dbd7f538cf1ee7624095b9a01ec5edd528f281168af020609e651ff316aa1320a710134ddfca600cc72174dcdb846d2aa29916488aa1b537b66da92e61af526debef4eb38c984569eaf549ff2129449269b492d030cd74d885f6f5785881cc4804b4a8a09ba4ff7aefe9074ac7d0c4f05d51fe4cc0ff7388a772092b9d02d70e5433a5cf3e02f46a6bd6b818d59a07ce3b9fbbf8b5faba74563bcc5240930c2d406c9aaee3e3ce0429bf68ac2b0a57adb09414cff50817d2a48fb9fa624ab863cb0c31a8b8dc5eaf6fa68cc1d7c6c685c5a33edd5c8933b9e8ab628ee428d0743699b2ff17f25586c7ce959280bb0b8c5342251f0a30b53dbc7bf1ee426ac9619c3560f811f2268ee37f189794e2e4b3db3a2fb2e34b649e504fb467438abfd1082619cc4a0b30d66beb831077812e418d2e2148db10cf4d4a29101ca52ec445b8d83519dd7de85a98e0beae9ee537096d3f1a55a7a80cdfa93d25f07c9f98e8af18cde19ec1f99c5dd4588b717a5039ddb7f177717caf0d0fd45420a70dbd6d3146890d9e450d5224146db4c33b779e3c3a04b976c052bad042ac57dd38be45407808c0fb0d7e2a8819e6cd53c6739e6612996ddaa6f066552590aa0343bc1e62b298ff2514a0cef8be21956c2e942816f7a3a3a0935eaf9b37251409ce444c986c3817e82835555fe18239f3ae33469d7965c2bde9991fde556bd07af01df52bbde0c35bb4ef48e3b5d0db53f8ca4ed35b83f760f0a1bc4ed9f86e85d6039a17df373c85402ef956f01db00eb39c4b74bd0660d29ee746714d9780d738e05c6cca414ce3d7b40dda8036a9eea9ab1388805f913eb19bdd3f09d9e161eaa50231bd9caba61971f194332dd28c696a60458c1c6c2cc5da8b1192611c7c553e9e12fe48ce46bbb891be8bb118721c86222e671ddd1da8f0ccb2b68e02f2014b4925e904e88369aaf7466bd7033a60c265d45955944916ecbdb84bf1b522b01b0149c632e04c568a7eb627c5bb90ece052ebcf79166c28b30d23fe52da0a5ab5dea83ca479a3e3b7a9cfbbfea04dbe6137c19d067317c2ec427a8c75a6b06bec6dcd5d5c0edc9aa80b9003b8e17c088b2f3db327d3e42630d82d20120240c3ba56232280787da4aabbf5bc95a864029f00710e195f2a76460a0317d10b552fe1bea097e41d49756c680a41d6ac186e62169b6b6cd7776ea84618b5b752328a5bacaa10aa122ff9b2698b43efe73d852a899db644863c8c9bc8068ea86ea843fd6fe36272b91cdc5d5317083ef3fd1e5462a0b0d0604dc57b3bbfceb0fca4cd349625dd7b25166af30efe5ee6a0af953a74d65f4736c59918ee55a3b0d9d9d42e04c7f8a77e479109f740e20c464d5d7e3d16805f47b61f403ff7f408c9e850d9baacd8067e544536a4953480b0f9ee9cd45f41ebd67b51f78788a6470cb1e5ca72ca346ce8a50d0ca0c921d5576a4455a1afb6d0bc688004712ee122cacdb29c51e84893324c27fa4a3f1917edf5352272b4c97579a6152e4b77663d0ab532915f2eeb6a862de8b696452321b660c3f2449673d086e95a7af28845a5259b763e0fcd09f72acf7b6c811066263060e5aa5b24658e880a01fd56bda4dad5ab604e129290f7d5489728f2a40968c6168b21cebbbcd11727cc9e9160c4e92e04387d3b0d62aab06a61f26daedd9fed11816ef2180172a47f47184ac4032b88758c98a2e0fb200f70e93ba695f5ebb7a1029610ad360d3b7fa1b4640b9dc674d3625eef786da93dff19bc7991b5d6193a3896664763fde479b5dfc04812111a80782854f2cf68ca7d82765cc9eb40fba4b44640710ed6e653abf9f07b466333f4fd22784d53cf40e17120f42caa841eaa24056b237827b0f47f7257c103c35027e9f503e5acfd023e7357b600d3084d361d5ee65ba319b45c153212a54e6fed85af7e43e0a926ebcbc2edf8de7e2ec9528f00bec262ad04d5c9dafccaea06a24748d28bf1799bae0e895543084539c50b5aaa4fb50d7431d6f0c8cee2a54aaf7ee7919b55bf40adb688632e5dbe273cea09e97b19c3d8e1f4de000deb66fa1942ad03a62d3252f51992244366c156000b49c297167a6cbdedea7ebae139d295f0ad298e0864249b905b7eb812886ec70ecdb286702274b5b8574149bf3866f9e46b997ff5ed622b169a0eb071347f18d530db1663906a28f4544ee4e004ab87b65476af30ede118052ff052b8dc986ca2c93dd5d4943266a579c7698ea014f688b3e8063a107feb162d392e2177b01bff77fb5abe5feebd0607158049a5a093325b7c9ee6b4dfa7a9f65c7c2fb628920d3603a1c2dad979eaa047cd661a268af1078c9788d720e64e4ce9d12e68de1e417ef2f293323681e1071f9220e1ee43d2e29d111b870ce3439f5100ecd4551ab65ee74aa1667e564957e9bc0ae1ea193980da2a0ec2698073388c85bec25ef447f0d5e93a5203fa44dff268e5cb799ed3b66e63d5e07b487e7534f24934c73a62a243e0151843a0fd3807711a101eaa7fc71f0ba68aebb9534d57cba41b094eebfb4c31cca8eddfa426f676aa347be8a7023a4e91ddb154b35cd4d5f7dbc2e5db491de99f33fc2cff2d57029ac950e1ccd681980af6a4e8969dfe39b3c7bfcbcf8fac92f1e6ec9fe572bfa6a7d65860eab2ed10ac01a71290b52e3148e84b7376a8605cd2bb0e8681ffc54691ce087685e33921bd44d36c78291713dce17569570f62137e6904f0d68cf53aa2ec395c389a75141f08114fb293ea63950e4ffee55ec6fc83cf44876b8e7f25cdd393ff87b9eda6eb746085b61a6900de191f0ce2cb388d61ece52e78bc47368194e8e00277e0d1631e6b9d4626ef76f8522582ccd5a40be3febc699bb510acc6271d55ff0f4cf3bb7669855a72efd9ca3e1056a2fe592a5bc877cce2b1f63b58383971da87873d2d1349cf5881242cdce4e7e2c5c514755746a0e0a7c2a6d9701cde005ae3420beb17c379a3516662253554f51f0423bb1844b0b90c54ed8177ceb0e1036a6609d836e748ca06c40ca64befadc6443ec286a0ce464678e8d11eb455f7bb305acebf6cb1f50e394a9bfeb752df1687831bac9cdd811f4f112ef6658d0f8799a866374ff96c5e2b79f30e7a74f8a2bc9ed1f88f01f30e30cb78ffb2bff10108f35e910ee3be4463e9e6f0ed910e8d598326e71dfa2277ffe5579d7fe9b6018bfe295b25219eae07b3b0270665c3fa00c3e0d180812b5cd62925585de84a7c48a9a86dba96544a251654d1966e082432dc85b6149cf21e91a46020ec32b66d28ba3b6a90c0617bc6fdd55aea819af2bcf84864ad60c28fe3c9f8339d0aee68b39d97f63b6e082835d86119cf9b9fdc8b827c847ce40aa10e1577a710132316845e825345e95bdf94d0c66ec65a6c4319fce4792313663b5f7a651a6710783e6ab71608ac6cbbf3af6911adf596ccf7c172b9bd5bceb6db379967b32b143bdd11d2ee12ddf64ecef6391e0f8570e6cddd3db95204919362b89b739fa94e7c1bfde799fd5e22aa25ca6ca42e30c08e23aae2385d99ebab441072a880dcefdab74a4c9bd39d363f6d1933d59400fca161d432aa00f23b1b1c19a154be8989699d549b66d44e39896f5523443bc6ddf4a65e91f1f3fb7b52318869a05856a4fc92f3694c81ed833c972fb918f7e5\",\"cipherparams\":{\"iv\":\"8c46d6162cd4c765759aedcbce2a5874\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"82fb6cdc6917609135277badacf15baa31899d08b71a5a0fa33167167c161537\"},\"mac\":\"9187b17f7eca48e6b8c586b0cd790dbe0feb876ac8385f93faa7d5e22a3c8fc7\"},\"id\":\"92caf6ee-2d43-48c0-859e-ffa1e0e23312\",\"version\":3}";
58
57
  const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
59
58
 
60
- const DEPLOY_GAS_LIMIT = 6_000_000;
61
- const TX_GAS_LIMIT = 400_000;
62
- const DEADLINE_OFFSET = 1200;
59
+ const DEPLOY_GAS_FALLBACK = 6_000_000n;
60
+ const TX_GAS_FALLBACK = 400_000n;
61
+ const GAS_BUFFER_PERCENT = 110n; // 10% buffer over estimate
62
+ // Use chain block timestamp (UTC) + offset for deadline so it matches what the router checks (no clock skew).
63
+ // Router requires block.timestamp <= deadline.
64
+ const DEADLINE_OFFSET = process.env.QC_DEADLINE_OFFSET ? Number(process.env.QC_DEADLINE_OFFSET) : 3600;
65
+
66
+ /** Returns deadline as UTC Unix timestamp: current chain block timestamp + DEADLINE_OFFSET. */
67
+ async function deadline(provider) {
68
+ const blockNumber = await provider.getBlockNumber();
69
+ const block = await provider.getBlock(blockNumber);
70
+ const blockTimestamp = block && block.timestamp != null ? Number(block.timestamp) : Math.floor(Date.now() / 1000);
71
+ return BigInt(blockTimestamp + DEADLINE_OFFSET);
72
+ }
73
+
74
+ function reportDexFlow(contractAddresses, txHashes) {
75
+ console.log("\n========== DEX-FULL-FLOW E2E REPORT ==========");
76
+ console.log("Contract addresses:\n");
77
+ for (const [name, addr] of Object.entries(contractAddresses)) {
78
+ console.log(` ${name}: ${addr}`);
79
+ }
80
+ console.log("\nTransaction IDs:\n");
81
+ (txHashes || []).forEach((hash, i) => {
82
+ console.log(` [${i + 1}] ${hash}`);
83
+ });
84
+ console.log("================================================\n");
85
+ }
86
+
87
+ /** Estimate gas via provider.estimateGas (quantumcoin.js SDK), add buffer, fallback on error. */
88
+ async function estimateGasLimit(provider, txRequest, fallback) {
89
+ try {
90
+ const est = await provider.estimateGas(txRequest);
91
+ const estBn = typeof est === "bigint" ? est : BigInt(est);
92
+ const withBuffer = (estBn * GAS_BUFFER_PERCENT) / 100n;
93
+ return withBuffer > 0n ? withBuffer : fallback;
94
+ } catch {
95
+ return fallback;
96
+ }
97
+ }
63
98
 
64
- function deadline() {
65
- return BigInt(Math.floor(Date.now() / 1000) + DEADLINE_OFFSET);
99
+ /** Deploy gas: estimate from getDeployTransaction().data, apply floor for large bytecode. */
100
+ async function deployGasLimit(provider, from, getDeployTx, fallback, bytecodeFloor) {
101
+ const txReq = getDeployTx();
102
+ if (!txReq || !txReq.data) return fallback;
103
+ let gas = await estimateGasLimit(
104
+ provider,
105
+ { from, data: txReq.data },
106
+ fallback,
107
+ );
108
+ if (bytecodeFloor && txReq.data && txReq.data.length > 40000 && gas < bytecodeFloor) {
109
+ gas = bytecodeFloor;
110
+ }
111
+ return gas;
66
112
  }
67
113
 
68
114
  describe("QuantumSwap V2 DEX full flow", () => {
69
- it("deploys WQ, Factory, Router; validates addresses; deploys two ERC20s; creates pair; parses PairCreated; balance checks", async (t) => {
115
+ it("deploys WQ, Factory, Router; validates addresses; deploys two ERC20s; creates pair; parses PairCreated; balance checks; sends tokens to another wallet", async (t) => {
116
+ const contractAddresses = {};
117
+ const txHashes = [];
118
+ try {
70
119
  const rpcUrl = process.env.QC_RPC_URL;
71
120
  if (!rpcUrl) {
72
121
  t.skip("QC_RPC_URL not provided");
@@ -80,7 +129,7 @@ describe("QuantumSwap V2 DEX full flow", () => {
80
129
  const chainId = process.env.QC_CHAIN_ID ? Number(process.env.QC_CHAIN_ID) : 123123;
81
130
  await Initialize(null);
82
131
 
83
- const provider = new JsonRpcProvider(rpcUrl, chainId);
132
+ const provider = getProvider(rpcUrl, chainId);
84
133
  const wallet = Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
85
134
 
86
135
  // --- Address validation (quantumcoin.js isAddress / getAddress) ---
@@ -88,34 +137,62 @@ describe("QuantumSwap V2 DEX full flow", () => {
88
137
  const walletAddr = getAddress(wallet.address);
89
138
  assert.ok(walletAddr && walletAddr.length === 66 && walletAddr.startsWith("0x"));
90
139
 
140
+ // Before-balance for step 11 (native): capture before any transactions
141
+ const nativeBalanceBeforeAllTxs = await provider.getBalance(walletAddr);
142
+
143
+ let totalGasUsed = 0n;
144
+ let pairWqTokenAAddress = null;
145
+
146
+ /** Add gasUsed from a transaction receipt to totalGasUsed. */
147
+ function addReceiptGas(receipt) {
148
+ if (!receipt) return;
149
+ const g = receipt.gasUsed;
150
+ totalGasUsed += typeof g === "bigint" ? g : BigInt(g ?? 0);
151
+ }
152
+
91
153
  // --- 1) Deploy WQ ---
92
154
  const wqFactory = new WQ__factory(wallet);
93
- const wq = await wqFactory.deploy({ gasLimit: DEPLOY_GAS_LIMIT });
155
+ const wqGasLimit = await deployGasLimit(provider, walletAddr, () => wqFactory.getDeployTransaction(), DEPLOY_GAS_FALLBACK, 6_000_000n);
156
+ const wq = await wqFactory.deploy({ gasLimit: wqGasLimit });
94
157
  const wqDeployTx = wq.deployTransaction();
95
158
  assert.ok(wqDeployTx && wqDeployTx.hash);
96
- await wqDeployTx.wait(1, 600_000);
159
+ console.log("[1] WQ deploy tx id:", wqDeployTx.hash);
160
+ addReceiptGas(await wqDeployTx.wait(1, 600_000));
97
161
  const wqAddress = wq.target;
98
162
  assert.ok(isAddress(wqAddress), "WQ address must be valid");
99
163
  const wqAddressNorm = getAddress(wqAddress);
164
+ contractAddresses.wallet = walletAddr;
165
+ contractAddresses.WQ = wqAddressNorm;
166
+ txHashes.push(wqDeployTx.hash);
100
167
 
101
168
  // --- 2) Deploy QuantumSwapV2Factory ---
102
- const factoryContract = await new QuantumSwapV2Factory__factory(wallet).deploy(walletAddr, { gasLimit: DEPLOY_GAS_LIMIT });
169
+ const factoryFactory = new QuantumSwapV2Factory__factory(wallet);
170
+ const factoryGasLimit = await deployGasLimit(provider, walletAddr, () => factoryFactory.getDeployTransaction(walletAddr), DEPLOY_GAS_FALLBACK, 6_000_000n);
171
+ const factoryContract = await factoryFactory.deploy(walletAddr, { gasLimit: factoryGasLimit });
103
172
  const factoryDeployTx = factoryContract.deployTransaction();
104
173
  assert.ok(factoryDeployTx && factoryDeployTx.hash);
105
- await factoryDeployTx.wait(1, 600_000);
174
+ console.log("[2] Factory deploy tx id:", factoryDeployTx.hash);
175
+ addReceiptGas(await factoryDeployTx.wait(1, 600_000));
106
176
  const factoryAddress = factoryContract.target;
107
177
  assert.ok(isAddress(factoryAddress), "Factory address must be valid");
108
178
  const factoryAddressNorm = getAddress(factoryAddress);
179
+ contractAddresses.Factory = factoryAddressNorm;
180
+ txHashes.push(factoryDeployTx.hash);
109
181
 
110
182
  // --- 3) Deploy QuantumSwapV2Router02 (needs Factory + WQ) ---
111
- const routerContract = await new QuantumSwapV2Router02__factory(wallet).deploy(factoryAddressNorm, wqAddressNorm, {
112
- gasLimit: DEPLOY_GAS_LIMIT,
183
+ const routerFactory = new QuantumSwapV2Router02__factory(wallet);
184
+ const routerGasLimit = await deployGasLimit(provider, walletAddr, () => routerFactory.getDeployTransaction(factoryAddressNorm, wqAddressNorm), DEPLOY_GAS_FALLBACK, 6_000_000n);
185
+ const routerContract = await routerFactory.deploy(factoryAddressNorm, wqAddressNorm, {
186
+ gasLimit: routerGasLimit,
113
187
  });
114
188
  const routerDeployTx = routerContract.deployTransaction();
115
189
  assert.ok(routerDeployTx && routerDeployTx.hash);
116
- await routerDeployTx.wait(1, 600_000);
190
+ console.log("[3] Router deploy tx id:", routerDeployTx.hash);
191
+ addReceiptGas(await routerDeployTx.wait(1, 600_000));
117
192
  const routerAddress = routerContract.target;
118
193
  assert.ok(isAddress(routerAddress), "Router address must be valid");
194
+ contractAddresses.Router = getAddress(routerAddress);
195
+ txHashes.push(routerDeployTx.hash);
119
196
 
120
197
  // Verify Router view calls (ABI / contract interface)
121
198
  const routerFactoryFromContract = await routerContract.factory();
@@ -130,19 +207,25 @@ describe("QuantumSwap V2 DEX full flow", () => {
130
207
  const tx = simpleErc20Factory.getDeployTransaction(name, symbol, initialSupply);
131
208
  const nonce = await provider.getTransactionCount(walletAddr, "pending");
132
209
  const address = getCreateAddress({ from: walletAddr, nonce });
133
- const resp = await wallet.sendTransaction({ ...tx, nonce, gasLimit: DEPLOY_GAS_LIMIT });
134
- await resp.wait(1, 600_000);
135
- const contract = new Contract(address, SIMPLE_ERC20_ABI, wallet);
210
+ const gasLimit = await estimateGasLimit(provider, { from: walletAddr, data: tx.data }, DEPLOY_GAS_FALLBACK);
211
+ const resp = await wallet.sendTransaction({ ...tx, nonce, gasLimit });
212
+ console.log("[4] ERC20 deploy tx id:", name, resp.hash);
213
+ addReceiptGas(await resp.wait(1, 600_000));
214
+ const contract = IERC20.connect(getAddress(address), wallet);
136
215
  contract._deployTx = resp;
137
216
  return contract;
138
217
  };
139
218
  const tokenA = await deploySimpleErc20("TokenA", "TKA");
140
219
  const tokenAAddress = getAddress(tokenA.target);
141
220
  assert.ok(isAddress(tokenAAddress), "TokenA address must be valid");
221
+ contractAddresses.TokenA = tokenAAddress;
222
+ txHashes.push(tokenA._deployTx.hash);
142
223
 
143
224
  const tokenB = await deploySimpleErc20("TokenB", "TKB");
144
225
  const tokenBAddress = getAddress(tokenB.target);
145
226
  assert.ok(isAddress(tokenBAddress), "TokenB address must be valid");
227
+ contractAddresses.TokenB = tokenBAddress;
228
+ txHashes.push(tokenB._deployTx.hash);
146
229
 
147
230
  // --- Balance checks: token A/B (SimpleERC20 mints to deployer) ---
148
231
  const tokenABalanceBeforeRaw = await tokenA.balanceOf(walletAddr);
@@ -153,8 +236,13 @@ describe("QuantumSwap V2 DEX full flow", () => {
153
236
  assert.ok(tokenBBalanceBefore >= initialSupply, "TokenB initial balance >= initialSupply");
154
237
 
155
238
  // --- 5) Create pair (tokenA, tokenB); parse PairCreated event when logs present (Interface.parseLog) ---
156
- const createPairTx = await factoryContract.createPair(tokenAAddress, tokenBAddress, { gasLimit: TX_GAS_LIMIT });
239
+ const createPairTxReq = await factoryContract.populateTransaction.createPair(tokenAAddress, tokenBAddress);
240
+ const createPairGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...createPairTxReq }, TX_GAS_FALLBACK);
241
+ const createPairTx = await factoryContract.createPair(tokenAAddress, tokenBAddress, { gasLimit: createPairGasLimit });
242
+ console.log("[5] createPair tx id:", createPairTx.hash);
243
+ txHashes.push(createPairTx.hash);
157
244
  const createPairReceipt = await createPairTx.wait(1, 600_000);
245
+ addReceiptGas(createPairReceipt);
158
246
  assert.ok(createPairReceipt, "createPair receipt must exist");
159
247
 
160
248
  // Pair address from Factory.getPair (ABI call) — source of truth
@@ -172,6 +260,7 @@ describe("QuantumSwap V2 DEX full flow", () => {
172
260
  isAddress(pairAddressFromEvent) &&
173
261
  pairAddressFromEvent !== zeroAddress32 &&
174
262
  pairAddressFromEvent !== "0x" + "0".repeat(64);
263
+ if (pairCreated) contractAddresses.PairTokenATokenB = pairAddressFromEvent;
175
264
 
176
265
  // When receipt has logs, decode PairCreated using quantumcoin.js Interface.parseLog
177
266
  const logs = createPairReceipt.logs;
@@ -240,10 +329,32 @@ describe("QuantumSwap V2 DEX full flow", () => {
240
329
  const amountAMin = 0n;
241
330
  const amountBMin = 0n;
242
331
  const approveAmount = amountADesired > amountBDesired ? amountADesired : amountBDesired;
243
- let approveA = await tokenA.approve(routerAddress, approveAmount, { gasLimit: TX_GAS_LIMIT });
244
- await approveA.wait(1, 600_000);
245
- let approveB = await tokenB.approve(routerAddress, approveAmount, { gasLimit: TX_GAS_LIMIT });
246
- await approveB.wait(1, 600_000);
332
+ const approveTxReqA = await tokenA.populateTransaction.approve(routerAddress, approveAmount);
333
+ const approveGasA = await estimateGasLimit(provider, { from: walletAddr, ...approveTxReqA }, TX_GAS_FALLBACK);
334
+ let approveA = await tokenA.approve(routerAddress, approveAmount, { gasLimit: approveGasA });
335
+ console.log("[8a] tokenA approve tx id:", approveA.hash);
336
+ txHashes.push(approveA.hash);
337
+ addReceiptGas(await approveA.wait(1, 600_000));
338
+ const approveTxReqB = await tokenB.populateTransaction.approve(routerAddress, approveAmount);
339
+ const approveGasB = await estimateGasLimit(provider, { from: walletAddr, ...approveTxReqB }, TX_GAS_FALLBACK);
340
+ let approveB = await tokenB.approve(routerAddress, approveAmount, { gasLimit: approveGasB });
341
+ console.log("[8b] tokenB approve tx id:", approveB.hash);
342
+ txHashes.push(approveB.hash);
343
+ addReceiptGas(await approveB.wait(1, 600_000));
344
+ const addLiqDeadline = await deadline(provider);
345
+ const addLiqTxReq = await routerContract.populateTransaction.addLiquidity(
346
+ tokenAAddress,
347
+ tokenBAddress,
348
+ amountADesired,
349
+ amountBDesired,
350
+ amountAMin,
351
+ amountBMin,
352
+ walletAddr,
353
+ addLiqDeadline,
354
+ );
355
+ const addLiqGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...addLiqTxReq }, TX_GAS_FALLBACK);
356
+ const tokenABalanceBeforeAddLiqRaw = await tokenA.balanceOf(walletAddr);
357
+ const tokenBBalanceBeforeAddLiqRaw = await tokenB.balanceOf(walletAddr);
247
358
  const addLiqTx = await routerContract.addLiquidity(
248
359
  tokenAAddress,
249
360
  tokenBAddress,
@@ -252,10 +363,13 @@ describe("QuantumSwap V2 DEX full flow", () => {
252
363
  amountAMin,
253
364
  amountBMin,
254
365
  walletAddr,
255
- deadline(),
256
- { gasLimit: TX_GAS_LIMIT },
366
+ addLiqDeadline,
367
+ { gasLimit: addLiqGasLimit },
257
368
  );
369
+ console.log("[8c] addLiquidity tx id:", addLiqTx.hash);
370
+ txHashes.push(addLiqTx.hash);
258
371
  const addLiqReceipt = await addLiqTx.wait(1, 600_000);
372
+ addReceiptGas(addLiqReceipt);
259
373
  assert.ok(addLiqReceipt && addLiqReceipt.status === 1, "addLiquidity must succeed");
260
374
 
261
375
  const pairContract = QuantumSwapV2Pair.connect(pairAddressFromEvent, provider);
@@ -270,28 +384,50 @@ describe("QuantumSwap V2 DEX full flow", () => {
270
384
  const swapAmountIn = parseUnits("10", 18);
271
385
  const amountOutMin = 0n;
272
386
  const pathSwap = [tokenAAddress, tokenBAddress];
273
- const approveSwap = await tokenA.approve(routerAddress, swapAmountIn, { gasLimit: TX_GAS_LIMIT });
274
- await approveSwap.wait(1, 600_000);
387
+ const approveSwapTxReq = await tokenA.populateTransaction.approve(routerAddress, swapAmountIn);
388
+ const approveSwapGas = await estimateGasLimit(provider, { from: walletAddr, ...approveSwapTxReq }, TX_GAS_FALLBACK);
389
+ const approveSwap = await tokenA.approve(routerAddress, swapAmountIn, { gasLimit: approveSwapGas });
390
+ console.log("[9a] swap approve tx id:", approveSwap.hash);
391
+ txHashes.push(approveSwap.hash);
392
+ addReceiptGas(await approveSwap.wait(1, 600_000));
393
+ const swapDeadline = await deadline(provider);
394
+ const swapTxReq = await routerContract.populateTransaction.swapExactTokensForTokens(
395
+ swapAmountIn,
396
+ amountOutMin,
397
+ pathSwap,
398
+ walletAddr,
399
+ swapDeadline,
400
+ );
401
+ const swapGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...swapTxReq }, TX_GAS_FALLBACK);
402
+ const tokenBBalanceBeforeSwapRaw = await tokenB.balanceOf(walletAddr);
403
+ const tokenBBalanceBeforeSwap = typeof tokenBBalanceBeforeSwapRaw === "bigint" ? tokenBBalanceBeforeSwapRaw : BigInt(String(tokenBBalanceBeforeSwapRaw));
275
404
  const swapTx = await routerContract.swapExactTokensForTokens(
276
405
  swapAmountIn,
277
406
  amountOutMin,
278
407
  pathSwap,
279
408
  walletAddr,
280
- deadline(),
281
- { gasLimit: TX_GAS_LIMIT },
409
+ swapDeadline,
410
+ { gasLimit: swapGasLimit },
282
411
  );
412
+ console.log("[9b] swapExactTokensForTokens tx id:", swapTx.hash);
413
+ txHashes.push(swapTx.hash);
283
414
  const swapReceipt = await swapTx.wait(1, 600_000);
415
+ addReceiptGas(swapReceipt);
284
416
  assert.ok(swapReceipt && swapReceipt.status === 1, "swapExactTokensForTokens must succeed");
285
417
  const tokenBBalanceAfterSwapRaw = await tokenB.balanceOf(walletAddr);
286
418
  const tokenBBalanceAfterSwap = typeof tokenBBalanceAfterSwapRaw === "bigint" ? tokenBBalanceAfterSwapRaw : BigInt(String(tokenBBalanceAfterSwapRaw));
287
- assert.ok(tokenBBalanceAfterSwap > tokenBBalanceBefore, "wallet TokenB balance must increase after swap");
419
+ assert.ok(tokenBBalanceAfterSwap > tokenBBalanceBeforeSwap, "wallet TokenB balance must increase after swap");
288
420
  }
289
421
 
290
422
  // --- 10) Swap ETH: wrap, add WQ/token liquidity, swapExactETHForTokens ---
291
423
  if (pairCreated) {
292
424
  const ethToWrap = parseUnits("1", 18);
293
- const depositTx = await wq.deposit({ value: ethToWrap, gasLimit: TX_GAS_LIMIT });
294
- await depositTx.wait(1, 600_000);
425
+ const depositTxReq = await wq.populateTransaction.deposit({ value: ethToWrap });
426
+ const depositGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...depositTxReq }, TX_GAS_FALLBACK);
427
+ const depositTx = await wq.deposit({ value: ethToWrap, gasLimit: depositGasLimit });
428
+ console.log("[10a] WQ deposit tx id:", depositTx.hash);
429
+ txHashes.push(depositTx.hash);
430
+ addReceiptGas(await depositTx.wait(1, 600_000));
295
431
  const wqBalanceAfterDepositRaw = await wq.balanceOf(walletAddr);
296
432
  const wqBalanceAfterDeposit = typeof wqBalanceAfterDepositRaw === "bigint" ? wqBalanceAfterDepositRaw : BigInt(String(wqBalanceAfterDepositRaw));
297
433
  assert.ok(wqBalanceAfterDeposit >= ethToWrap, "WQ balance after deposit >= ethToWrap");
@@ -306,48 +442,141 @@ describe("QuantumSwap V2 DEX full flow", () => {
306
442
  const zeroAddr = "0x0000000000000000000000000000000000000000000000000000000000000000";
307
443
  const hasWqPair = pairWqTokenAAddr && isAddress(pairWqTokenAAddr) && pairWqTokenAAddr !== zeroAddr && pairWqTokenAAddr !== "0x" + "0".repeat(64);
308
444
  if (!hasWqPair) {
309
- const createWqPairTx = await factoryContract.createPair(wqAddressNorm, tokenAAddress, { gasLimit: TX_GAS_LIMIT });
310
- await createWqPairTx.wait(1, 600_000);
445
+ const createWqPairTxReq = await factoryContract.populateTransaction.createPair(wqAddressNorm, tokenAAddress);
446
+ const createWqPairGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...createWqPairTxReq }, TX_GAS_FALLBACK);
447
+ const createWqPairTx = await factoryContract.createPair(wqAddressNorm, tokenAAddress, { gasLimit: createWqPairGasLimit });
448
+ console.log("[10b] createPair WQ/tokenA tx id:", createWqPairTx.hash);
449
+ txHashes.push(createWqPairTx.hash);
450
+ addReceiptGas(await createWqPairTx.wait(1, 600_000));
311
451
  pairWqTokenAAddr = getAddress(await factoryContract.getPair(wqAddressNorm, tokenAAddress));
312
452
  }
453
+ pairWqTokenAAddress = pairWqTokenAAddr;
454
+ if (pairWqTokenAAddress) contractAddresses.PairWqTokenA = pairWqTokenAAddress;
313
455
  const tokenForEthLiq = parseUnits("500", 18);
314
- const approveTokenAForEth = await tokenA.approve(routerAddress, tokenForEthLiq, { gasLimit: TX_GAS_LIMIT });
315
- await approveTokenAForEth.wait(1, 600_000);
456
+ const approveTokenAForEthTxReq = await tokenA.populateTransaction.approve(routerAddress, tokenForEthLiq);
457
+ const approveTokenAForEthGas = await estimateGasLimit(provider, { from: walletAddr, ...approveTokenAForEthTxReq }, TX_GAS_FALLBACK);
458
+ const approveTokenAForEth = await tokenA.approve(routerAddress, tokenForEthLiq, { gasLimit: approveTokenAForEthGas });
459
+ console.log("[10c] tokenA approve (ETH liq) tx id:", approveTokenAForEth.hash);
460
+ txHashes.push(approveTokenAForEth.hash);
461
+ addReceiptGas(await approveTokenAForEth.wait(1, 600_000));
462
+ const addLiqEthDeadline = await deadline(provider);
463
+ const addLiqEthTxReq = await routerContract.populateTransaction.addLiquidityETH(
464
+ tokenAAddress,
465
+ tokenForEthLiq,
466
+ 0n,
467
+ 0n,
468
+ walletAddr,
469
+ addLiqEthDeadline,
470
+ { value: ethToWrap },
471
+ );
472
+ const addLiqEthGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...addLiqEthTxReq }, TX_GAS_FALLBACK);
316
473
  const addLiqEthTx = await routerContract.addLiquidityETH(
317
474
  tokenAAddress,
318
475
  tokenForEthLiq,
319
476
  0n,
320
477
  0n,
321
478
  walletAddr,
322
- deadline(),
323
- { value: ethToWrap, gasLimit: TX_GAS_LIMIT },
479
+ addLiqEthDeadline,
480
+ { value: ethToWrap, gasLimit: addLiqEthGasLimit },
324
481
  );
325
- await addLiqEthTx.wait(1, 600_000);
482
+ console.log("[10d] addLiquidityETH tx id:", addLiqEthTx.hash);
483
+ txHashes.push(addLiqEthTx.hash);
484
+ addReceiptGas(await addLiqEthTx.wait(1, 600_000));
326
485
 
327
486
  const ethSwapValue = parseUnits("0.1", 18);
328
487
  const pathEthToToken = [wqAddressNorm, tokenAAddress];
488
+ const swapEthDeadline = await deadline(provider);
489
+ const swapEthTxReq = await routerContract.populateTransaction.swapExactETHForTokens(
490
+ 0n,
491
+ pathEthToToken,
492
+ walletAddr,
493
+ swapEthDeadline,
494
+ { value: ethSwapValue },
495
+ );
496
+ const swapEthGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...swapEthTxReq }, TX_GAS_FALLBACK);
497
+ const tokenABalanceBeforeEthSwapRaw = await tokenA.balanceOf(walletAddr);
498
+ const tokenABalanceBeforeEthSwap = typeof tokenABalanceBeforeEthSwapRaw === "bigint" ? tokenABalanceBeforeEthSwapRaw : BigInt(String(tokenABalanceBeforeEthSwapRaw));
329
499
  const swapEthTx = await routerContract.swapExactETHForTokens(
330
500
  0n,
331
501
  pathEthToToken,
332
502
  walletAddr,
333
- deadline(),
334
- { value: ethSwapValue, gasLimit: TX_GAS_LIMIT },
503
+ swapEthDeadline,
504
+ { value: ethSwapValue, gasLimit: swapEthGasLimit },
335
505
  );
506
+ console.log("[10e] swapExactETHForTokens tx id:", swapEthTx.hash);
507
+ txHashes.push(swapEthTx.hash);
336
508
  const swapEthReceipt = await swapEthTx.wait(1, 600_000);
509
+ addReceiptGas(swapEthReceipt);
337
510
  assert.ok(swapEthReceipt && swapEthReceipt.status === 1, "swapExactETHForTokens must succeed");
511
+ const tokenABalanceAfterEthSwapRaw = await tokenA.balanceOf(walletAddr);
512
+ const tokenABalanceAfterEthSwap = typeof tokenABalanceAfterEthSwapRaw === "bigint" ? tokenABalanceAfterEthSwapRaw : BigInt(String(tokenABalanceAfterEthSwapRaw));
513
+ assert.ok(tokenABalanceAfterEthSwap > tokenABalanceBeforeEthSwap, "wallet TokenA balance must increase after swapExactETHForTokens");
338
514
  }
339
515
 
340
- // --- 11) Account balance check: native balance ---
341
- const nativeBalanceBefore = await provider.getBalance(walletAddr);
342
- assert.ok(typeof nativeBalanceBefore === "bigint", "native balance is bigint");
343
- assert.ok(nativeBalanceBefore >= 0n, "native balance non-negative");
516
+ // --- 11) Send tokens to another wallet ---
517
+ const otherWallet = Wallet.createRandom().connect(provider);
518
+ const otherAddr = getAddress(otherWallet.address);
519
+ assert.ok(isAddress(otherAddr), "other wallet address must be valid");
520
+ contractAddresses.OtherWallet = otherAddr;
521
+
522
+ const transferAmountA = parseUnits("100", 18);
523
+ const transferAmountB = parseUnits("100", 18);
524
+ const transferATxReq = await tokenA.populateTransaction.transfer(otherAddr, transferAmountA);
525
+ const transferAGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...transferATxReq }, TX_GAS_FALLBACK);
526
+ const transferATx = await tokenA.transfer(otherAddr, transferAmountA, { gasLimit: transferAGasLimit });
527
+ console.log("[11a] tokenA transfer to other wallet tx id:", transferATx.hash);
528
+ txHashes.push(transferATx.hash);
529
+ addReceiptGas(await transferATx.wait(1, 600_000));
530
+
531
+ const transferBTxReq = await tokenB.populateTransaction.transfer(otherAddr, transferAmountB);
532
+ const transferBGasLimit = await estimateGasLimit(provider, { from: walletAddr, ...transferBTxReq }, TX_GAS_FALLBACK);
533
+ const transferBTx = await tokenB.transfer(otherAddr, transferAmountB, { gasLimit: transferBGasLimit });
534
+ console.log("[11b] tokenB transfer to other wallet tx id:", transferBTx.hash);
535
+ txHashes.push(transferBTx.hash);
536
+ addReceiptGas(await transferBTx.wait(1, 600_000));
537
+
538
+ const otherTokenABalanceRaw = await tokenA.balanceOf(otherAddr);
539
+ const otherTokenBBalanceRaw = await tokenB.balanceOf(otherAddr);
540
+ const otherTokenABalance = typeof otherTokenABalanceRaw === "bigint" ? otherTokenABalanceRaw : BigInt(String(otherTokenABalanceRaw));
541
+ const otherTokenBBalance = typeof otherTokenBBalanceRaw === "bigint" ? otherTokenBBalanceRaw : BigInt(String(otherTokenBBalanceRaw));
542
+ assert.equal(otherTokenABalance, transferAmountA, "other wallet must have received TokenA");
543
+ assert.equal(otherTokenBBalance, transferAmountB, "other wallet must have received TokenB");
544
+
545
+ // --- 12) Account balance check: native balance (before = nativeBalanceBeforeAllTxs at start) ---
546
+ const nativeBalanceAfter = await provider.getBalance(walletAddr);
547
+ assert.ok(typeof nativeBalanceAfter === "bigint", "native balance is bigint");
548
+ assert.ok(nativeBalanceAfter >= 0n, "native balance non-negative");
549
+ assert.ok(nativeBalanceAfter <= nativeBalanceBeforeAllTxs, "native balance decreased or unchanged after txs");
344
550
 
345
551
  const wqBalanceWallet = await wq.balanceOf(walletAddr);
346
552
  assert.ok(typeof wqBalanceWallet === "bigint", "WQ balance is bigint");
347
553
 
554
+ // --- Summary: contract addresses and transaction hashes ---
555
+ reportDexFlow(contractAddresses, txHashes);
556
+ console.log("========== TOTAL GAS USED (cumulative) ==========");
557
+ console.log(` ${totalGasUsed.toString()} (from ${txHashes.length} transaction receipts)`);
558
+ console.log("========================================\n");
559
+
560
+ // --- Test wallet token balances (after all steps) ---
561
+ const walletTokenABalanceRaw = await tokenA.balanceOf(walletAddr);
562
+ const walletTokenBBalanceRaw = await tokenB.balanceOf(walletAddr);
563
+ const walletTokenABalance = typeof walletTokenABalanceRaw === "bigint" ? walletTokenABalanceRaw : BigInt(String(walletTokenABalanceRaw));
564
+ const walletTokenBBalance = typeof walletTokenBBalanceRaw === "bigint" ? walletTokenBBalanceRaw : BigInt(String(walletTokenBBalanceRaw));
565
+ console.log("========== TEST WALLET TOKEN BALANCES ==========");
566
+ console.log(` Wallet: ${walletAddr}`);
567
+ console.log(` TokenA (BigCat) balance: ${walletTokenABalance.toString()} (${formatUnits(walletTokenABalance, 18)} formatted)`);
568
+ console.log(` TokenB (SmallDog) balance: ${walletTokenBBalance.toString()} (${formatUnits(walletTokenBBalance, 18)} formatted)`);
569
+ console.log("================================================\n");
570
+
348
571
  // Summary assertions
349
572
  assert.ok(isAddress(routerAddress) && isAddress(factoryAddress) && isAddress(wqAddress), "all core addresses valid");
350
573
  assert.ok(isAddress(tokenAAddress) && isAddress(tokenBAddress), "token addresses valid");
351
574
  assert.ok(!pairCreated || isAddress(pairAddressFromEvent), "pair address valid when pair created");
575
+ } catch (err) {
576
+ if (Object.keys(contractAddresses).length > 0 || txHashes.length > 0) {
577
+ reportDexFlow(contractAddresses, txHashes);
578
+ }
579
+ throw err;
580
+ }
352
581
  }, { timeout: 300_000 });
353
582
  });