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.
- package/README.md +73 -53
- package/examples/README.md +46 -0
- package/examples/deploy-IERC20.js +5 -3
- package/examples/deploy-QuantumSwapV2ERC20.js +5 -3
- package/examples/deploy-QuantumSwapV2Factory.js +5 -3
- package/examples/deploy-QuantumSwapV2Pair.js +5 -3
- package/examples/deploy-QuantumSwapV2Router02.js +5 -3
- package/examples/deploy-WQ.js +5 -3
- package/examples/events-IERC20.js +5 -3
- package/examples/events-QuantumSwapV2ERC20.js +5 -3
- package/examples/events-QuantumSwapV2Factory.js +5 -3
- package/examples/events-QuantumSwapV2Pair.js +5 -3
- package/examples/events-QuantumSwapV2Router02.js +5 -3
- package/examples/events-WQ.js +5 -3
- package/examples/offline-signing-IERC20.js +5 -3
- package/examples/offline-signing-QuantumSwapV2ERC20.js +5 -3
- package/examples/offline-signing-QuantumSwapV2Factory.js +5 -3
- package/examples/offline-signing-QuantumSwapV2Pair.js +5 -3
- package/examples/offline-signing-QuantumSwapV2Router02.js +5 -3
- package/examples/offline-signing-WQ.js +5 -3
- package/examples/package-lock.json +62 -0
- package/examples/package.json +14 -0
- package/examples/read-operations-IERC20.js +5 -3
- package/examples/read-operations-QuantumSwapV2ERC20.js +5 -3
- package/examples/read-operations-QuantumSwapV2Factory.js +5 -3
- package/examples/read-operations-QuantumSwapV2Pair.js +5 -3
- package/examples/read-operations-QuantumSwapV2Router02.js +5 -3
- package/examples/read-operations-WQ.js +5 -3
- package/examples/run-dex-flow-custom.js +493 -0
- package/examples/run-dex-flow-custom.ts +556 -0
- package/examples/write-operations-IERC20.js +5 -3
- package/examples/write-operations-QuantumSwapV2ERC20.js +5 -3
- package/examples/write-operations-QuantumSwapV2Factory.js +5 -3
- package/examples/write-operations-QuantumSwapV2Pair.js +5 -3
- package/examples/write-operations-QuantumSwapV2Router02.js +5 -3
- package/examples/write-operations-WQ.js +5 -3
- package/package.json +8 -9
- package/test/e2e/IERC20.e2e.test.js +2 -2
- package/test/e2e/QuantumSwapV2ERC20.e2e.test.js +2 -2
- package/test/e2e/QuantumSwapV2Factory.e2e.test.js +2 -2
- package/test/e2e/QuantumSwapV2Pair.e2e.test.js +2 -2
- package/test/e2e/QuantumSwapV2Router02.e2e.test.js +2 -2
- package/test/e2e/WQ.e2e.test.js +2 -2
- package/test/e2e/all-contracts.e2e.test.js +103 -72
- package/test/e2e/dex-full-flow.e2e.test.js +276 -47
- package/examples/walkthrough-dex-full-flow.js +0 -226
- 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
|
-
|
|
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
|
|
61
|
-
const
|
|
62
|
-
const
|
|
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
|
-
|
|
65
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
112
|
-
|
|
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
|
-
|
|
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
|
|
134
|
-
await
|
|
135
|
-
|
|
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
|
|
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
|
-
|
|
244
|
-
await
|
|
245
|
-
let
|
|
246
|
-
|
|
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
|
-
|
|
256
|
-
{ gasLimit:
|
|
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
|
|
274
|
-
await
|
|
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
|
-
|
|
281
|
-
{ gasLimit:
|
|
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 >
|
|
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
|
|
294
|
-
await
|
|
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
|
|
310
|
-
await
|
|
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
|
|
315
|
-
await
|
|
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
|
-
|
|
323
|
-
{ value: ethToWrap, gasLimit:
|
|
479
|
+
addLiqEthDeadline,
|
|
480
|
+
{ value: ethToWrap, gasLimit: addLiqEthGasLimit },
|
|
324
481
|
);
|
|
325
|
-
|
|
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
|
-
|
|
334
|
-
{ value: ethSwapValue, gasLimit:
|
|
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)
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
assert.ok(
|
|
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
|
});
|