quantumcoin 7.0.13 → 7.0.14

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 (204) hide show
  1. package/examples/node_modules/.bin/esbuild +16 -0
  2. package/examples/node_modules/.bin/esbuild.cmd +17 -0
  3. package/examples/node_modules/.bin/esbuild.ps1 +28 -0
  4. package/examples/node_modules/.bin/sdkgen +16 -0
  5. package/examples/node_modules/.bin/sdkgen.cmd +17 -0
  6. package/examples/node_modules/.bin/sdkgen.ps1 +28 -0
  7. package/examples/node_modules/.bin/tsx +16 -0
  8. package/examples/node_modules/.bin/tsx.cmd +17 -0
  9. package/examples/node_modules/.bin/tsx.ps1 +28 -0
  10. package/examples/node_modules/.package-lock.json +144 -0
  11. package/examples/node_modules/@esbuild/win32-x64/README.md +3 -0
  12. package/examples/node_modules/@esbuild/win32-x64/esbuild.exe +0 -0
  13. package/examples/node_modules/@esbuild/win32-x64/package.json +20 -0
  14. package/examples/node_modules/esbuild/LICENSE.md +21 -0
  15. package/examples/node_modules/esbuild/README.md +3 -0
  16. package/examples/node_modules/esbuild/bin/esbuild +223 -0
  17. package/examples/node_modules/esbuild/install.js +289 -0
  18. package/examples/node_modules/esbuild/lib/main.d.ts +716 -0
  19. package/examples/node_modules/esbuild/lib/main.js +2532 -0
  20. package/examples/node_modules/esbuild/package.json +49 -0
  21. package/examples/node_modules/get-tsconfig/LICENSE +21 -0
  22. package/examples/node_modules/get-tsconfig/README.md +235 -0
  23. package/examples/node_modules/get-tsconfig/dist/index.cjs +7 -0
  24. package/examples/node_modules/get-tsconfig/dist/index.d.cts +2088 -0
  25. package/examples/node_modules/get-tsconfig/dist/index.d.mts +2088 -0
  26. package/examples/node_modules/get-tsconfig/dist/index.mjs +7 -0
  27. package/examples/node_modules/get-tsconfig/package.json +46 -0
  28. package/examples/node_modules/quantum-coin-js-sdk/LICENSE +21 -0
  29. package/examples/node_modules/quantum-coin-js-sdk/LICENSE-wasm_exec.js.txt +30 -0
  30. package/examples/node_modules/quantum-coin-js-sdk/README.md +1675 -0
  31. package/examples/node_modules/quantum-coin-js-sdk/example/README.md +14 -0
  32. package/examples/node_modules/quantum-coin-js-sdk/example/conversion-example.js +19 -0
  33. package/examples/node_modules/quantum-coin-js-sdk/example/example-create-contract.js +396 -0
  34. package/examples/node_modules/quantum-coin-js-sdk/example/example-encode-decode-rlp.js +225 -0
  35. package/examples/node_modules/quantum-coin-js-sdk/example/example-event-pack-unpack.js +391 -0
  36. package/examples/node_modules/quantum-coin-js-sdk/example/example-misc.js +101 -0
  37. package/examples/node_modules/quantum-coin-js-sdk/example/example-rpc-send-signRawTransaction.js +318 -0
  38. package/examples/node_modules/quantum-coin-js-sdk/example/example-rpc-send.js +116 -0
  39. package/examples/node_modules/quantum-coin-js-sdk/example/example-send.js +70 -0
  40. package/examples/node_modules/quantum-coin-js-sdk/example/example-token-pack-unpack.js +961 -0
  41. package/examples/node_modules/quantum-coin-js-sdk/example/example-wallet-version4.js +35 -0
  42. package/examples/node_modules/quantum-coin-js-sdk/example/example-wallet.js +43 -0
  43. package/examples/node_modules/quantum-coin-js-sdk/example/example.js +405 -0
  44. package/examples/node_modules/quantum-coin-js-sdk/example/package-lock.json +134 -0
  45. package/examples/node_modules/quantum-coin-js-sdk/example/package.json +15 -0
  46. package/examples/node_modules/quantum-coin-js-sdk/index.d.ts +1031 -0
  47. package/examples/node_modules/quantum-coin-js-sdk/index.js +3144 -0
  48. package/examples/node_modules/quantum-coin-js-sdk/package.json +34 -0
  49. package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-32.json +1 -0
  50. package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-36.json +1 -0
  51. package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-48.json +1 -0
  52. package/examples/node_modules/quantum-coin-js-sdk/tests/generate-verify-vectors.js +91 -0
  53. package/examples/node_modules/quantum-coin-js-sdk/tests/non-transactional.preinit.test.js +41 -0
  54. package/examples/node_modules/quantum-coin-js-sdk/tests/non-transactional.test.js +1389 -0
  55. package/examples/node_modules/quantum-coin-js-sdk/tests/sign-raw-keytype5-context-null.test.js +107 -0
  56. package/examples/node_modules/quantum-coin-js-sdk/tests/sign-raw-transaction.test.js +196 -0
  57. package/examples/node_modules/quantum-coin-js-sdk/tests/sign-verify.test.js +311 -0
  58. package/examples/node_modules/quantum-coin-js-sdk/tests/transactional.relay.test.js +131 -0
  59. package/examples/node_modules/quantum-coin-js-sdk/tests/transactional.rpc.test.js +103 -0
  60. package/examples/node_modules/quantum-coin-js-sdk/tests/verify-vectors.json +95035 -0
  61. package/examples/node_modules/quantum-coin-js-sdk/wasmBase64.d.ts +9 -0
  62. package/examples/node_modules/quantum-coin-js-sdk/wasmBase64.js +16 -0
  63. package/examples/node_modules/quantum-coin-js-sdk/wasm_exec.d.ts +0 -0
  64. package/examples/node_modules/quantum-coin-js-sdk/wasm_exec.js +587 -0
  65. package/examples/node_modules/resolve-pkg-maps/LICENSE +21 -0
  66. package/examples/node_modules/resolve-pkg-maps/README.md +216 -0
  67. package/examples/node_modules/resolve-pkg-maps/dist/index.cjs +1 -0
  68. package/examples/node_modules/resolve-pkg-maps/dist/index.d.cts +11 -0
  69. package/examples/node_modules/resolve-pkg-maps/dist/index.d.mts +11 -0
  70. package/examples/node_modules/resolve-pkg-maps/dist/index.mjs +1 -0
  71. package/examples/node_modules/resolve-pkg-maps/package.json +42 -0
  72. package/examples/node_modules/seed-words/.github/workflows/publish-npmjs.yaml +22 -0
  73. package/examples/node_modules/seed-words/BUILD.md +7 -0
  74. package/examples/node_modules/seed-words/LICENSE +121 -0
  75. package/examples/node_modules/seed-words/README.md +67 -0
  76. package/examples/node_modules/seed-words/dist/seedwords.d.ts +39 -0
  77. package/examples/node_modules/seed-words/package.json +27 -0
  78. package/examples/node_modules/seed-words/seedwords.js +315 -0
  79. package/examples/node_modules/seed-words/seedwords.txt +65536 -0
  80. package/examples/node_modules/seed-words/tsconfig.json +21 -0
  81. package/examples/node_modules/tsx/LICENSE +21 -0
  82. package/examples/node_modules/tsx/README.md +32 -0
  83. package/examples/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
  84. package/examples/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
  85. package/examples/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
  86. package/examples/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
  87. package/examples/node_modules/tsx/dist/cjs/index.cjs +1 -0
  88. package/examples/node_modules/tsx/dist/cjs/index.mjs +1 -0
  89. package/examples/node_modules/tsx/dist/cli.cjs +54 -0
  90. package/examples/node_modules/tsx/dist/cli.mjs +55 -0
  91. package/examples/node_modules/tsx/dist/client-BQVF1NaW.mjs +1 -0
  92. package/examples/node_modules/tsx/dist/client-D6NvIMSC.cjs +1 -0
  93. package/examples/node_modules/tsx/dist/esm/api/index.cjs +1 -0
  94. package/examples/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
  95. package/examples/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
  96. package/examples/node_modules/tsx/dist/esm/api/index.mjs +1 -0
  97. package/examples/node_modules/tsx/dist/esm/index.cjs +2 -0
  98. package/examples/node_modules/tsx/dist/esm/index.mjs +2 -0
  99. package/examples/node_modules/tsx/dist/get-pipe-path-BHW2eJdv.mjs +1 -0
  100. package/examples/node_modules/tsx/dist/get-pipe-path-BoR10qr8.cjs +1 -0
  101. package/examples/node_modules/tsx/dist/index-7AaEi15b.mjs +14 -0
  102. package/examples/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
  103. package/examples/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
  104. package/examples/node_modules/tsx/dist/index-gckBtVBf.cjs +14 -0
  105. package/examples/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
  106. package/examples/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
  107. package/examples/node_modules/tsx/dist/loader.cjs +1 -0
  108. package/examples/node_modules/tsx/dist/loader.mjs +1 -0
  109. package/examples/node_modules/tsx/dist/node-features-_8ZFwP_x.mjs +1 -0
  110. package/examples/node_modules/tsx/dist/node-features-roYmp9jK.cjs +1 -0
  111. package/examples/node_modules/tsx/dist/package-CeBgXWuR.mjs +1 -0
  112. package/examples/node_modules/tsx/dist/package-Dxt5kIHw.cjs +1 -0
  113. package/examples/node_modules/tsx/dist/patch-repl.cjs +1 -0
  114. package/examples/node_modules/tsx/dist/patch-repl.mjs +1 -0
  115. package/examples/node_modules/tsx/dist/preflight.cjs +1 -0
  116. package/examples/node_modules/tsx/dist/preflight.mjs +1 -0
  117. package/examples/node_modules/tsx/dist/register-2sWVXuRQ.cjs +1 -0
  118. package/examples/node_modules/tsx/dist/register-B7jrtLTO.mjs +1 -0
  119. package/examples/node_modules/tsx/dist/register-CFH5oNdT.mjs +4 -0
  120. package/examples/node_modules/tsx/dist/register-D46fvsV_.cjs +4 -0
  121. package/examples/node_modules/tsx/dist/repl.cjs +3 -0
  122. package/examples/node_modules/tsx/dist/repl.mjs +3 -0
  123. package/examples/node_modules/tsx/dist/require-D4F1Lv60.cjs +1 -0
  124. package/examples/node_modules/tsx/dist/require-DQxpCAr4.mjs +1 -0
  125. package/examples/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
  126. package/examples/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
  127. package/examples/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
  128. package/examples/node_modules/tsx/dist/temporary-directory-CwHp0_NW.mjs +1 -0
  129. package/examples/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
  130. package/examples/node_modules/tsx/package.json +68 -0
  131. package/examples/package-lock.json +6 -6
  132. package/examples/package.json +1 -1
  133. package/generate-sdk.js +30 -9
  134. package/package.json +2 -2
  135. package/src/abi/interface.js +11 -2
  136. package/src/abi/js-abi-coder.js +61 -2
  137. package/src/contract/contract.js +53 -5
  138. package/src/generator/index.js +152 -13
  139. package/src/providers/provider.js +138 -5
  140. package/src/utils/rlp.js +13 -1
  141. package/src/wallet/wallet.js +91 -8
  142. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/_test-wallet.js +1 -1
  143. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/_test-wallet.ts +1 -1
  144. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/deploy.js +1 -1
  145. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/deploy.ts +1 -1
  146. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/offline-signing.js +1 -1
  147. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/offline-signing.ts +1 -1
  148. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/write-operations.js +1 -1
  149. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/examples/write-operations.ts +1 -1
  150. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/package-lock.json +6 -6
  151. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/package.json +1 -1
  152. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/src/AllSolidityTypes__factory.js +3 -1
  153. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-js/test/e2e/AllSolidityTypes.e2e.test.js +1 -1
  154. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/_test-wallet.js +1 -1
  155. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/_test-wallet.ts +1 -1
  156. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/deploy.js +1 -1
  157. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/deploy.ts +1 -1
  158. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/offline-signing.js +1 -1
  159. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/offline-signing.ts +1 -1
  160. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/write-operations.js +1 -1
  161. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/examples/write-operations.ts +1 -1
  162. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/package-lock.json +6 -6
  163. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/package.json +1 -1
  164. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/src/AllSolidityTypes__factory.ts +3 -1
  165. package/test/e2e/generated-sdks/all-solidity-types/all-solidity-types-ts/test/e2e/AllSolidityTypes.e2e.test.js +1 -1
  166. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/_test-wallet.js +1 -1
  167. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/_test-wallet.ts +1 -1
  168. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/deploy.js +1 -1
  169. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/deploy.ts +1 -1
  170. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/offline-signing.js +1 -1
  171. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/offline-signing.ts +1 -1
  172. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/write-operations.js +1 -1
  173. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/examples/write-operations.ts +1 -1
  174. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/package-lock.json +6 -6
  175. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/package.json +1 -1
  176. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/src/SimpleERC20.js +9 -3
  177. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/src/SimpleERC20__factory.js +3 -1
  178. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-js/test/e2e/SimpleERC20.e2e.test.js +1 -1
  179. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/_test-wallet.js +1 -1
  180. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/_test-wallet.ts +1 -1
  181. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/deploy.js +1 -1
  182. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/deploy.ts +1 -1
  183. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/offline-signing.js +1 -1
  184. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/offline-signing.ts +1 -1
  185. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/write-operations.js +1 -1
  186. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/examples/write-operations.ts +1 -1
  187. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/package-lock.json +6 -6
  188. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/package.json +1 -1
  189. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/src/SimpleERC20.ts +9 -3
  190. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/src/SimpleERC20__factory.ts +3 -1
  191. package/test/e2e/generated-sdks/simple-erc20/simple-erc20-ts/test/e2e/SimpleERC20.e2e.test.js +1 -1
  192. package/test/e2e/generator-interface.e2e.test.js +6 -4
  193. package/test/e2e/generator-interface.e2e.test.ts +6 -4
  194. package/test/security/abi-decoder-bounds.test.js +122 -0
  195. package/test/security/contract-overrides.test.js +112 -0
  196. package/test/security/generator-injection.test.js +195 -0
  197. package/test/security/malformed-input.test.js +26 -27
  198. package/test/security/rpc-numeric-bounds.test.js +81 -0
  199. package/test/security/rpc-trust.test.js +202 -0
  200. package/test/unit/abi-interface.test.js +12 -5
  201. package/test/unit/abi-interface.test.ts +8 -1
  202. package/test/unit/address-wallet.test.js +31 -0
  203. package/test/unit/encoding-units-rlp.test.js +35 -0
  204. package/test/unit/populate-transaction.test.js +33 -0
@@ -37,6 +37,40 @@ function _isOverridesLike(value) {
37
37
  return true;
38
38
  }
39
39
 
40
+ // Fields a caller is allowed to set via `overrides`. Protected routing /
41
+ // payload fields (`to`, `data`, `from`) are intentionally excluded so that a
42
+ // caller-supplied overrides object can never redirect the transaction to a
43
+ // different contract or replace the encoded calldata. Unknown keys are dropped.
44
+ const _ALLOWED_OVERRIDE_KEYS = [
45
+ "value",
46
+ "gasLimit",
47
+ "gasPrice",
48
+ "maxFeePerGas",
49
+ "maxPriorityFeePerGas",
50
+ "nonce",
51
+ "chainId",
52
+ "remarks",
53
+ "signingContext",
54
+ ];
55
+
56
+ /**
57
+ * Return a copy of `overrides` containing only the allow-listed fields.
58
+ * Drops `to`, `data`, `from`, and any unknown/prototype keys so the protected
59
+ * fields computed by the contract layer always win.
60
+ * @param {any} overrides
61
+ * @returns {Record<string, any>}
62
+ */
63
+ function _sanitizeOverrides(overrides) {
64
+ const out = {};
65
+ if (!_isOverridesLike(overrides)) return out;
66
+ for (const key of _ALLOWED_OVERRIDE_KEYS) {
67
+ if (Object.prototype.hasOwnProperty.call(overrides, key) && overrides[key] !== undefined) {
68
+ out[key] = overrides[key];
69
+ }
70
+ }
71
+ return out;
72
+ }
73
+
40
74
  /**
41
75
  * BaseContract placeholder (ethers-like).
42
76
  */
@@ -228,7 +262,8 @@ class Contract extends BaseContract {
228
262
  }
229
263
 
230
264
  const data = this.interface.encodeFunctionData(methodName, callArgs);
231
- return { to: this.address, data, ...(overrides || {}) };
265
+ // Protected fields (to/data) must win over caller-supplied overrides.
266
+ return { ..._sanitizeOverrides(overrides), to: this.address, data };
232
267
  }
233
268
 
234
269
  /**
@@ -241,7 +276,8 @@ class Contract extends BaseContract {
241
276
  async call(methodName, args, overrides) {
242
277
  if (!this.provider) throw makeError("missing provider", "UNKNOWN_ERROR", { operation: "call" });
243
278
  const data = this.interface.encodeFunctionData(methodName, args);
244
- const tx = { to: this.address, data, ...(overrides || {}) };
279
+ // Protected fields (to/data) must win over caller-supplied overrides.
280
+ const tx = { ..._sanitizeOverrides(overrides), to: this.address, data };
245
281
  const raw = await this.provider.call(tx, "latest");
246
282
  const decoded = this.interface.decodeFunctionResult(methodName, raw);
247
283
  return decoded;
@@ -257,7 +293,8 @@ class Contract extends BaseContract {
257
293
  async send(methodName, args, overrides) {
258
294
  if (!this.signer) throw makeError("missing signer", "UNKNOWN_ERROR", { operation: "send" });
259
295
  const data = this.interface.encodeFunctionData(methodName, args);
260
- const tx = { to: this.address, data, ...(overrides || {}) };
296
+ // Protected fields (to/data) must win over caller-supplied overrides.
297
+ const tx = { ..._sanitizeOverrides(overrides), to: this.address, data };
261
298
  const resp = await this.signer.sendTransaction(tx);
262
299
  return new ContractTransactionResponse(resp);
263
300
  }
@@ -273,9 +310,19 @@ class Contract extends BaseContract {
273
310
  if (!this.provider) throw makeError("missing provider", "UNKNOWN_ERROR", { operation: "queryFilter" });
274
311
  const name = typeof event === "string" ? event : event?.name;
275
312
  assertArgument(typeof name === "string", "invalid event", "event", event);
276
- const filter = { address: this.address, fromBlock, toBlock };
313
+
314
+ // Bind the query to the requested event's topic0 so a malicious node cannot
315
+ // return same-address logs for a *different* event. We both constrain the RPC
316
+ // filter and verify the returned logs' topic0 client-side.
317
+ const topic0 = this.interface.getEventTopic(name);
318
+ const filter = { address: this.address, topics: [topic0], fromBlock, toBlock };
277
319
  const logs = await this.provider.getLogs(filter);
278
- return logs.map((l) => new EventLog(l));
320
+ return logs
321
+ .filter((l) => {
322
+ const t = Array.isArray(l && l.topics) ? l.topics[0] : null;
323
+ return typeof t === "string" && normalizeHex(t).toLowerCase() === topic0.toLowerCase();
324
+ })
325
+ .map((l) => new EventLog(l));
279
326
  }
280
327
 
281
328
  on(event, callback) {
@@ -356,5 +403,6 @@ module.exports = {
356
403
  ContractTransactionResponse,
357
404
  ContractTransactionReceipt,
358
405
  EventLog,
406
+ _sanitizeOverrides,
359
407
  };
360
408
 
@@ -80,8 +80,9 @@ function _collectTupleRegistry(contractName, abi) {
80
80
  const key = _tupleKey(param);
81
81
  if (byKey.has(key)) return byKey.get(key);
82
82
 
83
- const suggested =
84
- _tupleBaseNameFromInternalType(contractName, param && param.internalType) || `${contractName}Tuple${++counter}`;
83
+ const suggested = _safeTypeIdent(
84
+ _tupleBaseNameFromInternalType(contractName, param && param.internalType) || `${contractName}Tuple${++counter}`,
85
+ );
85
86
  let baseName = suggested;
86
87
  if (usedNames.has(baseName) && usedNames.get(baseName) !== key) {
87
88
  let n = 1;
@@ -376,6 +377,93 @@ function _safeIdent(name) {
376
377
  return (name || "arg").replace(/[^a-zA-Z0-9_]/g, "_");
377
378
  }
378
379
 
380
+ // NatSpec/doc text is attacker-controllable (it comes from arbitrary Solidity
381
+ // source). It is emitted into generated `/** ... */` JSDoc blocks, so a `*/` in the
382
+ // text would close the comment early and turn whatever follows into executable code
383
+ // in the generated .js/.ts files. Neutralize the comment delimiters (and strip
384
+ // CR/LF so a line cannot be split) before interpolation.
385
+ function _escapeComment(text) {
386
+ return String(text == null ? "" : text)
387
+ .replace(/\*\//g, "* /")
388
+ .replace(/\/\*/g, "/ *")
389
+ .replace(/[\r\n]+/g, " ");
390
+ }
391
+
392
+ // Reserved words / dangerous property names that must never be emitted as a raw
393
+ // identifier (class name, function name, import/require specifier, etc.).
394
+ const _RESERVED_IDENTIFIERS = new Set([
395
+ "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do",
396
+ "else", "export", "extends", "finally", "for", "function", "if", "import", "in", "instanceof",
397
+ "new", "return", "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while",
398
+ "with", "yield", "enum", "await", "implements", "interface", "let", "package", "private",
399
+ "protected", "public", "static", "null", "true", "false",
400
+ "__proto__", "constructor", "prototype",
401
+ ]);
402
+
403
+ /**
404
+ * Assert that `name` is safe to interpolate verbatim into generated source code
405
+ * (class names, function names, type names, require/import specifiers, file names).
406
+ *
407
+ * This is the primary defense against code-injection and path-traversal
408
+ * via attacker-controlled ABI / artifact `name` fields: only strict JS/TS
409
+ * identifiers are allowed, which by construction cannot contain quotes, newlines,
410
+ * path separators, `..`, or other breakout characters.
411
+ *
412
+ * @param {any} name
413
+ * @param {string=} kind Human-readable description for error messages.
414
+ * @returns {string} the validated name
415
+ */
416
+ function _assertSafeIdentifier(name, kind) {
417
+ const label = kind || "identifier";
418
+ if (typeof name !== "string" || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
419
+ throw new Error(`Unsafe ${label} for code generation: ${JSON.stringify(name)}`);
420
+ }
421
+ if (_RESERVED_IDENTIFIERS.has(name)) {
422
+ throw new Error(`${label} must not be a reserved word: ${JSON.stringify(name)}`);
423
+ }
424
+ return name;
425
+ }
426
+
427
+ /**
428
+ * Coerce an arbitrary string (e.g. a struct name derived from an attacker-controlled
429
+ * `internalType`) into a safe type identifier. Unlike `_assertSafeIdentifier` this
430
+ * never throws; it sanitizes so auto-generated type names always remain valid.
431
+ * @param {any} s
432
+ * @returns {string}
433
+ */
434
+ function _safeTypeIdent(s) {
435
+ let v = String(s == null ? "" : s).replace(/[^A-Za-z0-9_$]/g, "_");
436
+ if (!/^[A-Za-z_$]/.test(v)) v = "_" + v;
437
+ return v || "_Tuple";
438
+ }
439
+
440
+ /**
441
+ * Validate every rendered function name in an ABI. Only `function` entries are
442
+ * emitted as method identifiers, so those are the names that must be safe.
443
+ * @param {any[]} abi
444
+ */
445
+ function _assertSafeAbiNames(abi) {
446
+ for (const f of abi || []) {
447
+ if (f && f.type === "function" && f.name != null) {
448
+ _assertSafeIdentifier(f.name, "ABI function name");
449
+ }
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Assert that `filePath` resolves to a location inside `outDir` (defense-in-depth).
455
+ * @param {string} outDir
456
+ * @param {string} filePath
457
+ */
458
+ function _assertWithinDir(outDir, filePath) {
459
+ const root = path.resolve(outDir);
460
+ const resolved = path.resolve(filePath);
461
+ const rel = path.relative(root, resolved);
462
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
463
+ throw new Error(`Refusing to write outside output directory: ${resolved}`);
464
+ }
465
+ }
466
+
379
467
  function _findConstructor(abi) {
380
468
  const ctor = abi.find((f) => f && f.type === "constructor");
381
469
  return ctor || { type: "constructor", inputs: [] };
@@ -508,7 +596,7 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
508
596
  for (const line of docs.contract.split(/\r?\n/g)) {
509
597
  const t = line.trim();
510
598
  if (!t) continue;
511
- contractTsLines.push(` * ${t}`);
599
+ contractTsLines.push(` * ${_escapeComment(t)}`);
512
600
  }
513
601
  }
514
602
  contractTsLines.push(` */`);
@@ -539,7 +627,12 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
539
627
  ` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides?: any): Promise<import("quantumcoin").TransactionRequest> => {`,
540
628
  );
541
629
  contractTsLines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
542
- contractTsLines.push(` return { to: this.address, data, ...(overrides || {}) };`);
630
+ // Drop attacker-controllable to/data/from; protected fields win.
631
+ contractTsLines.push(` const safeOverrides: any = {};`);
632
+ contractTsLines.push(
633
+ ` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "chainId", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
634
+ );
635
+ contractTsLines.push(` return { ...safeOverrides, to: this.address, data };`);
543
636
  contractTsLines.push(` },`);
544
637
  }
545
638
  contractTsLines.push(` } as any;`);
@@ -576,7 +669,7 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
576
669
  for (const line of fnDoc.split(/\r?\n/g)) {
577
670
  const t = line.trim();
578
671
  if (!t) continue;
579
- contractTsLines.push(` * ${t}`);
672
+ contractTsLines.push(` * ${_escapeComment(t)}`);
580
673
  }
581
674
  }
582
675
  contractTsLines.push(` */`);
@@ -628,7 +721,7 @@ function _renderContractJs({ contractName, abi, bytecode, docs }) {
628
721
  for (const line of docs.contract.split(/\r?\n/g)) {
629
722
  const t = line.trim();
630
723
  if (!t) continue;
631
- lines.push(` * ${t}`);
724
+ lines.push(` * ${_escapeComment(t)}`);
632
725
  }
633
726
  }
634
727
  lines.push(" */");
@@ -654,7 +747,12 @@ function _renderContractJs({ contractName, abi, bytecode, docs }) {
654
747
  const argsSig = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
655
748
  lines.push(` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides) => {`);
656
749
  lines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
657
- lines.push(` return { to: this.address, data, ...(overrides || {}) };`);
750
+ // Drop attacker-controllable to/data/from; protected fields win.
751
+ lines.push(` const safeOverrides = {};`);
752
+ lines.push(
753
+ ` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "chainId", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
754
+ );
755
+ lines.push(` return { ...safeOverrides, to: this.address, data };`);
658
756
  lines.push(" },");
659
757
  }
660
758
  lines.push(" };");
@@ -679,7 +777,7 @@ function _renderContractJs({ contractName, abi, bytecode, docs }) {
679
777
  for (const line of fnDoc.split(/\r?\n/g)) {
680
778
  const t = line.trim();
681
779
  if (!t) continue;
682
- lines.push(` * ${t}`);
780
+ lines.push(` * ${_escapeComment(t)}`);
683
781
  }
684
782
  }
685
783
  for (const p of inputs) {
@@ -843,7 +941,13 @@ function _renderFactoryTs({ contractName, abi }) {
843
941
  factoryTsLines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
844
942
  factoryTsLines.push(` const address = getCreateAddress({ from, nonce });`);
845
943
  factoryTsLines.push(` const txReq: any = this.getDeployTransaction(${deployArgsNames});`);
846
- factoryTsLines.push(` const tx = await signer.sendTransaction({ ...txReq, ...(overrides || {}), nonce });`);
944
+ // Only allow-listed override fields may reach the signer; protected fields
945
+ // (to/data from txReq, and the computed nonce) always win.
946
+ factoryTsLines.push(` const safeOverrides: any = {};`);
947
+ factoryTsLines.push(
948
+ ` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
949
+ );
950
+ factoryTsLines.push(` const tx = await signer.sendTransaction({ ...txReq, ...safeOverrides, nonce });`);
847
951
  factoryTsLines.push(` return new ${contractName}(address, signer as any, tx as any);`);
848
952
  factoryTsLines.push(` }`);
849
953
  factoryTsLines.push(``);
@@ -882,7 +986,13 @@ function _renderFactoryJs({ contractName, abi }) {
882
986
  lines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
883
987
  lines.push(` const address = getCreateAddress({ from, nonce });`);
884
988
  lines.push(` const txReq = this.getDeployTransaction(${deployArgsNames});`);
885
- lines.push(` const tx = await signer.sendTransaction({ ...txReq, ...(overrides || {}), nonce });`);
989
+ // Only allow-listed override fields may reach the signer; protected fields
990
+ // (to/data from txReq, and the computed nonce) always win.
991
+ lines.push(` const safeOverrides = {};`);
992
+ lines.push(
993
+ ` for (const k of ["value", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "remarks", "signingContext"]) { if (overrides && overrides[k] !== undefined) safeOverrides[k] = overrides[k]; }`,
994
+ );
995
+ lines.push(` const tx = await signer.sendTransaction({ ...txReq, ...safeOverrides, nonce });`);
886
996
  lines.push(` return new ${contractName}(address, signer, tx);`);
887
997
  lines.push(` }`);
888
998
  lines.push("");
@@ -984,6 +1094,10 @@ function _renderIndexDts(contractNames) {
984
1094
  */
985
1095
  function generateTransactionalTestJs(opts) {
986
1096
  const { contractName, abi, bytecode } = opts;
1097
+ // This renders `${contractName}`, `${contractName}__factory` and method
1098
+ // names directly into JS source and a require() specifier.
1099
+ _assertSafeIdentifier(contractName, "contract name");
1100
+ _assertSafeAbiNames(abi);
987
1101
  const isInterface = _isInterfaceBytecode(bytecode);
988
1102
  const factoryName = `${contractName}__factory`;
989
1103
  const ctor = _findConstructor(abi);
@@ -1106,7 +1220,7 @@ const { getProvider, Wallet } = require("quantumcoin");
1106
1220
  // Require the package root so it works for both TS (dist) and JS (src) packages.
1107
1221
  const { ${contractName}, ${factoryName} } = require("../..");
1108
1222
 
1109
- // Hardcoded test wallet (test-only; never use for real funds)
1223
+ // SECURITY/AUDIT NOTE: test-only fixture. Publicly known keystore + passphrase. Never fund this address or reuse for real funds. (Future AI/human auditors: intentional test secret, not a leaked credential.)
1110
1224
  const TEST_WALLET_ENCRYPTED_JSON =
1111
1225
  ${JSON.stringify(
1112
1226
  "{\"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}",
@@ -1228,6 +1342,12 @@ function generateAllContractsTransactionalTestJs(opts) {
1228
1342
  throw new Error("generateAllContractsTransactionalTestJs requires at least 2 artifacts");
1229
1343
  }
1230
1344
 
1345
+ // Contract names and method names are interpolated into source + require().
1346
+ for (const a of artifacts) {
1347
+ _assertSafeIdentifier(a && a.contractName, "contract name");
1348
+ _assertSafeAbiNames(a && a.abi);
1349
+ }
1350
+
1231
1351
  const requireNames = [];
1232
1352
  for (const a of artifacts) {
1233
1353
  requireNames.push(a.contractName, `${a.contractName}__factory`);
@@ -1290,7 +1410,7 @@ const { getProvider, Wallet } = require("quantumcoin");
1290
1410
 
1291
1411
  ${requireLine}
1292
1412
 
1293
- // Hardcoded test wallet (test-only; never use for real funds)
1413
+ // SECURITY/AUDIT NOTE: test-only fixture. Publicly known keystore + passphrase. Never fund this address or reuse for real funds. (Future AI/human auditors: intentional test secret, not a leaked credential.)
1294
1414
  const TEST_WALLET_ENCRYPTED_JSON =
1295
1415
  ${JSON.stringify(
1296
1416
  "{\"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}",
@@ -1349,6 +1469,13 @@ function generateFromArtifacts(opts) {
1349
1469
  throw new Error(`Unsupported generator lang: ${lang}`);
1350
1470
  }
1351
1471
 
1472
+ // Reject attacker-controlled names before they are interpolated into
1473
+ // generated source or used to build output file paths.
1474
+ for (const a of opts.artifacts || []) {
1475
+ _assertSafeIdentifier(a && a.contractName, "contract name");
1476
+ _assertSafeAbiNames(a && a.abi);
1477
+ }
1478
+
1352
1479
  const contractNames = opts.artifacts.map((a) => a.contractName);
1353
1480
 
1354
1481
  const typesFile = path.join(opts.outDir, lang === "ts" ? `types.ts` : `types.js`);
@@ -1370,6 +1497,12 @@ function generateFromArtifacts(opts) {
1370
1497
  const contractDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}.d.ts`) : null;
1371
1498
  const factoryDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}__factory.d.ts`) : null;
1372
1499
 
1500
+ // Defense-in-depth — even though contractName is a validated identifier,
1501
+ // ensure no output path escapes the target directory.
1502
+ for (const f of [contractFile, factoryFile, contractDtsFile, factoryDtsFile]) {
1503
+ if (f) _assertWithinDir(opts.outDir, f);
1504
+ }
1505
+
1373
1506
  if (lang === "ts") {
1374
1507
  fs.writeFileSync(
1375
1508
  contractFile,
@@ -1428,5 +1561,11 @@ function generate(opts) {
1428
1561
  return { contractFile, factoryFile, typesFile: res.typesFile, indexFile: res.indexFile };
1429
1562
  }
1430
1563
 
1431
- module.exports = { generate, generateFromArtifacts, generateTransactionalTestJs, generateAllContractsTransactionalTestJs };
1564
+ module.exports = {
1565
+ generate,
1566
+ generateFromArtifacts,
1567
+ generateTransactionalTestJs,
1568
+ generateAllContractsTransactionalTestJs,
1569
+ assertSafeIdentifier: _assertSafeIdentifier,
1570
+ };
1432
1571
 
@@ -44,7 +44,17 @@ function _hexToBigInt(hex) {
44
44
  }
45
45
 
46
46
  function _hexToNumber(hex) {
47
- return Number(_hexToBigInt(hex));
47
+ // RPC quantities are untrusted. Previously `Number(bigint)` silently
48
+ // truncated values above 2^53, corrupting blockNumber/nonce/status/indices used
49
+ // in confirmation math. We keep returning a `number` (no signature/type change),
50
+ // but fail loudly for out-of-range values instead of returning a corrupted one.
51
+ // Real chain values are far below MAX_SAFE_INTEGER, so honest flows are
52
+ // unaffected. (Returning bigint would break field types and wait() arithmetic.)
53
+ const bi = _hexToBigInt(hex);
54
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER) || bi < BigInt(Number.MIN_SAFE_INTEGER)) {
55
+ throw makeError("RPC quantity exceeds safe integer range", "NUMERIC_FAULT", { value: bi.toString() });
56
+ }
57
+ return Number(bi);
48
58
  }
49
59
 
50
60
  /**
@@ -52,6 +62,47 @@ function _hexToNumber(hex) {
52
62
  * @param {number|string|undefined} blockTag
53
63
  * @returns {string|undefined}
54
64
  */
65
+ /**
66
+ * Build a lowercase Set of allowed log addresses from a filter's `address` field
67
+ * (string or string[]). Returns null when no address constraint is present.
68
+ * @param {string|string[]|undefined|null} address
69
+ * @returns {Set<string>|null}
70
+ */
71
+ function _normalizeAddressFilter(address) {
72
+ if (address == null) return null;
73
+ const list = Array.isArray(address) ? address : [address];
74
+ const set = new Set();
75
+ for (const a of list) {
76
+ if (typeof a === "string" && a.length > 0) set.add(a.toLowerCase());
77
+ }
78
+ return set.size ? set : null;
79
+ }
80
+
81
+ /**
82
+ * Check a log's topics against a requested topics filter (eth_getLogs
83
+ * semantics). Each filter position may be null (wildcard), a string (exact
84
+ * match), or an array of strings (any match). Used to drop logs a malicious node
85
+ * returns that do not actually match the requested event topic(s).
86
+ * @param {any[]} logTopics
87
+ * @param {(string|string[]|null)[]} filterTopics
88
+ * @returns {boolean}
89
+ */
90
+ function _topicsMatch(logTopics, filterTopics) {
91
+ if (!Array.isArray(filterTopics)) return true;
92
+ const topics = Array.isArray(logTopics) ? logTopics : [];
93
+ for (let i = 0; i < filterTopics.length; i++) {
94
+ const want = filterTopics[i];
95
+ if (want == null) continue; // wildcard position
96
+ const have = typeof topics[i] === "string" ? topics[i].toLowerCase() : null;
97
+ if (have == null) return false;
98
+ const allowed = (Array.isArray(want) ? want : [want])
99
+ .filter((t) => typeof t === "string")
100
+ .map((t) => t.toLowerCase());
101
+ if (allowed.length && !allowed.includes(have)) return false;
102
+ }
103
+ return true;
104
+ }
105
+
55
106
  function _blockTagToHex(blockTag) {
56
107
  if (blockTag === undefined || blockTag === null) return undefined;
57
108
  const s = String(blockTag).toLowerCase();
@@ -263,7 +314,23 @@ class AbstractProvider extends Provider {
263
314
  async getTransaction(txHash) {
264
315
  const tx = await this._perform("eth_getTransactionByHash", [txHash]);
265
316
  if (!tx) return null;
266
- return new TransactionResponse(tx, this);
317
+ const result = new TransactionResponse(tx, this);
318
+ // Do not trust the node to return the transaction we actually asked for.
319
+ // When the response carries a hash, it must match the requested hash;
320
+ // otherwise a malicious node could substitute a different transaction.
321
+ if (
322
+ typeof txHash === "string" &&
323
+ typeof result.hash === "string" &&
324
+ result.hash.length > 0 &&
325
+ result.hash.toLowerCase() !== txHash.toLowerCase()
326
+ ) {
327
+ throw makeError("RPC node returned a transaction whose hash does not match the requested hash", "UNKNOWN_ERROR", {
328
+ operation: "getTransaction",
329
+ expected: txHash,
330
+ got: result.hash,
331
+ });
332
+ }
333
+ return result;
267
334
  }
268
335
 
269
336
  /**
@@ -273,7 +340,23 @@ class AbstractProvider extends Provider {
273
340
  async getTransactionReceipt(txHash) {
274
341
  const receipt = await this._perform("eth_getTransactionReceipt", [txHash]);
275
342
  if (!receipt) return null;
276
- return new TransactionReceipt(receipt, this);
343
+ const result = new TransactionReceipt(receipt, this);
344
+ // The receipt must belong to the transaction we asked about. A node must
345
+ // not be able to confirm an unrelated/fabricated transaction (which would let
346
+ // wait() report success for the wrong tx).
347
+ if (
348
+ typeof txHash === "string" &&
349
+ typeof result.transactionHash === "string" &&
350
+ result.transactionHash.length > 0 &&
351
+ result.transactionHash.toLowerCase() !== txHash.toLowerCase()
352
+ ) {
353
+ throw makeError("RPC node returned a receipt whose hash does not match the requested hash", "UNKNOWN_ERROR", {
354
+ operation: "getTransactionReceipt",
355
+ expected: txHash,
356
+ got: result.transactionHash,
357
+ });
358
+ }
359
+ return result;
277
360
  }
278
361
 
279
362
  /**
@@ -299,15 +382,47 @@ class AbstractProvider extends Provider {
299
382
  /**
300
383
  * Broadcasts a signed transaction.
301
384
  * @param {TransactionRequest|string} tx
385
+ * @param {{ expectedHash?: string }=} opts Optional. When `expectedHash` is set,
386
+ * the hash returned by the node (and the fetched-back transaction's hash) must
387
+ * match it, otherwise an error is thrown (do not trust the RPC node to
388
+ * broadcast the exact transaction that was signed).
302
389
  * @returns {Promise<TransactionResponse>}
303
390
  */
304
- async sendTransaction(tx) {
391
+ async sendTransaction(tx, opts) {
305
392
  // For QuantumCoin.js, tx is expected to be a signed raw transaction hex string.
306
393
  const raw = typeof tx === "string" ? tx : tx?.raw;
307
394
  if (typeof raw !== "string") throw makeError("sendTransaction requires a signed raw transaction string", "INVALID_ARGUMENT", { tx });
395
+ const expectedHash =
396
+ opts && typeof opts.expectedHash === "string" && opts.expectedHash.length > 0 ? opts.expectedHash.toLowerCase() : null;
397
+
308
398
  const hash = await this._perform("eth_sendRawTransaction", [raw]);
399
+
400
+ // The node returns the hash it claims to have accepted. If we know the
401
+ // hash of the transaction we signed, reject any mismatch — a malicious or
402
+ // buggy node must not be able to silently substitute a different transaction.
403
+ if (expectedHash != null && typeof hash === "string" && hash.length > 0) {
404
+ if (hash.toLowerCase() !== expectedHash) {
405
+ throw makeError("RPC node returned a transaction hash that does not match the signed transaction", "UNKNOWN_ERROR", {
406
+ operation: "sendTransaction",
407
+ expected: expectedHash,
408
+ got: hash,
409
+ });
410
+ }
411
+ }
412
+
309
413
  // Fetch back transaction (best-effort)
310
414
  const result = await this.getTransaction(hash);
415
+
416
+ // Verify the fetched-back transaction's hash too, so a node cannot return
417
+ // a fabricated transaction object under the correct hash lookup.
418
+ if (result && expectedHash != null && typeof result.hash === "string" && result.hash.toLowerCase() !== expectedHash) {
419
+ throw makeError("RPC node returned a transaction whose hash does not match the signed transaction", "UNKNOWN_ERROR", {
420
+ operation: "sendTransaction",
421
+ expected: expectedHash,
422
+ got: result.hash,
423
+ });
424
+ }
425
+
311
426
  return result || new TransactionResponse({ hash }, this);
312
427
  }
313
428
 
@@ -376,7 +491,25 @@ class AbstractProvider extends Provider {
376
491
  if (fromBlock !== undefined) normalized.fromBlock = fromBlock;
377
492
  if (toBlock !== undefined) normalized.toBlock = toBlock;
378
493
  const logs = await this._perform("eth_getLogs", [normalized]);
379
- return (logs || []).map((l) => new Log(l, this));
494
+ const mapped = (logs || []).map((l) => new Log(l, this));
495
+
496
+ // A malicious node could return logs emitted by contracts other than the
497
+ // one(s) requested. When the filter constrains the address, drop any log whose
498
+ // address is not in that set so foreign-contract events cannot be injected.
499
+ const allowed = _normalizeAddressFilter(filter.address);
500
+ // A malicious node could return logs for a different event (different
501
+ // topic0) or from foreign contracts. Drop anything that does not match the
502
+ // requested address and topic constraints.
503
+ const hasTopicFilter = Array.isArray(filter.topics) && filter.topics.some((t) => t != null);
504
+ if (allowed || hasTopicFilter) {
505
+ return mapped.filter((l) => {
506
+ if (!l) return false;
507
+ if (allowed && !(typeof l.address === "string" && allowed.has(l.address.toLowerCase()))) return false;
508
+ if (hasTopicFilter && !_topicsMatch(l.topics, filter.topics)) return false;
509
+ return true;
510
+ });
511
+ }
512
+ return mapped;
380
513
  }
381
514
  }
382
515
 
package/src/utils/rlp.js CHANGED
@@ -103,9 +103,14 @@ function _encode(value) {
103
103
  function _readLen(bytes, offset, lenOfLen) {
104
104
  if (lenOfLen === 0) return 0;
105
105
  if (offset + lenOfLen > bytes.length) throw new Error("RLP: insufficient data for length");
106
+ // Canonical encoding forbids a leading zero byte in the length field.
107
+ if (bytes[offset] === 0) throw new Error("RLP: non-canonical length (leading zero byte)");
108
+ // Accumulate with multiplication (not `<< 8`, which is a signed 32-bit op
109
+ // that overflows/wraps for 4+ byte lengths) and reject impossibly large lengths.
106
110
  let len = 0;
107
111
  for (let i = 0; i < lenOfLen; i++) {
108
- len = (len << 8) | bytes[offset + i];
112
+ len = len * 256 + bytes[offset + i];
113
+ if (len > Number.MAX_SAFE_INTEGER) throw new Error("RLP: length exceeds maximum safe integer");
109
114
  }
110
115
  return len;
111
116
  }
@@ -128,6 +133,9 @@ function _decode(bytes, start, end, depth) {
128
133
  const dataStart = start + 1;
129
134
  const dataEnd = dataStart + len;
130
135
  if (dataEnd > end) throw new Error("RLP: out of bounds");
136
+ // Canonical encoding requires a single byte < 0x80 to be encoded as
137
+ // itself, never as a 1-byte string.
138
+ if (len === 1 && bytes[dataStart] < 0x80) throw new Error("RLP: non-canonical single byte encoding");
131
139
  const out = bytesToHex(bytes.slice(dataStart, dataEnd));
132
140
  return { value: out, next: dataEnd };
133
141
  }
@@ -136,6 +144,8 @@ function _decode(bytes, start, end, depth) {
136
144
  if (prefix <= 0xbf) {
137
145
  const lenOfLen = prefix - 0xb7;
138
146
  const len = _readLen(bytes, start + 1, lenOfLen);
147
+ // Lengths <= 55 must use the short-string form.
148
+ if (len <= 55) throw new Error("RLP: non-canonical long string (length fits short form)");
139
149
  const dataStart = start + 1 + lenOfLen;
140
150
  const dataEnd = dataStart + len;
141
151
  if (dataEnd > end) throw new Error("RLP: out of bounds");
@@ -163,6 +173,8 @@ function _decode(bytes, start, end, depth) {
163
173
  // Long list
164
174
  const lenOfLen = prefix - 0xf7;
165
175
  const len = _readLen(bytes, start + 1, lenOfLen);
176
+ // Lengths <= 55 must use the short-list form.
177
+ if (len <= 55) throw new Error("RLP: non-canonical long list (length fits short form)");
166
178
  const dataStart = start + 1 + lenOfLen;
167
179
  const dataEnd = dataStart + len;
168
180
  if (dataEnd > end) throw new Error("RLP: out of bounds");