quantumcoin 7.0.2 → 7.0.4

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 (155) hide show
  1. package/.github/workflows/publish-npmjs.yaml +22 -22
  2. package/.gitignore +15 -15
  3. package/LICENSE +21 -21
  4. package/README-SDK.md +756 -756
  5. package/README.md +165 -152
  6. package/SPEC.md +3845 -3845
  7. package/config.d.ts +50 -50
  8. package/config.js +115 -115
  9. package/examples/AllSolidityTypes.sol +184 -184
  10. package/examples/SimpleIERC20.sol +74 -74
  11. package/examples/events.js +41 -35
  12. package/examples/events.ts +35 -0
  13. package/examples/example-generator-sdk-js.js +100 -95
  14. package/examples/example-generator-sdk-js.ts +77 -0
  15. package/examples/example-generator-sdk-ts.js +100 -95
  16. package/examples/example-generator-sdk-ts.ts +77 -0
  17. package/examples/example.js +72 -61
  18. package/examples/example.ts +61 -0
  19. package/examples/offline-signing.js +79 -0
  20. package/examples/offline-signing.ts +66 -0
  21. package/examples/package-lock.json +48 -57
  22. package/examples/package.json +32 -16
  23. package/examples/read-operations.js +32 -27
  24. package/examples/read-operations.ts +31 -0
  25. package/examples/sdk-generator-erc20.inline.json +251 -251
  26. package/examples/solidity-types.ts +43 -43
  27. package/examples/wallet-offline.js +35 -29
  28. package/examples/wallet-offline.ts +34 -0
  29. package/generate-sdk.js +1824 -1383
  30. package/index.js +12 -12
  31. package/package.json +95 -75
  32. package/scripts/copy-declarations.js +31 -0
  33. package/scripts/run-all-one-by-one.js +151 -0
  34. package/src/abi/fragments.d.ts +42 -42
  35. package/src/abi/fragments.js +63 -63
  36. package/src/abi/index.d.ts +13 -13
  37. package/src/abi/index.js +9 -9
  38. package/src/abi/interface.d.ts +128 -132
  39. package/src/abi/interface.js +590 -590
  40. package/src/abi/js-abi-coder.d.ts +8 -0
  41. package/src/abi/js-abi-coder.js +474 -474
  42. package/src/constants.d.ts +66 -61
  43. package/src/constants.js +101 -94
  44. package/src/contract/contract-factory.d.ts +28 -28
  45. package/src/contract/contract-factory.js +105 -105
  46. package/src/contract/contract.d.ts +113 -105
  47. package/src/contract/contract.js +354 -312
  48. package/src/contract/index.d.ts +9 -9
  49. package/src/contract/index.js +9 -9
  50. package/src/errors/index.d.ts +92 -92
  51. package/src/errors/index.js +188 -188
  52. package/src/generator/index.d.ts +74 -0
  53. package/src/generator/index.js +1404 -1201
  54. package/src/index.d.ts +125 -127
  55. package/src/index.js +41 -41
  56. package/src/internal/hex.d.ts +61 -61
  57. package/src/internal/hex.js +144 -144
  58. package/src/providers/extra-providers.d.ts +139 -128
  59. package/src/providers/extra-providers.js +600 -575
  60. package/src/providers/index.d.ts +17 -16
  61. package/src/providers/index.js +10 -10
  62. package/src/providers/json-rpc-provider.d.ts +12 -12
  63. package/src/providers/json-rpc-provider.js +79 -79
  64. package/src/providers/provider.d.ts +207 -196
  65. package/src/providers/provider.js +392 -359
  66. package/src/types/index.d.ts +214 -462
  67. package/src/types/index.js +9 -9
  68. package/src/utils/address.d.ts +72 -72
  69. package/src/utils/address.js +181 -182
  70. package/src/utils/encoding.d.ts +120 -120
  71. package/src/utils/encoding.js +306 -306
  72. package/src/utils/hashing.d.ts +82 -76
  73. package/src/utils/hashing.js +313 -298
  74. package/src/utils/index.d.ts +65 -55
  75. package/src/utils/index.js +13 -13
  76. package/src/utils/result.d.ts +57 -57
  77. package/src/utils/result.js +128 -128
  78. package/src/utils/rlp.d.ts +12 -12
  79. package/src/utils/rlp.js +200 -200
  80. package/src/utils/units.d.ts +29 -29
  81. package/src/utils/units.js +107 -107
  82. package/src/wallet/index.d.ts +10 -10
  83. package/src/wallet/index.js +8 -8
  84. package/src/wallet/wallet.d.ts +160 -160
  85. package/src/wallet/wallet.js +483 -489
  86. package/test/e2e/all-solidity-types.dynamic.test.js +207 -200
  87. package/test/e2e/all-solidity-types.dynamic.test.ts +191 -0
  88. package/test/e2e/all-solidity-types.fixtures.js +231 -231
  89. package/test/e2e/all-solidity-types.generated-sdks.e2e.test.js +387 -361
  90. package/test/e2e/all-solidity-types.generated-sdks.e2e.test.ts +350 -0
  91. package/test/e2e/helpers.js +59 -47
  92. package/test/e2e/signing-context-and-fee.e2e.test.js +137 -0
  93. package/test/e2e/signing-context-and-fee.e2e.test.ts +128 -0
  94. package/test/e2e/simple-erc20.generated-sdks.e2e.test.js +168 -144
  95. package/test/e2e/simple-erc20.generated-sdks.e2e.test.ts +141 -0
  96. package/test/e2e/transactional.test.js +245 -191
  97. package/test/e2e/transactional.test.ts +208 -0
  98. package/test/e2e/typed-generator.e2e.test.js +407 -402
  99. package/test/e2e/typed-generator.e2e.test.ts +337 -0
  100. package/test/fixtures/ConstructorParam.sol +23 -23
  101. package/test/fixtures/MultiContracts.sol +37 -37
  102. package/test/fixtures/SimpleStorage.sol +18 -18
  103. package/test/fixtures/StakingContract.abi.json +1 -1
  104. package/test/integration/ipc-provider.test.js +49 -44
  105. package/test/integration/ipc-provider.test.ts +44 -0
  106. package/test/integration/provider.test.js +88 -72
  107. package/test/integration/provider.test.ts +85 -0
  108. package/test/integration/ws-provider.test.js +41 -33
  109. package/test/integration/ws-provider.test.ts +38 -0
  110. package/test/security/malformed-input.test.js +37 -31
  111. package/test/security/malformed-input.test.ts +35 -0
  112. package/test/unit/_encrypted-output.txt +6 -0
  113. package/test/unit/_log-encrypted-jsons.js +45 -0
  114. package/test/unit/_write-keystore-fixture.js +16 -0
  115. package/test/unit/abi-interface.test.js +103 -98
  116. package/test/unit/abi-interface.test.ts +102 -0
  117. package/test/unit/address-wallet.test.js +355 -257
  118. package/test/unit/address-wallet.test.ts +342 -0
  119. package/test/unit/browser-provider.test.js +85 -82
  120. package/test/unit/browser-provider.test.ts +79 -0
  121. package/test/unit/contract.test.js +85 -82
  122. package/test/unit/contract.test.ts +83 -0
  123. package/test/unit/encoding-units-rlp.test.js +92 -89
  124. package/test/unit/encoding-units-rlp.test.ts +91 -0
  125. package/test/unit/errors.test.js +77 -74
  126. package/test/unit/errors.test.ts +76 -0
  127. package/test/unit/filter-by-blockhash.test.js +55 -52
  128. package/test/unit/filter-by-blockhash.test.ts +54 -0
  129. package/test/unit/fixtures/encrypted-keystores-48-32-36.js +9 -0
  130. package/test/unit/generate-contract-cli.test.js +42 -39
  131. package/test/unit/generate-contract-cli.test.ts +41 -0
  132. package/test/unit/generate-sdk-artifacts-json.test.js +113 -110
  133. package/test/unit/generate-sdk-artifacts-json.test.ts +110 -0
  134. package/test/unit/generator.test.js +102 -98
  135. package/test/unit/generator.test.ts +101 -0
  136. package/test/unit/hashing.test.js +68 -54
  137. package/test/unit/hashing.test.ts +67 -0
  138. package/test/unit/init.test.js +39 -36
  139. package/test/unit/init.test.ts +38 -0
  140. package/test/unit/interface.test.js +56 -53
  141. package/test/unit/interface.test.ts +54 -0
  142. package/test/unit/internal-hex.test.js +50 -47
  143. package/test/unit/internal-hex.test.ts +49 -0
  144. package/test/unit/populate-transaction.test.js +65 -0
  145. package/test/unit/populate-transaction.test.ts +64 -0
  146. package/test/unit/providers.test.js +200 -144
  147. package/test/unit/providers.test.ts +196 -0
  148. package/test/unit/result.test.js +80 -77
  149. package/test/unit/result.test.ts +79 -0
  150. package/test/unit/solidity-types.test.js +49 -46
  151. package/test/unit/solidity-types.test.ts +39 -0
  152. package/test/unit/utils.test.js +57 -54
  153. package/test/unit/utils.test.ts +56 -0
  154. package/test/verbose-logger.js +74 -0
  155. package/tsconfig.build.json +14 -0
@@ -1,191 +1,245 @@
1
- /**
2
- * @testCategory e2e
3
- * @blockchainRequired write
4
- * @transactional true
5
- * @description Transactional tests (send coins, deploy contract, invoke contract) against a JSON-RPC endpoint.
6
- *
7
- * IMPORTANT / WARNING:
8
- * - This file contains a HARDCODED TEST WALLET (encrypted JSON + passphrase).
9
- * - It is assumed to have funds on the target network for testing.
10
- * - NEVER reuse this wallet for real funds or production usage.
11
- * - Running these tests will broadcast REAL transactions and WILL change chain state.
12
- *
13
- * RPC endpoint parameterization:
14
- * - Provide the JSON-RPC URL via `QC_RPC_URL` env var OR `--rpc=<url>` argument.
15
- * - Example (PowerShell):
16
- * `$env:QC_RPC_URL = "https://public.rpc.quantumcoinapi.com"; npm run test:e2e`
17
- */
18
-
19
- const { describe, it } = require("node:test");
20
- const assert = require("node:assert/strict");
21
- const path = require("node:path");
22
- const os = require("node:os");
23
- const fs = require("node:fs");
24
- const { execFileSync } = require("node:child_process");
25
-
26
- const qc = require("../../index");
27
- const { Initialize } = require("../../config");
28
- const { getRpcUrl, getChainId, getSolcPath, assertSolcExists } = require("./helpers");
29
-
30
- // ---------------------------------------------------------------------------
31
- // Hardcoded funded test wallet (from upstream SDK examples)
32
- // ---------------------------------------------------------------------------
33
- // NOTE: This wallet is used in quantum-coin-js-sdk examples and is intended ONLY for testing.
34
- const TEST_WALLET_ENCRYPTED_JSON =
35
- "{\"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}";
36
- const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
37
-
38
- // Gas assumption per user requirements:
39
- // "Assume 1000 coins needed for 21000 units of gas."
40
- const ONE_COIN_WEI = 1000000000000000000n;
41
- const FIXED_COINS_PER_21000_GAS = 1000n;
42
- const FIXED_GAS_UNITS = 21000n;
43
- const FIXED_GAS_PRICE_WEI = (FIXED_COINS_PER_21000_GAS * ONE_COIN_WEI) / FIXED_GAS_UNITS;
44
-
45
- function parseHexToBigInt(hex) {
46
- return BigInt(hex);
47
- }
48
-
49
- function compileSimpleStorage(solcPath) {
50
- const srcPath = path.join(__dirname, "..", "fixtures", "SimpleStorage.sol");
51
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "qc-solc-"));
52
- const out = execFileSync(solcPath, ["--optimize", "--combined-json", "abi,bin", srcPath], { encoding: "utf8" });
53
-
54
- const json = JSON.parse(out);
55
- const contracts = json.contracts || {};
56
- const key = Object.keys(contracts).find((k) => k.toLowerCase().includes("simplestorage"));
57
- if (!key) throw new Error("solc output missing SimpleStorage contract");
58
-
59
- const abi = JSON.parse(contracts[key].abi);
60
- const bin = contracts[key].bin;
61
- if (!bin) throw new Error("solc output missing bytecode");
62
- return { abi, bytecode: "0x" + bin };
63
- }
64
-
65
- describe("Transactional E2E (write)", () => {
66
- it("sends coins and validates balances + receipt", async (t) => {
67
- const rpcUrl = getRpcUrl();
68
- if (!rpcUrl) {
69
- t.skip("QC_RPC_URL (or --rpc) not provided");
70
- return;
71
- }
72
-
73
- const chainId = getChainId();
74
- await Initialize(null);
75
-
76
- const provider = new qc.JsonRpcProvider(rpcUrl, chainId);
77
- const wallet = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
78
-
79
- // Recipient: random wallet (unfunded) so we can assert delta precisely.
80
- const recipient = qc.Wallet.createRandom();
81
-
82
- const sendValue = qc.parseEther("1.0");
83
-
84
- const senderBefore = await provider.getBalance(wallet.address);
85
- const recipientBefore = await provider.getBalance(recipient.address);
86
- const nonceBefore = await provider.getTransactionCount(wallet.address, "latest");
87
-
88
- // GasLimit is the standard transfer gas.
89
- const gasLimit = 21000;
90
- const tx = await wallet.sendTransaction({
91
- to: recipient.address,
92
- value: sendValue,
93
- gasLimit,
94
- remarks: null,
95
- });
96
-
97
- const receipt = await tx.wait(1, 600_000);
98
- assert.ok(receipt);
99
- assert.ok(receipt.blockNumber != null);
100
-
101
- const senderAfter = await provider.getBalance(wallet.address);
102
- const recipientAfter = await provider.getBalance(recipient.address);
103
- const nonceAfter = await provider.getTransactionCount(wallet.address, "latest");
104
-
105
- // Receiver must increase exactly by the sent value.
106
- assert.equal(recipientAfter - recipientBefore, sendValue);
107
-
108
- // Sender must decrease by at least value + fee (fee is chain-defined).
109
- const senderDelta = senderBefore - senderAfter;
110
- assert.ok(senderDelta >= sendValue);
111
-
112
- // Fee sanity check using fixed gas-price assumption.
113
- // feePaid ~= gasLimit * fixedGasPrice
114
- const expectedFee = BigInt(gasLimit) * FIXED_GAS_PRICE_WEI;
115
- const feePaid = senderDelta - sendValue;
116
- // Allow a wide tolerance (network/client may differ), but keep it bounded.
117
- assert.ok(feePaid > 0n);
118
- assert.ok(feePaid <= expectedFee * 2n);
119
-
120
- // Nonce should increment by at least 1.
121
- assert.ok(nonceAfter >= nonceBefore + 1);
122
- }, { timeout: 900_000 });
123
-
124
- it("deploys a contract (solc) and validates code + storage mutation", async (t) => {
125
- const rpcUrl = getRpcUrl();
126
- if (!rpcUrl) {
127
- t.skip("QC_RPC_URL (or --rpc) not provided");
128
- return;
129
- }
130
-
131
- const chainId = getChainId();
132
- const solcPath = getSolcPath();
133
- assertSolcExists(solcPath);
134
-
135
- await Initialize(null);
136
- const provider = new qc.JsonRpcProvider(rpcUrl, chainId);
137
- const wallet = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
138
-
139
- const { abi, bytecode } = compileSimpleStorage(solcPath);
140
-
141
- const deployNonce = await provider.getTransactionCount(wallet.address, "latest");
142
- const expectedAddress = qc.getCreateAddress({ from: wallet.address, nonce: deployNonce });
143
-
144
- // Use ContractFactory's deploy tx builder (uses quantum-coin-js-sdk packCreateContractData).
145
- const factory = new qc.ContractFactory(abi, bytecode, wallet);
146
- const deployTxReq = factory.getDeployTransaction();
147
-
148
- // Estimate gas and add some buffer.
149
- let gasLimit = 500000;
150
- try {
151
- const est = await provider.estimateGas({ from: wallet.address, data: deployTxReq.data });
152
- gasLimit = Number(est + 50_000n);
153
- } catch {
154
- // keep fallback
155
- }
156
-
157
- const deployTx = await wallet.sendTransaction({
158
- ...deployTxReq,
159
- nonce: deployNonce,
160
- gasLimit,
161
- value: 0n,
162
- });
163
-
164
- const receipt = await deployTx.wait(1, 300_000);
165
- assert.ok(receipt);
166
- if (receipt.contractAddress) {
167
- assert.equal(receipt.contractAddress.toLowerCase(), expectedAddress.toLowerCase());
168
- }
169
-
170
- const code = await provider.getCode(expectedAddress, "latest");
171
- assert.ok(typeof code === "string");
172
- assert.ok(code.length > 2); // "0x" means no code
173
-
174
- // Interact with the deployed contract.
175
- const contract = new qc.Contract(expectedAddress, abi, wallet);
176
-
177
- const before = await contract.get();
178
- const beforeValue = Array.isArray(before) ? before[0] : before;
179
- assert.ok(beforeValue == 0 || beforeValue === "0" || beforeValue === 0n);
180
-
181
- const setTx = await contract.set(42n, { gasLimit: 200000 });
182
- await setTx.wait(1, 600_000);
183
-
184
- const after = await contract.get();
185
- const afterValue = Array.isArray(after) ? after[0] : after;
186
- // qcsdk decode may return string/number; normalize to BigInt for comparison
187
- const afterBI = typeof afterValue === "bigint" ? afterValue : BigInt(afterValue);
188
- assert.equal(afterBI, 42n);
189
- }, { timeout: 600_000 });
190
- });
191
-
1
+ /**
2
+ * @testCategory e2e
3
+ * @blockchainRequired write
4
+ * @transactional true
5
+ * @description Transactional tests (send coins, deploy contract, invoke contract) against a JSON-RPC endpoint.
6
+ *
7
+ * IMPORTANT / WARNING:
8
+ * - This file contains a HARDCODED TEST WALLET (encrypted JSON + passphrase).
9
+ * - It is assumed to have funds on the target network for testing.
10
+ * - NEVER reuse this wallet for real funds or production usage.
11
+ * - Running these tests will broadcast REAL transactions and WILL change chain state.
12
+ *
13
+ * RPC endpoint parameterization:
14
+ * - Provide the JSON-RPC URL via `QC_RPC_URL` env var OR `--rpc=<url>` argument.
15
+ * - Example (PowerShell):
16
+ * `$env:QC_RPC_URL = "https://public.rpc.quantumcoinapi.com"; npm run test:e2e`
17
+ */
18
+
19
+ const { describe, it } = require("node:test");
20
+ const assert = require("node:assert/strict");
21
+ const path = require("node:path");
22
+ const os = require("node:os");
23
+ const fs = require("node:fs");
24
+ const { execFileSync } = require("node:child_process");
25
+
26
+ const qc = require("../../index");
27
+ const { Initialize } = require("../../config");
28
+ const { getRpcUrl, getChainId, getSolcPath, assertSolcExists, logE2eConfig } = require("./helpers");
29
+ const { logSuite, logTest, logTxn, logAddress } = require("../verbose-logger");
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Hardcoded funded test wallet (from upstream SDK examples)
33
+ // ---------------------------------------------------------------------------
34
+ // NOTE: This wallet is used in quantum-coin-js-sdk examples and is intended ONLY for testing.
35
+ const TEST_WALLET_ENCRYPTED_JSON =
36
+ "{\"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}";
37
+ const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
38
+
39
+ // Gas assumption per user requirements:
40
+ // "Assume 1000 coins needed for 21000 units of gas."
41
+ const ONE_COIN_WEI = 1000000000000000000n;
42
+ const FIXED_COINS_PER_21000_GAS = 1000n;
43
+ const FIXED_GAS_UNITS = 21000n;
44
+ const FIXED_GAS_PRICE_WEI = (FIXED_COINS_PER_21000_GAS * ONE_COIN_WEI) / FIXED_GAS_UNITS;
45
+
46
+ function parseHexToBigInt(hex) {
47
+ return BigInt(hex);
48
+ }
49
+
50
+ function compileSimpleStorage(solcPath) {
51
+ const srcPath = path.join(__dirname, "..", "fixtures", "SimpleStorage.sol");
52
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "qc-solc-"));
53
+ const out = execFileSync(solcPath, ["--optimize", "--combined-json", "abi,bin", srcPath], { encoding: "utf8" });
54
+
55
+ const json = JSON.parse(out);
56
+ const contracts = json.contracts || {};
57
+ const key = Object.keys(contracts).find((k) => k.toLowerCase().includes("simplestorage"));
58
+ if (!key) throw new Error("solc output missing SimpleStorage contract");
59
+
60
+ const abi = JSON.parse(contracts[key].abi);
61
+ const bin = contracts[key].bin;
62
+ if (!bin) throw new Error("solc output missing bytecode");
63
+ return { abi, bytecode: "0x" + bin };
64
+ }
65
+
66
+ describe("Transactional E2E (write)", () => {
67
+ it("sends coins and validates balances + receipt", async (t) => {
68
+ logSuite("Transactional E2E (write)");
69
+ logTest("sends coins and validates balances + receipt", {});
70
+ const rpcUrl = getRpcUrl();
71
+ if (!rpcUrl) {
72
+ t.skip("QC_RPC_URL (or --rpc) not provided");
73
+ return;
74
+ }
75
+ logE2eConfig();
76
+
77
+ const chainId = getChainId();
78
+ await Initialize(null);
79
+
80
+ const provider = qc.getProvider(rpcUrl, chainId);
81
+ const wallet = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
82
+ logAddress("sender", wallet.address);
83
+
84
+ // Recipient: random wallet (unfunded) so we can assert delta precisely.
85
+ const recipient = qc.Wallet.createRandom();
86
+ logAddress("recipient", recipient.address);
87
+
88
+ const sendValue = qc.parseEther("1.0");
89
+
90
+ const senderBefore = await provider.getBalance(wallet.address);
91
+ const recipientBefore = await provider.getBalance(recipient.address);
92
+ const nonceBefore = await provider.getTransactionCount(wallet.address, "latest");
93
+
94
+ // GasLimit is the standard transfer gas.
95
+ const gasLimit = 21000;
96
+ const tx = await wallet.sendTransaction({
97
+ to: recipient.address,
98
+ value: sendValue,
99
+ gasLimit,
100
+ remarks: null,
101
+ });
102
+ logTxn(tx.hash, { from: wallet.address, to: recipient.address, value: sendValue.toString() });
103
+
104
+ const receipt = await tx.wait(1, 600_000);
105
+ logTxn(tx.hash, { blockNumber: receipt.blockNumber, status: receipt.status });
106
+ assert.ok(receipt);
107
+ assert.ok(receipt.blockNumber != null);
108
+
109
+ const senderAfter = await provider.getBalance(wallet.address);
110
+ const recipientAfter = await provider.getBalance(recipient.address);
111
+ const nonceAfter = await provider.getTransactionCount(wallet.address, "latest");
112
+
113
+ // Receiver must increase exactly by the sent value.
114
+ assert.equal(recipientAfter - recipientBefore, sendValue);
115
+
116
+ // Sender must decrease by at least value + fee (fee is chain-defined).
117
+ const senderDelta = senderBefore - senderAfter;
118
+ assert.ok(senderDelta >= sendValue);
119
+
120
+ // Fee sanity check using fixed gas-price assumption.
121
+ // feePaid ~= gasLimit * fixedGasPrice
122
+ const expectedFee = BigInt(gasLimit) * FIXED_GAS_PRICE_WEI;
123
+ const feePaid = senderDelta - sendValue;
124
+ // Allow a wide tolerance (network/client gas price may differ significantly).
125
+ assert.ok(feePaid > 0n);
126
+ assert.ok(feePaid <= expectedFee * 20n);
127
+
128
+ // Nonce should increment by at least 1.
129
+ assert.ok(nonceAfter >= nonceBefore + 1);
130
+ }, { timeout: 900_000 });
131
+
132
+ it("sends coins with remarks and verifies remarks on read-back", async (t) => {
133
+ logSuite("Transactional E2E (write)");
134
+ logTest("sends coins with remarks and verifies remarks on read-back", {});
135
+ const rpcUrl = getRpcUrl();
136
+ if (!rpcUrl) {
137
+ t.skip("QC_RPC_URL (or --rpc) not provided");
138
+ return;
139
+ }
140
+ logE2eConfig();
141
+
142
+ const chainId = getChainId();
143
+ await Initialize(null);
144
+
145
+ const provider = qc.getProvider(rpcUrl, chainId);
146
+ const wallet = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
147
+ const recipient = qc.Wallet.createRandom();
148
+
149
+ const remarksText = "hello world";
150
+ const remarksHex = qc.hexlify(qc.toUtf8Bytes(remarksText));
151
+ const sendValue = qc.parseEther("0.01");
152
+ const gasLimit = 21000;
153
+
154
+ const tx = await wallet.sendTransaction({
155
+ to: recipient.address,
156
+ value: sendValue,
157
+ gasLimit,
158
+ remarks: remarksHex,
159
+ });
160
+ logTxn(tx.hash, { from: wallet.address, to: recipient.address, remarks: remarksText });
161
+
162
+ const receipt = await tx.wait(1, 600_000);
163
+ assert.ok(receipt);
164
+ assert.ok(receipt.blockNumber != null);
165
+
166
+ const readBack = await provider.getTransaction(tx.hash);
167
+ assert.ok(readBack, "getTransaction must return the transaction");
168
+ assert.equal(readBack.remarks, remarksHex, "remarks on chain must match what was set");
169
+ }, { timeout: 900_000 });
170
+
171
+ it("deploys a contract (solc) and validates code + storage mutation", async (t) => {
172
+ logTest("deploys a contract (solc) and validates code + storage mutation", {});
173
+ const rpcUrl = getRpcUrl();
174
+ if (!rpcUrl) {
175
+ t.skip("QC_RPC_URL (or --rpc) not provided");
176
+ return;
177
+ }
178
+ logE2eConfig();
179
+
180
+ const chainId = getChainId();
181
+ const solcPath = getSolcPath();
182
+ assertSolcExists(solcPath);
183
+
184
+ await Initialize(null);
185
+ const provider = qc.getProvider(rpcUrl, chainId);
186
+ const wallet = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
187
+ logAddress("deployer", wallet.address);
188
+
189
+ const { abi, bytecode } = compileSimpleStorage(solcPath);
190
+
191
+ const deployNonce = await provider.getTransactionCount(wallet.address, "latest");
192
+ const expectedAddress = qc.getCreateAddress({ from: wallet.address, nonce: deployNonce });
193
+ logAddress("expected_contract", expectedAddress);
194
+
195
+ // Use ContractFactory's deploy tx builder (uses quantum-coin-js-sdk packCreateContractData).
196
+ const factory = new qc.ContractFactory(abi, bytecode, wallet);
197
+ const deployTxReq = factory.getDeployTransaction();
198
+
199
+ // Estimate gas and add some buffer.
200
+ let gasLimit = 500000;
201
+ try {
202
+ const est = await provider.estimateGas({ from: wallet.address, data: deployTxReq.data });
203
+ gasLimit = Number(est + 50_000n);
204
+ } catch {
205
+ // keep fallback
206
+ }
207
+
208
+ const deployTx = await wallet.sendTransaction({
209
+ ...deployTxReq,
210
+ nonce: deployNonce,
211
+ gasLimit,
212
+ value: 0n,
213
+ });
214
+ logTxn(deployTx.hash, { type: "deploy", expectedAddress });
215
+
216
+ const receipt = await deployTx.wait(1, 600_000);
217
+ logTxn(deployTx.hash, { blockNumber: receipt.blockNumber, contractAddress: receipt.contractAddress });
218
+ assert.ok(receipt);
219
+ if (receipt.contractAddress) {
220
+ assert.equal(receipt.contractAddress.toLowerCase(), expectedAddress.toLowerCase());
221
+ }
222
+
223
+ const code = await provider.getCode(expectedAddress, "latest");
224
+ assert.ok(typeof code === "string");
225
+ assert.ok(code.length > 2); // "0x" means no code
226
+
227
+ // Interact with the deployed contract.
228
+ const contract = new qc.Contract(expectedAddress, abi, wallet);
229
+
230
+ const before = await contract.get();
231
+ const beforeValue = Array.isArray(before) ? before[0] : before;
232
+ assert.ok(beforeValue == 0 || beforeValue === "0" || beforeValue === 0n);
233
+
234
+ const setTx = await contract.set(42n, { gasLimit: 200000 });
235
+ logTxn(setTx.hash, { contractAddress: expectedAddress });
236
+ await setTx.wait(1, 600_000);
237
+
238
+ const after = await contract.get();
239
+ const afterValue = Array.isArray(after) ? after[0] : after;
240
+ // qcsdk decode may return string/number; normalize to BigInt for comparison
241
+ const afterBI = typeof afterValue === "bigint" ? afterValue : BigInt(afterValue);
242
+ assert.equal(afterBI, 42n);
243
+ }, { timeout: 900_000 });
244
+ });
245
+