quantumcoin 7.0.3 → 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 -754
  5. package/README.md +165 -150
  6. package/SPEC.md +3845 -3843
  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 -73
  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 -1490
  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 -114
  47. package/src/contract/contract.js +354 -354
  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 -1404
  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 -203
  65. package/src/providers/provider.js +392 -371
  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 -368
  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 -151
  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 -404
  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 -99
  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 -62
  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,1404 +1,1404 @@
1
- /**
2
- * @fileoverview Typed contract generator (SPEC.md section 15).
3
- *
4
- * Supports generating:
5
- * - TypeScript source (`.ts`)
6
- * - JavaScript source (`.js`) + TypeScript declaration files (`.d.ts`)
7
- *
8
- * It is designed to be invoked by `generate-sdk.js` (CLI) and
9
- * can also be imported as a library.
10
- */
11
-
12
- const fs = require("node:fs");
13
- const path = require("node:path");
14
-
15
- function _ensureDir(p) {
16
- fs.mkdirSync(p, { recursive: true });
17
- }
18
-
19
- function _readJson(filePath) {
20
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
21
- }
22
-
23
- function _stripArraySuffixes(s) {
24
- let out = String(s || "");
25
- while (out.endsWith("]")) {
26
- out = out.slice(0, out.lastIndexOf("["));
27
- }
28
- return out;
29
- }
30
-
31
- function _parseArray(type) {
32
- const t = String(type || "");
33
- const idx = t.lastIndexOf("[");
34
- if (idx < 0 || !t.endsWith("]")) return null;
35
- const inner = t.slice(0, idx);
36
- const bracket = t.slice(idx + 1, t.length - 1); // "" => dynamic
37
- const fixedLen = bracket.length ? Number(bracket) : null;
38
- return { inner, fixedLen: fixedLen != null && Number.isFinite(fixedLen) ? fixedLen : null };
39
- }
40
-
41
- function _tupleBaseNameFromInternalType(contractName, internalType) {
42
- const raw = String(internalType || "");
43
- if (!raw) return null;
44
- const cleaned = _stripArraySuffixes(raw);
45
- const m = cleaned.match(/struct\s+([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)/);
46
- if (m && m[2]) return m[2];
47
- // Some compilers omit "struct" keyword.
48
- const m2 = cleaned.match(/^([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)$/);
49
- if (m2 && m2[2]) return m2[2];
50
- // Fallback: last segment
51
- const parts = cleaned.split(".");
52
- const last = parts[parts.length - 1];
53
- if (last && last !== contractName) return last;
54
- return null;
55
- }
56
-
57
- function _tupleKey(param) {
58
- /** @param {any} p */
59
- function norm(p) {
60
- const out = {
61
- name: String(p && p.name ? p.name : ""),
62
- type: String(p && p.type ? p.type : ""),
63
- internalType: String(p && p.internalType ? p.internalType : ""),
64
- components: [],
65
- };
66
- const comps = Array.isArray(p && p.components) ? p.components : [];
67
- out.components = comps.map((c) => norm(c));
68
- return out;
69
- }
70
- return JSON.stringify(norm(param || {}));
71
- }
72
-
73
- function _collectTupleRegistry(contractName, abi) {
74
- const byKey = new Map(); // key -> baseName
75
- const usedNames = new Map(); // baseName -> key
76
- let counter = 0;
77
-
78
- /** @param {any} param */
79
- function ensureTuple(param) {
80
- const key = _tupleKey(param);
81
- if (byKey.has(key)) return byKey.get(key);
82
-
83
- const suggested =
84
- _tupleBaseNameFromInternalType(contractName, param && param.internalType) || `${contractName}Tuple${++counter}`;
85
- let baseName = suggested;
86
- if (usedNames.has(baseName) && usedNames.get(baseName) !== key) {
87
- let n = 1;
88
- while (usedNames.has(`${suggested}_${n}`) && usedNames.get(`${suggested}_${n}`) !== key) n++;
89
- baseName = `${suggested}_${n}`;
90
- }
91
- byKey.set(key, baseName);
92
- usedNames.set(baseName, key);
93
-
94
- // Recurse to nested tuples.
95
- const comps = Array.isArray(param && param.components) ? param.components : [];
96
- for (const c of comps) visitParam(c);
97
- return baseName;
98
- }
99
-
100
- /** @param {any} param */
101
- function visitParam(param) {
102
- const type = String(param && param.type ? param.type : "");
103
- if (!type) return;
104
- const arr = _parseArray(type);
105
- if (arr) {
106
- visitParam({ ...(param || {}), type: arr.inner });
107
- return;
108
- }
109
- if (type === "tuple") {
110
- ensureTuple(param);
111
- return;
112
- }
113
- }
114
-
115
- for (const f of abi || []) {
116
- if (!f || typeof f !== "object") continue;
117
- const inputs = Array.isArray(f.inputs) ? f.inputs : [];
118
- const outputs = Array.isArray(f.outputs) ? f.outputs : [];
119
- for (const p of inputs) visitParam(p);
120
- for (const p of outputs) visitParam(p);
121
- }
122
-
123
- return { byKey };
124
- }
125
-
126
- function _solParamToTs(param, mode, tupleReg) {
127
- const type = String(param && param.type ? param.type : "");
128
- const m = mode === "output" ? "output" : "input";
129
-
130
- const arr = _parseArray(type);
131
- if (arr) {
132
- const innerParam = { ...(param || {}), type: arr.inner };
133
- const innerTs = _solParamToTs(innerParam, mode, tupleReg);
134
- if (arr.fixedLen != null) return `Types.SolFixedArray<${innerTs}, ${arr.fixedLen}>`;
135
- return `Types.SolArray<${innerTs}>`;
136
- }
137
-
138
- if (type === "tuple") {
139
- const key = _tupleKey(param);
140
- const baseName = tupleReg && tupleReg.byKey ? tupleReg.byKey.get(key) : null;
141
- const n = baseName || "Tuple";
142
- return `${n}${m === "input" ? "Input" : "Output"}`;
143
- }
144
-
145
- // Elementary types (hard typed)
146
- if (type === "address") return m === "input" ? "Types.AddressLike" : "Types.SolAddress";
147
- if (type === "bool") return "boolean";
148
- if (type === "string") return "string";
149
- if (type === "bytes") return m === "input" ? "Types.BytesLike" : "Types.HexString";
150
-
151
- const mBytesN = type.match(/^bytes(\d+)$/);
152
- if (mBytesN) {
153
- const n = Number(mBytesN[1]);
154
- if (n === 32) return m === "input" ? "Types.Bytes32Like" : "Types.Bytes32";
155
- if (Number.isFinite(n) && n >= 1 && n <= 32) return m === "input" ? `Types.Bytes${n}Like` : `Types.Bytes${n}`;
156
- }
157
-
158
- const mUint = type === "uint" ? ["uint", "256"] : type.match(/^uint(\d+)$/);
159
- if (mUint) {
160
- const bits = type === "uint" ? 256 : Number(mUint[1]);
161
- const b = Number.isFinite(bits) ? bits : 256;
162
- return m === "input" ? `Types.Uint${b}Like` : `Types.Uint${b}`;
163
- }
164
-
165
- const mInt = type === "int" ? ["int", "256"] : type.match(/^int(\d+)$/);
166
- if (mInt) {
167
- const bits = type === "int" ? 256 : Number(mInt[1]);
168
- const b = Number.isFinite(bits) ? bits : 256;
169
- return m === "input" ? `Types.Int${b}Like` : `Types.Int${b}`;
170
- }
171
-
172
- // Fallback (unknown type)
173
- return "any";
174
- }
175
-
176
- function _solParamToJsDoc(param, mode, tupleReg) {
177
- const type = String(param && param.type ? param.type : "");
178
- const m = mode === "output" ? "output" : "input";
179
-
180
- const arr = _parseArray(type);
181
- if (arr) {
182
- const innerParam = { ...(param || {}), type: arr.inner };
183
- const inner = _solParamToJsDoc(innerParam, mode, tupleReg);
184
- return `Array<${inner}>`;
185
- }
186
-
187
- if (type === "tuple") {
188
- const key = _tupleKey(param);
189
- const baseName = tupleReg && tupleReg.byKey ? tupleReg.byKey.get(key) : null;
190
- const n = baseName || "Tuple";
191
- return `${n}${m === "input" ? "Input" : "Output"}`;
192
- }
193
-
194
- if (type === "address") {
195
- return m === "input" ? `import("quantumcoin/types").AddressLike` : `import("quantumcoin/types").SolAddress`;
196
- }
197
- if (type === "bool") return "boolean";
198
- if (type === "string") return "string";
199
- if (type === "bytes") {
200
- return m === "input" ? `import("quantumcoin/types").BytesLike` : `import("quantumcoin/types").HexString`;
201
- }
202
-
203
- const mBytesN = type.match(/^bytes(\d+)$/);
204
- if (mBytesN) {
205
- const n = Number(mBytesN[1]);
206
- if (n === 32) {
207
- return m === "input" ? `import("quantumcoin/types").Bytes32Like` : `import("quantumcoin/types").Bytes32`;
208
- }
209
- if (Number.isFinite(n) && n >= 1 && n <= 32) {
210
- return m === "input"
211
- ? `import("quantumcoin/types").Bytes${n}Like`
212
- : `import("quantumcoin/types").Bytes${n}`;
213
- }
214
- }
215
-
216
- const mUint = type === "uint" ? ["uint", "256"] : type.match(/^uint(\d+)$/);
217
- if (mUint) {
218
- const bits = type === "uint" ? 256 : Number(mUint[1]);
219
- const b = Number.isFinite(bits) ? bits : 256;
220
- return m === "input"
221
- ? `import("quantumcoin/types").Uint${b}Like`
222
- : `import("quantumcoin/types").Uint${b}`;
223
- }
224
-
225
- const mInt = type === "int" ? ["int", "256"] : type.match(/^int(\d+)$/);
226
- if (mInt) {
227
- const bits = type === "int" ? 256 : Number(mInt[1]);
228
- const b = Number.isFinite(bits) ? bits : 256;
229
- return m === "input"
230
- ? `import("quantumcoin/types").Int${b}Like`
231
- : `import("quantumcoin/types").Int${b}`;
232
- }
233
-
234
- return "any";
235
- }
236
-
237
- function _renderTupleTypeDefs(contractName, abi, tupleReg) {
238
- const lines = [];
239
- void contractName;
240
-
241
- function _findTupleParamByKey(key) {
242
- /** @param {any} p */
243
- function walkParam(p) {
244
- if (!p || typeof p !== "object") return null;
245
- const t = String(p.type || "");
246
- if (!t) return null;
247
- const arr = _parseArray(t);
248
- if (arr) return walkParam({ ...(p || {}), type: arr.inner });
249
- if (t === "tuple") {
250
- if (_tupleKey(p) === key) return p;
251
- const comps = Array.isArray(p.components) ? p.components : [];
252
- for (const c of comps) {
253
- const found = walkParam(c);
254
- if (found) return found;
255
- }
256
- }
257
- return null;
258
- }
259
-
260
- for (const f of abi || []) {
261
- if (!f || typeof f !== "object") continue;
262
- const inputs = Array.isArray(f.inputs) ? f.inputs : [];
263
- const outputs = Array.isArray(f.outputs) ? f.outputs : [];
264
- for (const p of inputs) {
265
- const found = walkParam(p);
266
- if (found) return found;
267
- }
268
- for (const p of outputs) {
269
- const found = walkParam(p);
270
- if (found) return found;
271
- }
272
- }
273
- return null;
274
- }
275
-
276
- /** @param {any} param */
277
- function tupleFields(param, mode) {
278
- const comps = Array.isArray(param && param.components) ? param.components : [];
279
- const fields = [];
280
- for (let i = 0; i < comps.length; i++) {
281
- const c = comps[i];
282
- const field = _safeIdent((c && c.name) || `field${i}`);
283
- fields.push(` ${field}: ${_solParamToTs(c, mode, tupleReg)};`);
284
- }
285
- return fields;
286
- }
287
-
288
- // Render each known tuple once (as Input + Output).
289
- for (const [key, baseName] of tupleReg.byKey.entries()) {
290
- const param = _findTupleParamByKey(key);
291
- if (!param) continue;
292
-
293
- lines.push(`export type ${baseName}Input = {`);
294
- lines.push(...tupleFields(param, "input"));
295
- lines.push(`};`);
296
- lines.push(``);
297
- lines.push(`export type ${baseName}Output = {`);
298
- lines.push(...tupleFields(param, "output"));
299
- lines.push(`};`);
300
- lines.push(``);
301
- }
302
-
303
- return lines.length ? lines.join("\n") + "\n" : "";
304
- }
305
-
306
- function _renderTupleJsDocTypeDefs(contractName, abi, tupleReg) {
307
- void contractName;
308
- if (!tupleReg || !tupleReg.byKey || tupleReg.byKey.size === 0) return "";
309
-
310
- const lines = [];
311
-
312
- /** @param {string} key */
313
- function findTupleParamByKey(key) {
314
- /** @param {any} p */
315
- function walkParam(p) {
316
- if (!p || typeof p !== "object") return null;
317
- const t = String(p.type || "");
318
- if (!t) return null;
319
- const arr = _parseArray(t);
320
- if (arr) return walkParam({ ...(p || {}), type: arr.inner });
321
- if (t === "tuple") {
322
- if (_tupleKey(p) === key) return p;
323
- const comps = Array.isArray(p.components) ? p.components : [];
324
- for (const c of comps) {
325
- const found = walkParam(c);
326
- if (found) return found;
327
- }
328
- }
329
- return null;
330
- }
331
-
332
- for (const f of abi || []) {
333
- if (!f || typeof f !== "object") continue;
334
- const inputs = Array.isArray(f.inputs) ? f.inputs : [];
335
- const outputs = Array.isArray(f.outputs) ? f.outputs : [];
336
- for (const p of inputs) {
337
- const found = walkParam(p);
338
- if (found) return found;
339
- }
340
- for (const p of outputs) {
341
- const found = walkParam(p);
342
- if (found) return found;
343
- }
344
- }
345
- return null;
346
- }
347
-
348
- function renderTypedef(typeName, tupleParam, mode) {
349
- const comps = Array.isArray(tupleParam && tupleParam.components) ? tupleParam.components : [];
350
- lines.push("/**");
351
- lines.push(` * @typedef {Object} ${typeName}`);
352
- for (let i = 0; i < comps.length; i++) {
353
- const c = comps[i];
354
- const field = _safeIdent((c && c.name) || `field${i}`);
355
- lines.push(` * @property {${_solParamToJsDoc(c, mode, tupleReg)}} ${field}`);
356
- }
357
- lines.push(" */");
358
- lines.push("");
359
- }
360
-
361
- for (const [key, baseName] of tupleReg.byKey.entries()) {
362
- const param = findTupleParamByKey(key);
363
- if (!param) continue;
364
- renderTypedef(`${baseName}Input`, param, "input");
365
- renderTypedef(`${baseName}Output`, param, "output");
366
- }
367
-
368
- return lines.join("\n");
369
- }
370
-
371
- function _cap(s) {
372
- return s ? s[0].toUpperCase() + s.slice(1) : s;
373
- }
374
-
375
- function _safeIdent(name) {
376
- return (name || "arg").replace(/[^a-zA-Z0-9_]/g, "_");
377
- }
378
-
379
- function _findConstructor(abi) {
380
- const ctor = abi.find((f) => f && f.type === "constructor");
381
- return ctor || { type: "constructor", inputs: [] };
382
- }
383
-
384
- function _solTypeToTestValueExpr(param) {
385
- const type = typeof param === "string" ? param : String(param && param.type ? param.type : "");
386
- const internalType = typeof param === "object" && param ? String(param.internalType || "") : "";
387
-
388
- // Arrays (dynamic or fixed)
389
- if (type.endsWith("]")) {
390
- const inner = type.slice(0, type.lastIndexOf("["));
391
- const bracket = type.slice(type.lastIndexOf("[") + 1, type.length - 1);
392
- const isFixed = bracket.length > 0;
393
- const fixedLen = isFixed ? Number(bracket) : 0;
394
- const elemParam = { ...(param || {}), type: inner };
395
-
396
- const elemExpr = _solTypeToTestValueExpr(elemParam);
397
- if (isFixed && Number.isFinite(fixedLen) && fixedLen > 0) {
398
- // Fixed arrays MUST match the exact declared length.
399
- // Use Array(len).fill(expr) to keep source size reasonable.
400
- return `Array(${fixedLen}).fill(${elemExpr})`;
401
- }
402
- return `[${elemExpr}]`;
403
- }
404
-
405
- if (type === "address") return "wallet.address";
406
- if (type === "bool") return "true";
407
- if (type === "string") return JSON.stringify("hello");
408
- if (type === "bytes") return JSON.stringify("0x1234");
409
-
410
- // Fixed-size bytes
411
- const mBytesN = type.match(/^bytes(\d+)$/);
412
- if (mBytesN) {
413
- const n = Number(mBytesN[1]);
414
- if (Number.isFinite(n) && n >= 1 && n <= 32) return JSON.stringify(`0x${"11".repeat(n)}`);
415
- }
416
-
417
- // NOTE: quantum-coin-js-sdk WASM interop does not accept BigInt values directly.
418
- // Use plain numbers/strings for ints/uints.
419
- // Enums are ABI-encoded as uints but Solidity will revert if the value is out of range.
420
- if (type.startsWith("uint") && /\benum\b/.test(internalType)) return "1";
421
- if (type.startsWith("uint")) return "123";
422
- if (type.startsWith("int")) return "-123";
423
-
424
- // Tuple / struct
425
- if (type === "tuple") {
426
- const comps = Array.isArray(param && param.components) ? param.components : [];
427
- if (comps.length === 0) return "{}";
428
- const fields = comps.map((c, idx) => {
429
- const name = c && typeof c.name === "string" && c.name ? c.name : `field${idx}`;
430
- return `${JSON.stringify(name)}: ${_solTypeToTestValueExpr(c)}`;
431
- });
432
- return `{ ${fields.join(", ")} }`;
433
- }
434
-
435
- // Fallback
436
- return "undefined";
437
- }
438
-
439
- function _isSupportedType(type) {
440
- if (type.endsWith("]")) return _isSupportedType(type.slice(0, type.lastIndexOf("[")));
441
- return (
442
- type === "address" ||
443
- type === "bool" ||
444
- type === "string" ||
445
- type === "bytes" ||
446
- type === "bytes32" ||
447
- type.startsWith("uint") ||
448
- type.startsWith("int")
449
- );
450
- }
451
-
452
- function _allSupportedParams(inputs) {
453
- return (inputs || []).every((i) => i && typeof i.type === "string" && _isSupportedType(i.type));
454
- }
455
-
456
- function _typesTs() {
457
- return (
458
- `// Auto-generated by sdkgen\n\n` +
459
- `// Re-export ALL Solidity-related types from quantumcoin.\n` +
460
- `export type * from "quantumcoin/types";\n`
461
- );
462
- }
463
-
464
- function _typesJs() {
465
- return `// Auto-generated by sdkgen\n\n` + `module.exports = {};\n`;
466
- }
467
-
468
- function _typesDts() {
469
- return (
470
- `// Auto-generated by sdkgen\n\n` +
471
- `export type * from "quantumcoin/types";\n`
472
- );
473
- }
474
-
475
- function _renderContractTs({ contractName, abi, bytecode, docs }) {
476
- const functions = (abi || []).filter((f) => f && f.type === "function");
477
- const tupleReg = _collectTupleRegistry(contractName, abi);
478
- const txFns = functions.filter((f) => (f.stateMutability || "") !== "view" && (f.stateMutability || "") !== "pure");
479
-
480
- const contractTsLines = [];
481
- contractTsLines.push(`// Auto-generated by sdkgen`);
482
- contractTsLines.push(`import { Contract, ContractTransactionResponse, ContractRunner, TransactionResponse } from "quantumcoin";`);
483
- contractTsLines.push(`import type * as Types from "./types";`);
484
- contractTsLines.push(``);
485
- contractTsLines.push(_renderTupleTypeDefs(contractName, abi, tupleReg).trimEnd());
486
- contractTsLines.push(``);
487
- contractTsLines.push(`/**`);
488
- contractTsLines.push(` * ${contractName} - A typed contract interface for ${contractName}`);
489
- if (docs && typeof docs.contract === "string" && docs.contract.trim()) {
490
- contractTsLines.push(` *`);
491
- for (const line of docs.contract.split(/\r?\n/g)) {
492
- const t = line.trim();
493
- if (!t) continue;
494
- contractTsLines.push(` * ${t}`);
495
- }
496
- }
497
- contractTsLines.push(` */`);
498
- contractTsLines.push(`export class ${contractName} extends Contract {`);
499
- contractTsLines.push(` static readonly abi = ${JSON.stringify(abi, null, 2)} as const;`);
500
- contractTsLines.push(` static readonly bytecode = ${JSON.stringify(bytecode)};`);
501
- contractTsLines.push(``);
502
- contractTsLines.push(` static connect(address: string, runner?: ContractRunner): ${contractName} {`);
503
- contractTsLines.push(` return new ${contractName}(address, runner);`);
504
- contractTsLines.push(` }`);
505
- contractTsLines.push(``);
506
- contractTsLines.push(` constructor(address: string, runner?: ContractRunner, _deployTx?: TransactionResponse) {`);
507
- contractTsLines.push(` super(address, ${contractName}.abi as any, runner as any, ${contractName}.bytecode);`);
508
- contractTsLines.push(` // @ts-expect-error internal attach`);
509
- contractTsLines.push(` this._deployTx = _deployTx;`);
510
- if (txFns.length) {
511
- contractTsLines.push(``);
512
- contractTsLines.push(` // Typed populateTransaction helpers (offline signing / sendRawTransaction flows)`);
513
- contractTsLines.push(` this.populateTransaction = {`);
514
- for (const fn of txFns) {
515
- const name = fn.name;
516
- const inputs = fn.inputs || [];
517
- const argsSig = inputs
518
- .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
519
- .join(", ");
520
- const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
521
- contractTsLines.push(
522
- ` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides?: any): Promise<import("quantumcoin").TransactionRequest> => {`,
523
- );
524
- contractTsLines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
525
- contractTsLines.push(` return { to: this.address, data, ...(overrides || {}) };`);
526
- contractTsLines.push(` },`);
527
- }
528
- contractTsLines.push(` } as any;`);
529
- }
530
- contractTsLines.push(` }`);
531
-
532
- for (const fn of functions) {
533
- const name = fn.name;
534
- const inputs = fn.inputs || [];
535
- const outputs = fn.outputs || [];
536
- const argsSig = inputs
537
- .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
538
- .join(", ");
539
- const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
540
-
541
- const mut = fn.stateMutability || "";
542
- const isView = mut === "view" || mut === "pure";
543
-
544
- let returnTs;
545
- if (isView) {
546
- if (outputs.length === 0) returnTs = "Promise<void>";
547
- else if (outputs.length === 1) returnTs = `Promise<${_solParamToTs(outputs[0], "output", tupleReg)}>`;
548
- else returnTs = `Promise<[${outputs.map((o) => _solParamToTs(o, "output", tupleReg)).join(", ")}]>`;
549
- } else {
550
- returnTs = "Promise<ContractTransactionResponse>";
551
- }
552
-
553
- contractTsLines.push(``);
554
- contractTsLines.push(` /**`);
555
- contractTsLines.push(` * ${name}`);
556
- const fnDoc = docs && docs.functions && typeof docs.functions[name] === "string" ? docs.functions[name] : "";
557
- if (fnDoc && fnDoc.trim()) {
558
- contractTsLines.push(` *`);
559
- for (const line of fnDoc.split(/\r?\n/g)) {
560
- const t = line.trim();
561
- if (!t) continue;
562
- contractTsLines.push(` * ${t}`);
563
- }
564
- }
565
- contractTsLines.push(` */`);
566
- if (isView) {
567
- contractTsLines.push(` async ${name}(${argsSig}): ${returnTs} {`);
568
- contractTsLines.push(` const res = await this.call(${JSON.stringify(name)}, [${argsNames}]);`);
569
- if (outputs.length === 0) {
570
- contractTsLines.push(` void res;`);
571
- contractTsLines.push(` return;`);
572
- } else if (outputs.length === 1) {
573
- contractTsLines.push(` return (Array.isArray(res) ? res[0] : res) as unknown as ${_solParamToTs(outputs[0], "output", tupleReg)};`);
574
- } else {
575
- contractTsLines.push(
576
- ` return res as unknown as [${outputs.map((o) => _solParamToTs(o, "output", tupleReg)).join(", ")}];`,
577
- );
578
- }
579
- contractTsLines.push(` }`);
580
- } else {
581
- contractTsLines.push(` async ${name}(${argsSig}${argsSig ? ", " : ""}overrides?: any): ${returnTs} {`);
582
- contractTsLines.push(` return this.send(${JSON.stringify(name)}, [${argsNames}], overrides);`);
583
- contractTsLines.push(` }`);
584
- }
585
- }
586
-
587
- contractTsLines.push(`}`);
588
- return contractTsLines.join("\n") + "\n";
589
- }
590
-
591
- function _renderContractJs({ contractName, abi, bytecode, docs }) {
592
- const functions = (abi || []).filter((f) => f && f.type === "function");
593
- const tupleReg = _collectTupleRegistry(contractName, abi);
594
- const txFns = functions.filter((f) => (f.stateMutability || "") !== "view" && (f.stateMutability || "") !== "pure");
595
-
596
- const lines = [];
597
- lines.push(`// Auto-generated by sdkgen`);
598
- lines.push(`const { Contract } = require("quantumcoin");`);
599
- lines.push("");
600
-
601
- const tupleDoc = _renderTupleJsDocTypeDefs(contractName, abi, tupleReg).trimEnd();
602
- if (tupleDoc) {
603
- lines.push(tupleDoc);
604
- lines.push("");
605
- }
606
-
607
- lines.push("/**");
608
- lines.push(` * ${contractName} - A typed contract interface for ${contractName}`);
609
- if (docs && typeof docs.contract === "string" && docs.contract.trim()) {
610
- lines.push(" *");
611
- for (const line of docs.contract.split(/\r?\n/g)) {
612
- const t = line.trim();
613
- if (!t) continue;
614
- lines.push(` * ${t}`);
615
- }
616
- }
617
- lines.push(" */");
618
- lines.push(`class ${contractName} extends Contract {`);
619
- lines.push(` static abi = ${JSON.stringify(abi, null, 2)};`);
620
- lines.push(` static bytecode = ${JSON.stringify(bytecode)};`);
621
- lines.push("");
622
- lines.push(` static connect(address, runner) {`);
623
- lines.push(` return new ${contractName}(address, runner);`);
624
- lines.push(` }`);
625
- lines.push("");
626
- lines.push(` constructor(address, runner, _deployTx) {`);
627
- lines.push(` super(address, ${contractName}.abi, runner, ${contractName}.bytecode);`);
628
- lines.push(` this._deployTx = _deployTx;`);
629
- if (txFns.length) {
630
- lines.push("");
631
- lines.push(" // Typed populateTransaction helpers (offline signing / sendRawTransaction flows)");
632
- lines.push(" this.populateTransaction = {");
633
- for (const fn of txFns) {
634
- const name = fn.name;
635
- const inputs = fn.inputs || [];
636
- const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
637
- const argsSig = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
638
- lines.push(` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides) => {`);
639
- lines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
640
- lines.push(` return { to: this.address, data, ...(overrides || {}) };`);
641
- lines.push(" },");
642
- }
643
- lines.push(" };");
644
- }
645
- lines.push(` }`);
646
-
647
- for (const fn of functions) {
648
- const name = fn.name;
649
- const inputs = fn.inputs || [];
650
- const outputs = fn.outputs || [];
651
- const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
652
-
653
- const mut = fn.stateMutability || "";
654
- const isView = mut === "view" || mut === "pure";
655
-
656
- lines.push("");
657
- lines.push(" /**");
658
- lines.push(` * ${name}`);
659
- const fnDoc = docs && docs.functions && typeof docs.functions[name] === "string" ? docs.functions[name] : "";
660
- if (fnDoc && fnDoc.trim()) {
661
- lines.push(" *");
662
- for (const line of fnDoc.split(/\r?\n/g)) {
663
- const t = line.trim();
664
- if (!t) continue;
665
- lines.push(` * ${t}`);
666
- }
667
- }
668
- for (const p of inputs) {
669
- const pName = _safeIdent(p.name || "arg");
670
- lines.push(` * @param {${_solParamToJsDoc(p, "input", tupleReg)}} ${pName}`);
671
- }
672
- if (isView) {
673
- if (outputs.length === 0) {
674
- lines.push(` * @returns {Promise<void>}`);
675
- } else if (outputs.length === 1) {
676
- lines.push(` * @returns {Promise<${_solParamToJsDoc(outputs[0], "output", tupleReg)}>} `);
677
- } else {
678
- lines.push(
679
- ` * @returns {Promise<[${outputs.map((o) => _solParamToJsDoc(o, "output", tupleReg)).join(", ")}]>}`,
680
- );
681
- }
682
- } else {
683
- lines.push(` * @returns {Promise<import("quantumcoin").ContractTransactionResponse>}`);
684
- }
685
- lines.push(" */");
686
-
687
- if (isView) {
688
- lines.push(` async ${name}(${argsNames}) {`);
689
- lines.push(` const res = await this.call(${JSON.stringify(name)}, [${argsNames}]);`);
690
- if (outputs.length === 0) {
691
- lines.push(` return;`);
692
- } else if (outputs.length === 1) {
693
- lines.push(` return Array.isArray(res) ? res[0] : res;`);
694
- } else {
695
- lines.push(` return res;`);
696
- }
697
- lines.push(` }`);
698
- } else {
699
- lines.push(` async ${name}(${argsNames}${argsNames ? ", " : ""}overrides) {`);
700
- lines.push(` return this.send(${JSON.stringify(name)}, [${argsNames}], overrides);`);
701
- lines.push(` }`);
702
- }
703
- }
704
-
705
- lines.push(`}`);
706
- lines.push("");
707
- lines.push(`module.exports = { ${contractName} };`);
708
- lines.push("");
709
- return lines.join("\n");
710
- }
711
-
712
- function _renderContractDts({ contractName, abi }) {
713
- const functions = (abi || []).filter((f) => f && f.type === "function");
714
- const tupleReg = _collectTupleRegistry(contractName, abi);
715
- const txFns = functions.filter((f) => (f.stateMutability || "") !== "view" && (f.stateMutability || "") !== "pure");
716
- const lines = [];
717
- lines.push(`// Auto-generated by sdkgen`);
718
- lines.push(`import { Contract, ContractRunner, ContractTransactionResponse, TransactionResponse } from "quantumcoin";`);
719
- lines.push(`import type * as Types from "./types";`);
720
- lines.push("");
721
- const tupleDefs = _renderTupleTypeDefs(contractName, abi, tupleReg).trimEnd();
722
- if (tupleDefs) {
723
- lines.push(tupleDefs);
724
- lines.push("");
725
- }
726
- lines.push(`export declare class ${contractName} extends Contract {`);
727
- lines.push(` static readonly abi: readonly any[];`);
728
- lines.push(` static readonly bytecode: string;`);
729
- lines.push(` static connect(address: string, runner?: ContractRunner): ${contractName};`);
730
- lines.push(` constructor(address: string, runner?: ContractRunner, _deployTx?: TransactionResponse);`);
731
- if (txFns.length) {
732
- lines.push(` readonly populateTransaction: {`);
733
- for (const fn of txFns) {
734
- const name = fn.name;
735
- const inputs = fn.inputs || [];
736
- const argsSig = inputs
737
- .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
738
- .join(", ");
739
- lines.push(
740
- ` ${name}(${argsSig}${argsSig ? ", " : ""}overrides?: any): Promise<import("quantumcoin").TransactionRequest>;`,
741
- );
742
- }
743
- lines.push(` };`);
744
- }
745
-
746
- for (const fn of functions) {
747
- const name = fn.name;
748
- const inputs = fn.inputs || [];
749
- const outputs = fn.outputs || [];
750
-
751
- const argsSig = inputs.map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`).join(", ");
752
-
753
- const mut = fn.stateMutability || "";
754
- const isView = mut === "view" || mut === "pure";
755
-
756
- let returnTs;
757
- if (isView) {
758
- if (outputs.length === 0) returnTs = "Promise<void>";
759
- else if (outputs.length === 1) returnTs = `Promise<${_solParamToTs(outputs[0], "output", tupleReg)}>`;
760
- else returnTs = `Promise<[${outputs.map((o) => _solParamToTs(o, "output", tupleReg)).join(", ")}]>`;
761
- } else {
762
- returnTs = "Promise<ContractTransactionResponse>";
763
- }
764
-
765
- if (isView) {
766
- lines.push(` ${name}(${argsSig}): ${returnTs};`);
767
- } else {
768
- lines.push(` ${name}(${argsSig}${argsSig ? ", " : ""}overrides?: any): ${returnTs};`);
769
- }
770
- }
771
-
772
- lines.push(`}`);
773
- return lines.join("\n") + "\n";
774
- }
775
-
776
- function _renderFactoryTs({ contractName, abi }) {
777
- const factoryName = `${contractName}__factory`;
778
- const ctor = _findConstructor(abi);
779
- const ctorInputs = (ctor && ctor.inputs) || [];
780
- const tupleReg = _collectTupleRegistry(contractName, abi);
781
-
782
- const factoryTsLines = [];
783
- factoryTsLines.push(`// Auto-generated by sdkgen`);
784
- factoryTsLines.push(`import { ContractFactory, ContractRunner, getCreateAddress } from "quantumcoin";`);
785
- factoryTsLines.push(`import { ${contractName} } from "./${contractName}";`);
786
- factoryTsLines.push(`import type * as Types from "./types";`);
787
-
788
- // Import tuple input types used by the constructor (if any)
789
- const ctorTupleTypes = new Set();
790
- const visit = (p) => {
791
- const t = String(p && p.type ? p.type : "");
792
- const arr = _parseArray(t);
793
- if (arr) return visit({ ...(p || {}), type: arr.inner });
794
- if (t === "tuple") {
795
- const key = _tupleKey(p);
796
- const base = tupleReg.byKey.get(key);
797
- if (base) ctorTupleTypes.add(`${base}Input`);
798
- const comps = Array.isArray(p && p.components) ? p.components : [];
799
- for (const c of comps) visit(c);
800
- }
801
- };
802
- for (const p of ctorInputs) visit(p);
803
- if (ctorTupleTypes.size) {
804
- factoryTsLines.push(`import type { ${Array.from(ctorTupleTypes).sort().join(", ")} } from "./${contractName}";`);
805
- }
806
- factoryTsLines.push(``);
807
- factoryTsLines.push(`export class ${factoryName} extends ContractFactory {`);
808
- factoryTsLines.push(` constructor(runner: ContractRunner) {`);
809
- factoryTsLines.push(` super(${contractName}.abi as any, ${contractName}.bytecode as any, runner as any);`);
810
- factoryTsLines.push(` }`);
811
- factoryTsLines.push(``);
812
-
813
- // Typed deploy method (uses constructor args + optional overrides)
814
- const deployArgsSig = ctorInputs
815
- .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
816
- .join(", ");
817
- const deployArgsNames = ctorInputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
818
-
819
- factoryTsLines.push(` async deploy(${deployArgsSig}${deployArgsSig ? ", " : ""}overrides?: any): Promise<${contractName}> {`);
820
- factoryTsLines.push(` const signer: any = (this as any).signer;`);
821
- factoryTsLines.push(` if (!signer) { throw new Error("missing signer"); }`);
822
- factoryTsLines.push(` const from: string = signer.getAddress ? await signer.getAddress() : signer.address;`);
823
- factoryTsLines.push(` const provider: any = signer.provider;`);
824
- factoryTsLines.push(` if (!provider || !provider.getTransactionCount) { throw new Error("missing provider"); }`);
825
- factoryTsLines.push(` let nonce: number;`);
826
- factoryTsLines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
827
- factoryTsLines.push(` const address = getCreateAddress({ from, nonce });`);
828
- factoryTsLines.push(` const txReq: any = this.getDeployTransaction(${deployArgsNames});`);
829
- factoryTsLines.push(` const tx = await signer.sendTransaction({ ...txReq, ...(overrides || {}), nonce });`);
830
- factoryTsLines.push(` return new ${contractName}(address, signer as any, tx as any);`);
831
- factoryTsLines.push(` }`);
832
- factoryTsLines.push(``);
833
-
834
- factoryTsLines.push(` static connect(address: string, runner?: ContractRunner): ${contractName} {`);
835
- factoryTsLines.push(` return ${contractName}.connect(address, runner);`);
836
- factoryTsLines.push(` }`);
837
- factoryTsLines.push(`}`);
838
-
839
- return factoryTsLines.join("\n") + "\n";
840
- }
841
-
842
- function _renderFactoryJs({ contractName, abi }) {
843
- const factoryName = `${contractName}__factory`;
844
- const ctor = _findConstructor(abi);
845
- const ctorInputs = (ctor && ctor.inputs) || [];
846
- const deployArgsNames = ctorInputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
847
-
848
- const lines = [];
849
- lines.push(`// Auto-generated by sdkgen`);
850
- lines.push(`const { ContractFactory, getCreateAddress } = require("quantumcoin");`);
851
- lines.push(`const { ${contractName} } = require("./${contractName}");`);
852
- lines.push("");
853
- lines.push(`class ${factoryName} extends ContractFactory {`);
854
- lines.push(` constructor(runner) {`);
855
- lines.push(` super(${contractName}.abi, ${contractName}.bytecode, runner);`);
856
- lines.push(` }`);
857
- lines.push("");
858
- lines.push(` async deploy(${deployArgsNames}${deployArgsNames ? ", " : ""}overrides) {`);
859
- lines.push(` const signer = this.signer;`);
860
- lines.push(` if (!signer) { throw new Error("missing signer"); }`);
861
- lines.push(` const from = signer.getAddress ? await signer.getAddress() : signer.address;`);
862
- lines.push(` const provider = signer.provider;`);
863
- lines.push(` if (!provider || !provider.getTransactionCount) { throw new Error("missing provider"); }`);
864
- lines.push(` let nonce;`);
865
- lines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
866
- lines.push(` const address = getCreateAddress({ from, nonce });`);
867
- lines.push(` const txReq = this.getDeployTransaction(${deployArgsNames});`);
868
- lines.push(` const tx = await signer.sendTransaction({ ...txReq, ...(overrides || {}), nonce });`);
869
- lines.push(` return new ${contractName}(address, signer, tx);`);
870
- lines.push(` }`);
871
- lines.push("");
872
- lines.push(` static connect(address, runner) {`);
873
- lines.push(` return ${contractName}.connect(address, runner);`);
874
- lines.push(` }`);
875
- lines.push(`}`);
876
- lines.push("");
877
- lines.push(`module.exports = { ${factoryName} };`);
878
- lines.push("");
879
- return lines.join("\n");
880
- }
881
-
882
- function _renderFactoryDts({ contractName, abi }) {
883
- const factoryName = `${contractName}__factory`;
884
- const ctor = _findConstructor(abi);
885
- const ctorInputs = (ctor && ctor.inputs) || [];
886
- const tupleReg = _collectTupleRegistry(contractName, abi);
887
- const deployArgsSig = ctorInputs.map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`).join(", ");
888
-
889
- const lines = [];
890
- lines.push(`// Auto-generated by sdkgen`);
891
- lines.push(`import { ContractFactory, ContractRunner } from "quantumcoin";`);
892
- lines.push(`import { ${contractName} } from "./${contractName}";`);
893
- lines.push(`import type * as Types from "./types";`);
894
-
895
- const ctorTupleTypes = new Set();
896
- const visit = (p) => {
897
- const t = String(p && p.type ? p.type : "");
898
- const arr = _parseArray(t);
899
- if (arr) return visit({ ...(p || {}), type: arr.inner });
900
- if (t === "tuple") {
901
- const key = _tupleKey(p);
902
- const base = tupleReg.byKey.get(key);
903
- if (base) ctorTupleTypes.add(`${base}Input`);
904
- const comps = Array.isArray(p && p.components) ? p.components : [];
905
- for (const c of comps) visit(c);
906
- }
907
- };
908
- for (const p of ctorInputs) visit(p);
909
- if (ctorTupleTypes.size) {
910
- lines.push(`import type { ${Array.from(ctorTupleTypes).sort().join(", ")} } from "./${contractName}";`);
911
- }
912
- lines.push("");
913
- lines.push(`export declare class ${factoryName} extends ContractFactory {`);
914
- lines.push(` constructor(runner: ContractRunner);`);
915
- lines.push(` deploy(${deployArgsSig}${deployArgsSig ? ", " : ""}overrides?: any): Promise<${contractName}>;`);
916
- lines.push(` static connect(address: string, runner?: ContractRunner): ${contractName};`);
917
- lines.push(`}`);
918
- return lines.join("\n") + "\n";
919
- }
920
-
921
- function _renderIndexTs(contractNames) {
922
- const lines = [];
923
- lines.push(`// Auto-generated by sdkgen`);
924
- lines.push(`export * from "./types";`);
925
- for (const name of contractNames) {
926
- lines.push(`export * from "./${name}";`);
927
- lines.push(`export * from "./${name}__factory";`);
928
- }
929
- return lines.join("\n") + "\n";
930
- }
931
-
932
- function _renderIndexJs(contractNames) {
933
- const lines = [];
934
- lines.push(`// Auto-generated by sdkgen`);
935
- lines.push("");
936
- lines.push(`Object.assign(exports, require("./types"));`);
937
- for (const name of contractNames) {
938
- lines.push(`exports.${name} = require("./${name}").${name};`);
939
- lines.push(`exports.${name}__factory = require("./${name}__factory").${name}__factory;`);
940
- }
941
- lines.push("");
942
- return lines.join("\n");
943
- }
944
-
945
- function _renderIndexDts(contractNames) {
946
- const lines = [];
947
- lines.push(`// Auto-generated by sdkgen`);
948
- lines.push(`export * from "./types";`);
949
- for (const name of contractNames) {
950
- lines.push(`export * from "./${name}";`);
951
- lines.push(`export * from "./${name}__factory";`);
952
- }
953
- return lines.join("\n") + "\n";
954
- }
955
-
956
- /**
957
- * Generate a transactional e2e test file (JavaScript) for the typed contract package.
958
- * The test deploys the contract with constructor args (if any) and invokes one write method.
959
- *
960
- * @param {{ contractName: string, abi: any[] }} opts
961
- * @returns {string}
962
- */
963
- function generateTransactionalTestJs(opts) {
964
- const { contractName, abi } = opts;
965
- const factoryName = `${contractName}__factory`;
966
- const ctor = _findConstructor(abi);
967
- const ctorInputs = ctor.inputs || [];
968
-
969
- const ctorArgsExpr = ctorInputs.map((i) => _solTypeToTestValueExpr(i)).join(", ");
970
-
971
- // ---------------------------------------------------------------------------
972
- // ERC-20 style assertions (optional)
973
- // ---------------------------------------------------------------------------
974
- const fnByName = (name) => (abi || []).find((f) => f && f.type === "function" && f.name === name);
975
- const nameFn = fnByName("name");
976
- const supplyFn = fnByName("totalSupply") || fnByName("supply");
977
- const balanceOfFn = fnByName("balanceOf");
978
-
979
- const isViewNoArgs = (f) =>
980
- !!(
981
- f &&
982
- f.type === "function" &&
983
- (f.stateMutability === "view" || f.stateMutability === "pure") &&
984
- (f.inputs || []).length === 0
985
- );
986
- const isViewBalanceOf = (f) =>
987
- !!(
988
- f &&
989
- f.type === "function" &&
990
- (f.stateMutability === "view" || f.stateMutability === "pure") &&
991
- (f.inputs || []).length === 1 &&
992
- (f.inputs[0].type || "") === "address"
993
- );
994
- const isStringOut = (f) => !!(f && (f.outputs || []).length === 1 && (f.outputs[0].type || "") === "string");
995
- const isUintOut = (f) => !!(f && (f.outputs || []).length === 1 && String(f.outputs[0].type || "").startsWith("uint"));
996
-
997
- const hasErc20Surface =
998
- isViewNoArgs(nameFn) && isStringOut(nameFn) && isViewNoArgs(supplyFn) && isUintOut(supplyFn) && isViewBalanceOf(balanceOfFn) && isUintOut(balanceOfFn);
999
-
1000
- // Common ERC-20 constructor: (string name, string symbol, uint256 initialSupply)
1001
- const isErc20Ctor =
1002
- ctorInputs.length === 3 &&
1003
- (ctorInputs[0].type || "") === "string" &&
1004
- (ctorInputs[1].type || "") === "string" &&
1005
- String(ctorInputs[2].type || "").startsWith("uint");
1006
-
1007
- const erc20TokenName = "TestToken";
1008
- const erc20TokenSymbol = "TT";
1009
- const erc20InitialSupply = 1000;
1010
- const deployArgsExpr = hasErc20Surface && isErc20Ctor ? `${JSON.stringify(erc20TokenName)}, ${JSON.stringify(erc20TokenSymbol)}, ${erc20InitialSupply}` : ctorArgsExpr;
1011
- const supplyMethodName = supplyFn && typeof supplyFn.name === "string" ? supplyFn.name : "totalSupply";
1012
- const erc20Assertions =
1013
- hasErc20Surface && isErc20Ctor
1014
- ? `// ERC-20 assertions (name / supply / balanceOf)
1015
- const nm = await contract.name();
1016
- assert.equal(nm, ${JSON.stringify(erc20TokenName)});
1017
-
1018
- // Generated wrappers already unwrap single-return values to a hard type (bigint for uints).
1019
- const supply = await contract.${supplyMethodName}();
1020
- assert.equal(supply, ${erc20InitialSupply}n);
1021
-
1022
- // Generated wrappers already unwrap single-return values to a hard type (bigint for uints).
1023
- const bal = await contract.balanceOf(wallet.address);
1024
- assert.equal(bal, ${erc20InitialSupply}n);
1025
- `
1026
- : "";
1027
-
1028
- // Pick first view function with no inputs for state reads (optional).
1029
- const viewNoArg = (abi || []).find(
1030
- (f) => f && f.type === "function" && (f.stateMutability === "view" || f.stateMutability === "pure") && (f.inputs || []).length === 0,
1031
- );
1032
-
1033
- // Pick first state-changing function with supported params (prefer 1-arg numeric setter patterns).
1034
- const writeFn =
1035
- (abi || []).find((f) => {
1036
- if (!f || f.type !== "function") return false;
1037
- if (f.stateMutability === "view" || f.stateMutability === "pure") return false;
1038
- if (!_allSupportedParams(f.inputs)) return false;
1039
- return f.name === "set" && (f.inputs || []).length === 1 && (f.inputs[0].type || "").startsWith("uint");
1040
- }) ||
1041
- (abi || []).find(
1042
- (f) => f && f.type === "function" && !(f.stateMutability === "view" || f.stateMutability === "pure") && _allSupportedParams(f.inputs),
1043
- );
1044
-
1045
- const writeName = writeFn ? writeFn.name : null;
1046
- const writeArgsExpr =
1047
- writeFn && (writeFn.inputs || []).length === 1 && (writeFn.inputs[0].type || "").startsWith("uint")
1048
- ? "456"
1049
- : writeFn
1050
- ? (writeFn.inputs || []).map((i) => _solTypeToTestValueExpr(i)).join(", ")
1051
- : "";
1052
-
1053
- const canAssertValueChange =
1054
- !!(
1055
- viewNoArg &&
1056
- (viewNoArg.outputs || []).length === 1 &&
1057
- typeof viewNoArg.outputs[0].type === "string" &&
1058
- (viewNoArg.outputs[0].type.startsWith("uint") || viewNoArg.outputs[0].type.startsWith("int")) &&
1059
- writeFn &&
1060
- (writeFn.inputs || []).length === 1 &&
1061
- typeof writeFn.inputs[0].type === "string" &&
1062
- (writeFn.inputs[0].type.startsWith("uint") || writeFn.inputs[0].type.startsWith("int"))
1063
- );
1064
-
1065
- return `/**
1066
- * @testCategory e2e
1067
- * @blockchainRequired write
1068
- * @description Auto-generated transactional tests for ${contractName}
1069
- *
1070
- * WARNING:
1071
- * - This test uses a HARDCODED TEST WALLET (encrypted JSON + passphrase).
1072
- * - It assumes the wallet has funds on the target network.
1073
- * - It will broadcast real transactions and change chain state.
1074
- */
1075
-
1076
- const { describe, it } = require("node:test");
1077
- const assert = require("node:assert/strict");
1078
-
1079
- const { Initialize } = require("quantumcoin/config");
1080
- const { JsonRpcProvider, Wallet } = require("quantumcoin");
1081
-
1082
- // NOTE: this test file lives at test/e2e/*.js, so package root is two levels up.
1083
- // Require the package root so it works for both TS (dist) and JS (src) packages.
1084
- const { ${contractName}, ${factoryName} } = require("../..");
1085
-
1086
- // Hardcoded test wallet (test-only; never use for real funds)
1087
- const TEST_WALLET_ENCRYPTED_JSON =
1088
- ${JSON.stringify(
1089
- "{\"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}",
1090
- )};
1091
- const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
1092
-
1093
- describe("${contractName} transactional", () => {
1094
- it("deploys and invokes contract", async (t) => {
1095
- const rpcUrl = process.env.QC_RPC_URL;
1096
- if (!rpcUrl) {
1097
- t.skip("QC_RPC_URL not provided");
1098
- return;
1099
- }
1100
-
1101
- const chainId = process.env.QC_CHAIN_ID ? Number(process.env.QC_CHAIN_ID) : 123123;
1102
- await Initialize(null);
1103
-
1104
- const provider = new JsonRpcProvider(rpcUrl, chainId);
1105
- const wallet = Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
1106
-
1107
- const factory = new ${factoryName}(wallet);
1108
- // Build deploy transaction + estimate gas (best-effort).
1109
- const deployTxReq = factory.getDeployTransaction(${deployArgsExpr});
1110
- let deployGasLimit = 600000;
1111
- try {
1112
- const est = await provider.estimateGas({ from: wallet.address, data: deployTxReq.data });
1113
- deployGasLimit = Number(est + 200_000n);
1114
- } catch {
1115
- // Keep fallback. Some RPCs do not support estimateGas for create.
1116
- deployGasLimit = 6_000_000;
1117
- }
1118
- // Some large contracts underestimate gas; apply a sane floor based on bytecode size.
1119
- const bytecodeSize = (${contractName}.bytecode || "").length;
1120
- if (bytecodeSize > 20000 && deployGasLimit < 6_000_000) deployGasLimit = 6_000_000;
1121
-
1122
- const contract = await factory.deploy(${deployArgsExpr}${deployArgsExpr ? ", " : ""}{ gasLimit: deployGasLimit });
1123
-
1124
- const deployTx = contract.deployTransaction();
1125
- assert.ok(deployTx && deployTx.hash);
1126
- const deployReceipt = await deployTx.wait(1, 600_000);
1127
- assert.ok(deployReceipt);
1128
- assert.ok(deployReceipt.blockNumber != null);
1129
-
1130
- const code = await provider.getCode(contract.target, "latest");
1131
- assert.ok(code && code !== "0x");
1132
-
1133
- ${erc20Assertions ? erc20Assertions : `// (no ERC-20 surface detected for extra assertions)`}
1134
-
1135
- ${viewNoArg ? `// Basic view call
1136
- const before = await contract.${viewNoArg.name}();
1137
- void before;` : `// No zero-arg view method detected; deployment is still validated.`}
1138
-
1139
- ${writeName ? `// Write call + wait
1140
- const tx = await contract.${writeName}(${writeArgsExpr}${writeArgsExpr ? ", " : ""}{ gasLimit: 200000 });
1141
- await tx.wait(1, 600_000);` : `// No supported write method detected for auto invocation.`}
1142
-
1143
- ${canAssertValueChange ? `// Assert value change (best-effort; normalize to BigInt)
1144
- const after = await contract.${viewNoArg.name}();
1145
- assert.equal(after, 456n);` : `// No compatible getter+setter pair detected for value assertions.`}
1146
- }, { timeout: 900_000 });
1147
- });
1148
- `;
1149
- }
1150
-
1151
- /**
1152
- * For a single contract ABI, compute deploy args and optional view/write method for e2e.
1153
- * @param {{ contractName: string, abi: any[] }} opts
1154
- * @returns {{ ctorArgsExpr: string, deployArgsExpr: string, viewName: string | null, writeName: string | null, writeArgsExpr: string }}
1155
- */
1156
- function _getContractTestMeta(opts) {
1157
- const { contractName, abi } = opts;
1158
- const ctor = _findConstructor(abi);
1159
- const ctorInputs = ctor.inputs || [];
1160
- const ctorArgsExpr = ctorInputs.map((i) => _solTypeToTestValueExpr(i)).join(", ");
1161
- const viewNoArg = (abi || []).find(
1162
- (f) => f && f.type === "function" && (f.stateMutability === "view" || f.stateMutability === "pure") && (f.inputs || []).length === 0,
1163
- );
1164
- const writeFn =
1165
- (abi || []).find(
1166
- (f) =>
1167
- f && f.type === "function" && !(f.stateMutability === "view" || f.stateMutability === "pure") && f.name === "set" &&
1168
- (f.inputs || []).length === 1 &&
1169
- (f.inputs[0].type || "").startsWith("uint") &&
1170
- _allSupportedParams(f.inputs),
1171
- ) ||
1172
- (abi || []).find(
1173
- (f) => f && f.type === "function" && !(f.stateMutability === "view" || f.stateMutability === "pure") && _allSupportedParams(f.inputs),
1174
- );
1175
- const writeArgsExpr =
1176
- writeFn && (writeFn.inputs || []).length === 1 && (writeFn.inputs[0].type || "").startsWith("uint")
1177
- ? "456"
1178
- : writeFn
1179
- ? (writeFn.inputs || []).map((i) => _solTypeToTestValueExpr(i)).join(", ")
1180
- : "";
1181
- return {
1182
- ctorArgsExpr,
1183
- deployArgsExpr: ctorArgsExpr,
1184
- viewName: viewNoArg ? viewNoArg.name : null,
1185
- writeName: writeFn ? writeFn.name : null,
1186
- writeArgsExpr,
1187
- };
1188
- }
1189
-
1190
- /**
1191
- * Generate a single transactional e2e test that deploys and invokes methods on ALL contracts.
1192
- * Used when the package has multiple contracts so one test exercises every contract.
1193
- *
1194
- * @param {{ artifacts: Array<{ contractName: string, abi: any[] }> }} opts
1195
- * @returns {string}
1196
- */
1197
- function generateAllContractsTransactionalTestJs(opts) {
1198
- const { artifacts } = opts;
1199
- if (!artifacts || artifacts.length < 2) {
1200
- throw new Error("generateAllContractsTransactionalTestJs requires at least 2 artifacts");
1201
- }
1202
-
1203
- const requireNames = [];
1204
- for (const a of artifacts) {
1205
- requireNames.push(a.contractName, `${a.contractName}__factory`);
1206
- }
1207
- const requireLine = `const { ${requireNames.join(", ")} } = require("../..");`;
1208
-
1209
- const blocks = [];
1210
- for (const a of artifacts) {
1211
- const meta = _getContractTestMeta({ contractName: a.contractName, abi: a.abi });
1212
- const factoryName = `${a.contractName}__factory`;
1213
- const deployRhs =
1214
- meta.ctorArgsExpr
1215
- ? `await (new ${factoryName}(wallet)).deploy(${meta.deployArgsExpr}, { gasLimit: deployGasLimit })`
1216
- : `await (new ${factoryName}(wallet)).deploy({ gasLimit: deployGasLimit })`;
1217
- let invokeBlock = "";
1218
- if (meta.viewName) {
1219
- invokeBlock += `const _v = await ${a.contractName}Inst.${meta.viewName}();\n void _v;\n`;
1220
- }
1221
- if (meta.writeName) {
1222
- invokeBlock += `const _tx = await ${a.contractName}Inst.${meta.writeName}(${meta.writeArgsExpr}${meta.writeArgsExpr ? ", " : ""}{ gasLimit: 200000 });\n await _tx.wait(1, 600_000);\n`;
1223
- }
1224
- blocks.push({
1225
- contractName: a.contractName,
1226
- factoryName,
1227
- deployRhs,
1228
- invokeBlock: invokeBlock || `// No view/write method detected for ${a.contractName}\n`,
1229
- });
1230
- }
1231
-
1232
- const deployAndInvokeBlocks = blocks
1233
- .map(
1234
- (b) => `
1235
- let ${b.contractName}Inst = ${b.deployRhs};
1236
- const _deployTx${b.contractName} = ${b.contractName}Inst.deployTransaction();
1237
- assert.ok(_deployTx${b.contractName} && _deployTx${b.contractName}.hash);
1238
- await _deployTx${b.contractName}.wait(1, 600_000);
1239
- ${b.invokeBlock.trim().split("\n").join("\n ")}`,
1240
- )
1241
- .join("\n");
1242
-
1243
- const firstMeta = _getContractTestMeta({ contractName: artifacts[0].contractName, abi: artifacts[0].abi });
1244
- const firstCtorArgs = firstMeta.ctorArgsExpr ? firstMeta.ctorArgsExpr + ", " : "";
1245
-
1246
- return `/**
1247
- * @testCategory e2e
1248
- * @blockchainRequired write
1249
- * @description Auto-generated test that deploys and invokes all contracts in this package.
1250
- *
1251
- * WARNING:
1252
- * - This test uses a HARDCODED TEST WALLET (encrypted JSON + passphrase).
1253
- * - It assumes the wallet has funds on the target network.
1254
- * - It will broadcast real transactions and change chain state.
1255
- */
1256
-
1257
- const { describe, it } = require("node:test");
1258
- const assert = require("node:assert/strict");
1259
-
1260
- const { Initialize } = require("quantumcoin/config");
1261
- const { JsonRpcProvider, Wallet } = require("quantumcoin");
1262
-
1263
- ${requireLine}
1264
-
1265
- // Hardcoded test wallet (test-only; never use for real funds)
1266
- const TEST_WALLET_ENCRYPTED_JSON =
1267
- ${JSON.stringify(
1268
- "{\"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}",
1269
- )};
1270
- const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
1271
-
1272
- describe("all contracts", () => {
1273
- it("deploys and invokes all contracts", async (t) => {
1274
- const rpcUrl = process.env.QC_RPC_URL;
1275
- if (!rpcUrl) {
1276
- t.skip("QC_RPC_URL not provided");
1277
- return;
1278
- }
1279
-
1280
- const chainId = process.env.QC_CHAIN_ID ? Number(process.env.QC_CHAIN_ID) : 123123;
1281
- await Initialize(null);
1282
-
1283
- const provider = new JsonRpcProvider(rpcUrl, chainId);
1284
- const wallet = Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
1285
-
1286
- let deployGasLimit = 600000;
1287
- try {
1288
- const firstFactory = new ${artifacts[0].contractName}__factory(wallet);
1289
- const sampleReq = firstFactory.getDeployTransaction(${firstMeta.ctorArgsExpr || ""});
1290
- const est = await provider.estimateGas({ from: wallet.address, data: sampleReq.data });
1291
- deployGasLimit = Number(est + 200_000n);
1292
- } catch {
1293
- deployGasLimit = 6_000_000;
1294
- }
1295
- ${deployAndInvokeBlocks}
1296
- }, { timeout: ${Math.max(900_000, 300_000 * artifacts.length)}});
1297
- });
1298
- `;
1299
- }
1300
-
1301
- /**
1302
- * @typedef {Object} GenerateOptions
1303
- * @property {string} abiPath
1304
- * @property {string} binPath
1305
- * @property {string} outDir
1306
- * @property {string} contractName
1307
- * @property {string=} packageName
1308
- * @property {boolean=} createPackage
1309
- * @property {Record<string,string>=} dependencies
1310
- */
1311
-
1312
- /**
1313
- * Generate multiple typed contract files.
1314
- * @param {{ outDir: string, artifacts: Array<{ contractName: string, abi: any[], bytecode: string }> }} opts
1315
- * @returns {{ contracts: Array<{ contractFile: string, factoryFile: string }>, typesFile: string, indexFile: string }}
1316
- */
1317
- function generateFromArtifacts(opts) {
1318
- _ensureDir(opts.outDir);
1319
- const lang = (opts && opts.lang) || "ts"; // "ts" | "js"
1320
- if (lang !== "ts" && lang !== "js") {
1321
- throw new Error(`Unsupported generator lang: ${lang}`);
1322
- }
1323
-
1324
- const contractNames = opts.artifacts.map((a) => a.contractName);
1325
-
1326
- const typesFile = path.join(opts.outDir, lang === "ts" ? `types.ts` : `types.js`);
1327
- const indexFile = path.join(opts.outDir, lang === "ts" ? `index.ts` : `index.js`);
1328
-
1329
- fs.writeFileSync(typesFile, lang === "ts" ? _typesTs() : _typesJs(), "utf8");
1330
-
1331
- // For JS generation, we also emit .d.ts files (TS declarations) so JS packages remain typed.
1332
- const typesDtsFile = lang === "js" ? path.join(opts.outDir, `types.d.ts`) : null;
1333
- const indexDtsFile = lang === "js" ? path.join(opts.outDir, `index.d.ts`) : null;
1334
- if (lang === "js") {
1335
- fs.writeFileSync(typesDtsFile, _typesDts(), "utf8");
1336
- }
1337
-
1338
- const contracts = [];
1339
- for (const a of opts.artifacts) {
1340
- const contractFile = path.join(opts.outDir, `${a.contractName}.${lang === "ts" ? "ts" : "js"}`);
1341
- const factoryFile = path.join(opts.outDir, `${a.contractName}__factory.${lang === "ts" ? "ts" : "js"}`);
1342
- const contractDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}.d.ts`) : null;
1343
- const factoryDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}__factory.d.ts`) : null;
1344
-
1345
- if (lang === "ts") {
1346
- fs.writeFileSync(
1347
- contractFile,
1348
- _renderContractTs({ contractName: a.contractName, abi: a.abi, bytecode: a.bytecode, docs: a.docs || null }),
1349
- "utf8",
1350
- );
1351
- fs.writeFileSync(factoryFile, _renderFactoryTs({ contractName: a.contractName, abi: a.abi }), "utf8");
1352
- } else {
1353
- fs.writeFileSync(
1354
- contractFile,
1355
- _renderContractJs({ contractName: a.contractName, abi: a.abi, bytecode: a.bytecode, docs: a.docs || null }),
1356
- "utf8",
1357
- );
1358
- fs.writeFileSync(factoryFile, _renderFactoryJs({ contractName: a.contractName, abi: a.abi }), "utf8");
1359
- }
1360
-
1361
- if (lang === "js") {
1362
- fs.writeFileSync(contractDtsFile, _renderContractDts({ contractName: a.contractName, abi: a.abi }), "utf8");
1363
- fs.writeFileSync(factoryDtsFile, _renderFactoryDts({ contractName: a.contractName, abi: a.abi }), "utf8");
1364
- }
1365
- contracts.push({ contractFile, factoryFile });
1366
- }
1367
-
1368
- fs.writeFileSync(indexFile, lang === "ts" ? _renderIndexTs(contractNames) : _renderIndexJs(contractNames), "utf8");
1369
- if (lang === "js") {
1370
- fs.writeFileSync(indexDtsFile, _renderIndexDts(contractNames), "utf8");
1371
- }
1372
-
1373
- if (lang === "js") {
1374
- return { contracts, typesFile, indexFile, typesDtsFile, indexDtsFile };
1375
- }
1376
- return { contracts, typesFile, indexFile };
1377
- }
1378
-
1379
- /**
1380
- * Generate typed contract files.
1381
- * @param {GenerateOptions} opts
1382
- * @returns {{ contractFile: string, factoryFile: string, typesFile: string, indexFile: string }}
1383
- */
1384
- function generate(opts) {
1385
- const abi = _readJson(opts.abiPath);
1386
- const bytecodeRaw = fs.readFileSync(opts.binPath, "utf8").trim();
1387
- const bytecode = bytecodeRaw.startsWith("0x") ? bytecodeRaw : "0x" + bytecodeRaw;
1388
-
1389
- _ensureDir(opts.outDir);
1390
- const contractName = opts.contractName;
1391
-
1392
- const res = generateFromArtifacts({
1393
- outDir: opts.outDir,
1394
- artifacts: [{ contractName, abi, bytecode }],
1395
- lang: opts.lang || "ts",
1396
- });
1397
-
1398
- const contractFile = res.contracts[0].contractFile;
1399
- const factoryFile = res.contracts[0].factoryFile;
1400
- return { contractFile, factoryFile, typesFile: res.typesFile, indexFile: res.indexFile };
1401
- }
1402
-
1403
- module.exports = { generate, generateFromArtifacts, generateTransactionalTestJs, generateAllContractsTransactionalTestJs };
1404
-
1
+ /**
2
+ * @fileoverview Typed contract generator (SPEC.md section 15).
3
+ *
4
+ * Supports generating:
5
+ * - TypeScript source (`.ts`)
6
+ * - JavaScript source (`.js`) + TypeScript declaration files (`.d.ts`)
7
+ *
8
+ * It is designed to be invoked by `generate-sdk.js` (CLI) and
9
+ * can also be imported as a library.
10
+ */
11
+
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+
15
+ function _ensureDir(p) {
16
+ fs.mkdirSync(p, { recursive: true });
17
+ }
18
+
19
+ function _readJson(filePath) {
20
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
21
+ }
22
+
23
+ function _stripArraySuffixes(s) {
24
+ let out = String(s || "");
25
+ while (out.endsWith("]")) {
26
+ out = out.slice(0, out.lastIndexOf("["));
27
+ }
28
+ return out;
29
+ }
30
+
31
+ function _parseArray(type) {
32
+ const t = String(type || "");
33
+ const idx = t.lastIndexOf("[");
34
+ if (idx < 0 || !t.endsWith("]")) return null;
35
+ const inner = t.slice(0, idx);
36
+ const bracket = t.slice(idx + 1, t.length - 1); // "" => dynamic
37
+ const fixedLen = bracket.length ? Number(bracket) : null;
38
+ return { inner, fixedLen: fixedLen != null && Number.isFinite(fixedLen) ? fixedLen : null };
39
+ }
40
+
41
+ function _tupleBaseNameFromInternalType(contractName, internalType) {
42
+ const raw = String(internalType || "");
43
+ if (!raw) return null;
44
+ const cleaned = _stripArraySuffixes(raw);
45
+ const m = cleaned.match(/struct\s+([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)/);
46
+ if (m && m[2]) return m[2];
47
+ // Some compilers omit "struct" keyword.
48
+ const m2 = cleaned.match(/^([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)$/);
49
+ if (m2 && m2[2]) return m2[2];
50
+ // Fallback: last segment
51
+ const parts = cleaned.split(".");
52
+ const last = parts[parts.length - 1];
53
+ if (last && last !== contractName) return last;
54
+ return null;
55
+ }
56
+
57
+ function _tupleKey(param) {
58
+ /** @param {any} p */
59
+ function norm(p) {
60
+ const out = {
61
+ name: String(p && p.name ? p.name : ""),
62
+ type: String(p && p.type ? p.type : ""),
63
+ internalType: String(p && p.internalType ? p.internalType : ""),
64
+ components: [],
65
+ };
66
+ const comps = Array.isArray(p && p.components) ? p.components : [];
67
+ out.components = comps.map((c) => norm(c));
68
+ return out;
69
+ }
70
+ return JSON.stringify(norm(param || {}));
71
+ }
72
+
73
+ function _collectTupleRegistry(contractName, abi) {
74
+ const byKey = new Map(); // key -> baseName
75
+ const usedNames = new Map(); // baseName -> key
76
+ let counter = 0;
77
+
78
+ /** @param {any} param */
79
+ function ensureTuple(param) {
80
+ const key = _tupleKey(param);
81
+ if (byKey.has(key)) return byKey.get(key);
82
+
83
+ const suggested =
84
+ _tupleBaseNameFromInternalType(contractName, param && param.internalType) || `${contractName}Tuple${++counter}`;
85
+ let baseName = suggested;
86
+ if (usedNames.has(baseName) && usedNames.get(baseName) !== key) {
87
+ let n = 1;
88
+ while (usedNames.has(`${suggested}_${n}`) && usedNames.get(`${suggested}_${n}`) !== key) n++;
89
+ baseName = `${suggested}_${n}`;
90
+ }
91
+ byKey.set(key, baseName);
92
+ usedNames.set(baseName, key);
93
+
94
+ // Recurse to nested tuples.
95
+ const comps = Array.isArray(param && param.components) ? param.components : [];
96
+ for (const c of comps) visitParam(c);
97
+ return baseName;
98
+ }
99
+
100
+ /** @param {any} param */
101
+ function visitParam(param) {
102
+ const type = String(param && param.type ? param.type : "");
103
+ if (!type) return;
104
+ const arr = _parseArray(type);
105
+ if (arr) {
106
+ visitParam({ ...(param || {}), type: arr.inner });
107
+ return;
108
+ }
109
+ if (type === "tuple") {
110
+ ensureTuple(param);
111
+ return;
112
+ }
113
+ }
114
+
115
+ for (const f of abi || []) {
116
+ if (!f || typeof f !== "object") continue;
117
+ const inputs = Array.isArray(f.inputs) ? f.inputs : [];
118
+ const outputs = Array.isArray(f.outputs) ? f.outputs : [];
119
+ for (const p of inputs) visitParam(p);
120
+ for (const p of outputs) visitParam(p);
121
+ }
122
+
123
+ return { byKey };
124
+ }
125
+
126
+ function _solParamToTs(param, mode, tupleReg) {
127
+ const type = String(param && param.type ? param.type : "");
128
+ const m = mode === "output" ? "output" : "input";
129
+
130
+ const arr = _parseArray(type);
131
+ if (arr) {
132
+ const innerParam = { ...(param || {}), type: arr.inner };
133
+ const innerTs = _solParamToTs(innerParam, mode, tupleReg);
134
+ if (arr.fixedLen != null) return `Types.SolFixedArray<${innerTs}, ${arr.fixedLen}>`;
135
+ return `Types.SolArray<${innerTs}>`;
136
+ }
137
+
138
+ if (type === "tuple") {
139
+ const key = _tupleKey(param);
140
+ const baseName = tupleReg && tupleReg.byKey ? tupleReg.byKey.get(key) : null;
141
+ const n = baseName || "Tuple";
142
+ return `${n}${m === "input" ? "Input" : "Output"}`;
143
+ }
144
+
145
+ // Elementary types (hard typed)
146
+ if (type === "address") return m === "input" ? "Types.AddressLike" : "Types.SolAddress";
147
+ if (type === "bool") return "boolean";
148
+ if (type === "string") return "string";
149
+ if (type === "bytes") return m === "input" ? "Types.BytesLike" : "Types.HexString";
150
+
151
+ const mBytesN = type.match(/^bytes(\d+)$/);
152
+ if (mBytesN) {
153
+ const n = Number(mBytesN[1]);
154
+ if (n === 32) return m === "input" ? "Types.Bytes32Like" : "Types.Bytes32";
155
+ if (Number.isFinite(n) && n >= 1 && n <= 32) return m === "input" ? `Types.Bytes${n}Like` : `Types.Bytes${n}`;
156
+ }
157
+
158
+ const mUint = type === "uint" ? ["uint", "256"] : type.match(/^uint(\d+)$/);
159
+ if (mUint) {
160
+ const bits = type === "uint" ? 256 : Number(mUint[1]);
161
+ const b = Number.isFinite(bits) ? bits : 256;
162
+ return m === "input" ? `Types.Uint${b}Like` : `Types.Uint${b}`;
163
+ }
164
+
165
+ const mInt = type === "int" ? ["int", "256"] : type.match(/^int(\d+)$/);
166
+ if (mInt) {
167
+ const bits = type === "int" ? 256 : Number(mInt[1]);
168
+ const b = Number.isFinite(bits) ? bits : 256;
169
+ return m === "input" ? `Types.Int${b}Like` : `Types.Int${b}`;
170
+ }
171
+
172
+ // Fallback (unknown type)
173
+ return "any";
174
+ }
175
+
176
+ function _solParamToJsDoc(param, mode, tupleReg) {
177
+ const type = String(param && param.type ? param.type : "");
178
+ const m = mode === "output" ? "output" : "input";
179
+
180
+ const arr = _parseArray(type);
181
+ if (arr) {
182
+ const innerParam = { ...(param || {}), type: arr.inner };
183
+ const inner = _solParamToJsDoc(innerParam, mode, tupleReg);
184
+ return `Array<${inner}>`;
185
+ }
186
+
187
+ if (type === "tuple") {
188
+ const key = _tupleKey(param);
189
+ const baseName = tupleReg && tupleReg.byKey ? tupleReg.byKey.get(key) : null;
190
+ const n = baseName || "Tuple";
191
+ return `${n}${m === "input" ? "Input" : "Output"}`;
192
+ }
193
+
194
+ if (type === "address") {
195
+ return m === "input" ? `import("quantumcoin/types").AddressLike` : `import("quantumcoin/types").SolAddress`;
196
+ }
197
+ if (type === "bool") return "boolean";
198
+ if (type === "string") return "string";
199
+ if (type === "bytes") {
200
+ return m === "input" ? `import("quantumcoin/types").BytesLike` : `import("quantumcoin/types").HexString`;
201
+ }
202
+
203
+ const mBytesN = type.match(/^bytes(\d+)$/);
204
+ if (mBytesN) {
205
+ const n = Number(mBytesN[1]);
206
+ if (n === 32) {
207
+ return m === "input" ? `import("quantumcoin/types").Bytes32Like` : `import("quantumcoin/types").Bytes32`;
208
+ }
209
+ if (Number.isFinite(n) && n >= 1 && n <= 32) {
210
+ return m === "input"
211
+ ? `import("quantumcoin/types").Bytes${n}Like`
212
+ : `import("quantumcoin/types").Bytes${n}`;
213
+ }
214
+ }
215
+
216
+ const mUint = type === "uint" ? ["uint", "256"] : type.match(/^uint(\d+)$/);
217
+ if (mUint) {
218
+ const bits = type === "uint" ? 256 : Number(mUint[1]);
219
+ const b = Number.isFinite(bits) ? bits : 256;
220
+ return m === "input"
221
+ ? `import("quantumcoin/types").Uint${b}Like`
222
+ : `import("quantumcoin/types").Uint${b}`;
223
+ }
224
+
225
+ const mInt = type === "int" ? ["int", "256"] : type.match(/^int(\d+)$/);
226
+ if (mInt) {
227
+ const bits = type === "int" ? 256 : Number(mInt[1]);
228
+ const b = Number.isFinite(bits) ? bits : 256;
229
+ return m === "input"
230
+ ? `import("quantumcoin/types").Int${b}Like`
231
+ : `import("quantumcoin/types").Int${b}`;
232
+ }
233
+
234
+ return "any";
235
+ }
236
+
237
+ function _renderTupleTypeDefs(contractName, abi, tupleReg) {
238
+ const lines = [];
239
+ void contractName;
240
+
241
+ function _findTupleParamByKey(key) {
242
+ /** @param {any} p */
243
+ function walkParam(p) {
244
+ if (!p || typeof p !== "object") return null;
245
+ const t = String(p.type || "");
246
+ if (!t) return null;
247
+ const arr = _parseArray(t);
248
+ if (arr) return walkParam({ ...(p || {}), type: arr.inner });
249
+ if (t === "tuple") {
250
+ if (_tupleKey(p) === key) return p;
251
+ const comps = Array.isArray(p.components) ? p.components : [];
252
+ for (const c of comps) {
253
+ const found = walkParam(c);
254
+ if (found) return found;
255
+ }
256
+ }
257
+ return null;
258
+ }
259
+
260
+ for (const f of abi || []) {
261
+ if (!f || typeof f !== "object") continue;
262
+ const inputs = Array.isArray(f.inputs) ? f.inputs : [];
263
+ const outputs = Array.isArray(f.outputs) ? f.outputs : [];
264
+ for (const p of inputs) {
265
+ const found = walkParam(p);
266
+ if (found) return found;
267
+ }
268
+ for (const p of outputs) {
269
+ const found = walkParam(p);
270
+ if (found) return found;
271
+ }
272
+ }
273
+ return null;
274
+ }
275
+
276
+ /** @param {any} param */
277
+ function tupleFields(param, mode) {
278
+ const comps = Array.isArray(param && param.components) ? param.components : [];
279
+ const fields = [];
280
+ for (let i = 0; i < comps.length; i++) {
281
+ const c = comps[i];
282
+ const field = _safeIdent((c && c.name) || `field${i}`);
283
+ fields.push(` ${field}: ${_solParamToTs(c, mode, tupleReg)};`);
284
+ }
285
+ return fields;
286
+ }
287
+
288
+ // Render each known tuple once (as Input + Output).
289
+ for (const [key, baseName] of tupleReg.byKey.entries()) {
290
+ const param = _findTupleParamByKey(key);
291
+ if (!param) continue;
292
+
293
+ lines.push(`export type ${baseName}Input = {`);
294
+ lines.push(...tupleFields(param, "input"));
295
+ lines.push(`};`);
296
+ lines.push(``);
297
+ lines.push(`export type ${baseName}Output = {`);
298
+ lines.push(...tupleFields(param, "output"));
299
+ lines.push(`};`);
300
+ lines.push(``);
301
+ }
302
+
303
+ return lines.length ? lines.join("\n") + "\n" : "";
304
+ }
305
+
306
+ function _renderTupleJsDocTypeDefs(contractName, abi, tupleReg) {
307
+ void contractName;
308
+ if (!tupleReg || !tupleReg.byKey || tupleReg.byKey.size === 0) return "";
309
+
310
+ const lines = [];
311
+
312
+ /** @param {string} key */
313
+ function findTupleParamByKey(key) {
314
+ /** @param {any} p */
315
+ function walkParam(p) {
316
+ if (!p || typeof p !== "object") return null;
317
+ const t = String(p.type || "");
318
+ if (!t) return null;
319
+ const arr = _parseArray(t);
320
+ if (arr) return walkParam({ ...(p || {}), type: arr.inner });
321
+ if (t === "tuple") {
322
+ if (_tupleKey(p) === key) return p;
323
+ const comps = Array.isArray(p.components) ? p.components : [];
324
+ for (const c of comps) {
325
+ const found = walkParam(c);
326
+ if (found) return found;
327
+ }
328
+ }
329
+ return null;
330
+ }
331
+
332
+ for (const f of abi || []) {
333
+ if (!f || typeof f !== "object") continue;
334
+ const inputs = Array.isArray(f.inputs) ? f.inputs : [];
335
+ const outputs = Array.isArray(f.outputs) ? f.outputs : [];
336
+ for (const p of inputs) {
337
+ const found = walkParam(p);
338
+ if (found) return found;
339
+ }
340
+ for (const p of outputs) {
341
+ const found = walkParam(p);
342
+ if (found) return found;
343
+ }
344
+ }
345
+ return null;
346
+ }
347
+
348
+ function renderTypedef(typeName, tupleParam, mode) {
349
+ const comps = Array.isArray(tupleParam && tupleParam.components) ? tupleParam.components : [];
350
+ lines.push("/**");
351
+ lines.push(` * @typedef {Object} ${typeName}`);
352
+ for (let i = 0; i < comps.length; i++) {
353
+ const c = comps[i];
354
+ const field = _safeIdent((c && c.name) || `field${i}`);
355
+ lines.push(` * @property {${_solParamToJsDoc(c, mode, tupleReg)}} ${field}`);
356
+ }
357
+ lines.push(" */");
358
+ lines.push("");
359
+ }
360
+
361
+ for (const [key, baseName] of tupleReg.byKey.entries()) {
362
+ const param = findTupleParamByKey(key);
363
+ if (!param) continue;
364
+ renderTypedef(`${baseName}Input`, param, "input");
365
+ renderTypedef(`${baseName}Output`, param, "output");
366
+ }
367
+
368
+ return lines.join("\n");
369
+ }
370
+
371
+ function _cap(s) {
372
+ return s ? s[0].toUpperCase() + s.slice(1) : s;
373
+ }
374
+
375
+ function _safeIdent(name) {
376
+ return (name || "arg").replace(/[^a-zA-Z0-9_]/g, "_");
377
+ }
378
+
379
+ function _findConstructor(abi) {
380
+ const ctor = abi.find((f) => f && f.type === "constructor");
381
+ return ctor || { type: "constructor", inputs: [] };
382
+ }
383
+
384
+ function _solTypeToTestValueExpr(param) {
385
+ const type = typeof param === "string" ? param : String(param && param.type ? param.type : "");
386
+ const internalType = typeof param === "object" && param ? String(param.internalType || "") : "";
387
+
388
+ // Arrays (dynamic or fixed)
389
+ if (type.endsWith("]")) {
390
+ const inner = type.slice(0, type.lastIndexOf("["));
391
+ const bracket = type.slice(type.lastIndexOf("[") + 1, type.length - 1);
392
+ const isFixed = bracket.length > 0;
393
+ const fixedLen = isFixed ? Number(bracket) : 0;
394
+ const elemParam = { ...(param || {}), type: inner };
395
+
396
+ const elemExpr = _solTypeToTestValueExpr(elemParam);
397
+ if (isFixed && Number.isFinite(fixedLen) && fixedLen > 0) {
398
+ // Fixed arrays MUST match the exact declared length.
399
+ // Use Array(len).fill(expr) to keep source size reasonable.
400
+ return `Array(${fixedLen}).fill(${elemExpr})`;
401
+ }
402
+ return `[${elemExpr}]`;
403
+ }
404
+
405
+ if (type === "address") return "wallet.address";
406
+ if (type === "bool") return "true";
407
+ if (type === "string") return JSON.stringify("hello");
408
+ if (type === "bytes") return JSON.stringify("0x1234");
409
+
410
+ // Fixed-size bytes
411
+ const mBytesN = type.match(/^bytes(\d+)$/);
412
+ if (mBytesN) {
413
+ const n = Number(mBytesN[1]);
414
+ if (Number.isFinite(n) && n >= 1 && n <= 32) return JSON.stringify(`0x${"11".repeat(n)}`);
415
+ }
416
+
417
+ // NOTE: quantum-coin-js-sdk WASM interop does not accept BigInt values directly.
418
+ // Use plain numbers/strings for ints/uints.
419
+ // Enums are ABI-encoded as uints but Solidity will revert if the value is out of range.
420
+ if (type.startsWith("uint") && /\benum\b/.test(internalType)) return "1";
421
+ if (type.startsWith("uint")) return "123";
422
+ if (type.startsWith("int")) return "-123";
423
+
424
+ // Tuple / struct
425
+ if (type === "tuple") {
426
+ const comps = Array.isArray(param && param.components) ? param.components : [];
427
+ if (comps.length === 0) return "{}";
428
+ const fields = comps.map((c, idx) => {
429
+ const name = c && typeof c.name === "string" && c.name ? c.name : `field${idx}`;
430
+ return `${JSON.stringify(name)}: ${_solTypeToTestValueExpr(c)}`;
431
+ });
432
+ return `{ ${fields.join(", ")} }`;
433
+ }
434
+
435
+ // Fallback
436
+ return "undefined";
437
+ }
438
+
439
+ function _isSupportedType(type) {
440
+ if (type.endsWith("]")) return _isSupportedType(type.slice(0, type.lastIndexOf("[")));
441
+ return (
442
+ type === "address" ||
443
+ type === "bool" ||
444
+ type === "string" ||
445
+ type === "bytes" ||
446
+ type === "bytes32" ||
447
+ type.startsWith("uint") ||
448
+ type.startsWith("int")
449
+ );
450
+ }
451
+
452
+ function _allSupportedParams(inputs) {
453
+ return (inputs || []).every((i) => i && typeof i.type === "string" && _isSupportedType(i.type));
454
+ }
455
+
456
+ function _typesTs() {
457
+ return (
458
+ `// Auto-generated by sdkgen\n\n` +
459
+ `// Re-export ALL Solidity-related types from quantumcoin.\n` +
460
+ `export type * from "quantumcoin/types";\n`
461
+ );
462
+ }
463
+
464
+ function _typesJs() {
465
+ return `// Auto-generated by sdkgen\n\n` + `module.exports = {};\n`;
466
+ }
467
+
468
+ function _typesDts() {
469
+ return (
470
+ `// Auto-generated by sdkgen\n\n` +
471
+ `export type * from "quantumcoin/types";\n`
472
+ );
473
+ }
474
+
475
+ function _renderContractTs({ contractName, abi, bytecode, docs }) {
476
+ const functions = (abi || []).filter((f) => f && f.type === "function");
477
+ const tupleReg = _collectTupleRegistry(contractName, abi);
478
+ const txFns = functions.filter((f) => (f.stateMutability || "") !== "view" && (f.stateMutability || "") !== "pure");
479
+
480
+ const contractTsLines = [];
481
+ contractTsLines.push(`// Auto-generated by sdkgen`);
482
+ contractTsLines.push(`import { Contract, ContractTransactionResponse, ContractRunner, TransactionResponse } from "quantumcoin";`);
483
+ contractTsLines.push(`import type * as Types from "./types";`);
484
+ contractTsLines.push(``);
485
+ contractTsLines.push(_renderTupleTypeDefs(contractName, abi, tupleReg).trimEnd());
486
+ contractTsLines.push(``);
487
+ contractTsLines.push(`/**`);
488
+ contractTsLines.push(` * ${contractName} - A typed contract interface for ${contractName}`);
489
+ if (docs && typeof docs.contract === "string" && docs.contract.trim()) {
490
+ contractTsLines.push(` *`);
491
+ for (const line of docs.contract.split(/\r?\n/g)) {
492
+ const t = line.trim();
493
+ if (!t) continue;
494
+ contractTsLines.push(` * ${t}`);
495
+ }
496
+ }
497
+ contractTsLines.push(` */`);
498
+ contractTsLines.push(`export class ${contractName} extends Contract {`);
499
+ contractTsLines.push(` static readonly abi = ${JSON.stringify(abi, null, 2)} as const;`);
500
+ contractTsLines.push(` static readonly bytecode = ${JSON.stringify(bytecode)};`);
501
+ contractTsLines.push(``);
502
+ contractTsLines.push(` static connect(address: string, runner?: ContractRunner): ${contractName} {`);
503
+ contractTsLines.push(` return new ${contractName}(address, runner);`);
504
+ contractTsLines.push(` }`);
505
+ contractTsLines.push(``);
506
+ contractTsLines.push(` constructor(address: string, runner?: ContractRunner, _deployTx?: TransactionResponse) {`);
507
+ contractTsLines.push(` super(address, ${contractName}.abi as any, runner as any, ${contractName}.bytecode);`);
508
+ contractTsLines.push(` // @ts-expect-error internal attach`);
509
+ contractTsLines.push(` this._deployTx = _deployTx;`);
510
+ if (txFns.length) {
511
+ contractTsLines.push(``);
512
+ contractTsLines.push(` // Typed populateTransaction helpers (offline signing / sendRawTransaction flows)`);
513
+ contractTsLines.push(` this.populateTransaction = {`);
514
+ for (const fn of txFns) {
515
+ const name = fn.name;
516
+ const inputs = fn.inputs || [];
517
+ const argsSig = inputs
518
+ .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
519
+ .join(", ");
520
+ const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
521
+ contractTsLines.push(
522
+ ` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides?: any): Promise<import("quantumcoin").TransactionRequest> => {`,
523
+ );
524
+ contractTsLines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
525
+ contractTsLines.push(` return { to: this.address, data, ...(overrides || {}) };`);
526
+ contractTsLines.push(` },`);
527
+ }
528
+ contractTsLines.push(` } as any;`);
529
+ }
530
+ contractTsLines.push(` }`);
531
+
532
+ for (const fn of functions) {
533
+ const name = fn.name;
534
+ const inputs = fn.inputs || [];
535
+ const outputs = fn.outputs || [];
536
+ const argsSig = inputs
537
+ .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
538
+ .join(", ");
539
+ const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
540
+
541
+ const mut = fn.stateMutability || "";
542
+ const isView = mut === "view" || mut === "pure";
543
+
544
+ let returnTs;
545
+ if (isView) {
546
+ if (outputs.length === 0) returnTs = "Promise<void>";
547
+ else if (outputs.length === 1) returnTs = `Promise<${_solParamToTs(outputs[0], "output", tupleReg)}>`;
548
+ else returnTs = `Promise<[${outputs.map((o) => _solParamToTs(o, "output", tupleReg)).join(", ")}]>`;
549
+ } else {
550
+ returnTs = "Promise<ContractTransactionResponse>";
551
+ }
552
+
553
+ contractTsLines.push(``);
554
+ contractTsLines.push(` /**`);
555
+ contractTsLines.push(` * ${name}`);
556
+ const fnDoc = docs && docs.functions && typeof docs.functions[name] === "string" ? docs.functions[name] : "";
557
+ if (fnDoc && fnDoc.trim()) {
558
+ contractTsLines.push(` *`);
559
+ for (const line of fnDoc.split(/\r?\n/g)) {
560
+ const t = line.trim();
561
+ if (!t) continue;
562
+ contractTsLines.push(` * ${t}`);
563
+ }
564
+ }
565
+ contractTsLines.push(` */`);
566
+ if (isView) {
567
+ contractTsLines.push(` async ${name}(${argsSig}): ${returnTs} {`);
568
+ contractTsLines.push(` const res = await this.call(${JSON.stringify(name)}, [${argsNames}]);`);
569
+ if (outputs.length === 0) {
570
+ contractTsLines.push(` void res;`);
571
+ contractTsLines.push(` return;`);
572
+ } else if (outputs.length === 1) {
573
+ contractTsLines.push(` return (Array.isArray(res) ? res[0] : res) as unknown as ${_solParamToTs(outputs[0], "output", tupleReg)};`);
574
+ } else {
575
+ contractTsLines.push(
576
+ ` return res as unknown as [${outputs.map((o) => _solParamToTs(o, "output", tupleReg)).join(", ")}];`,
577
+ );
578
+ }
579
+ contractTsLines.push(` }`);
580
+ } else {
581
+ contractTsLines.push(` async ${name}(${argsSig}${argsSig ? ", " : ""}overrides?: any): ${returnTs} {`);
582
+ contractTsLines.push(` return this.send(${JSON.stringify(name)}, [${argsNames}], overrides);`);
583
+ contractTsLines.push(` }`);
584
+ }
585
+ }
586
+
587
+ contractTsLines.push(`}`);
588
+ return contractTsLines.join("\n") + "\n";
589
+ }
590
+
591
+ function _renderContractJs({ contractName, abi, bytecode, docs }) {
592
+ const functions = (abi || []).filter((f) => f && f.type === "function");
593
+ const tupleReg = _collectTupleRegistry(contractName, abi);
594
+ const txFns = functions.filter((f) => (f.stateMutability || "") !== "view" && (f.stateMutability || "") !== "pure");
595
+
596
+ const lines = [];
597
+ lines.push(`// Auto-generated by sdkgen`);
598
+ lines.push(`const { Contract } = require("quantumcoin");`);
599
+ lines.push("");
600
+
601
+ const tupleDoc = _renderTupleJsDocTypeDefs(contractName, abi, tupleReg).trimEnd();
602
+ if (tupleDoc) {
603
+ lines.push(tupleDoc);
604
+ lines.push("");
605
+ }
606
+
607
+ lines.push("/**");
608
+ lines.push(` * ${contractName} - A typed contract interface for ${contractName}`);
609
+ if (docs && typeof docs.contract === "string" && docs.contract.trim()) {
610
+ lines.push(" *");
611
+ for (const line of docs.contract.split(/\r?\n/g)) {
612
+ const t = line.trim();
613
+ if (!t) continue;
614
+ lines.push(` * ${t}`);
615
+ }
616
+ }
617
+ lines.push(" */");
618
+ lines.push(`class ${contractName} extends Contract {`);
619
+ lines.push(` static abi = ${JSON.stringify(abi, null, 2)};`);
620
+ lines.push(` static bytecode = ${JSON.stringify(bytecode)};`);
621
+ lines.push("");
622
+ lines.push(` static connect(address, runner) {`);
623
+ lines.push(` return new ${contractName}(address, runner);`);
624
+ lines.push(` }`);
625
+ lines.push("");
626
+ lines.push(` constructor(address, runner, _deployTx) {`);
627
+ lines.push(` super(address, ${contractName}.abi, runner, ${contractName}.bytecode);`);
628
+ lines.push(` this._deployTx = _deployTx;`);
629
+ if (txFns.length) {
630
+ lines.push("");
631
+ lines.push(" // Typed populateTransaction helpers (offline signing / sendRawTransaction flows)");
632
+ lines.push(" this.populateTransaction = {");
633
+ for (const fn of txFns) {
634
+ const name = fn.name;
635
+ const inputs = fn.inputs || [];
636
+ const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
637
+ const argsSig = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
638
+ lines.push(` ${name}: async (${argsSig}${argsSig ? ", " : ""}overrides) => {`);
639
+ lines.push(` const data = this.interface.encodeFunctionData(${JSON.stringify(name)}, [${argsNames}]);`);
640
+ lines.push(` return { to: this.address, data, ...(overrides || {}) };`);
641
+ lines.push(" },");
642
+ }
643
+ lines.push(" };");
644
+ }
645
+ lines.push(` }`);
646
+
647
+ for (const fn of functions) {
648
+ const name = fn.name;
649
+ const inputs = fn.inputs || [];
650
+ const outputs = fn.outputs || [];
651
+ const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
652
+
653
+ const mut = fn.stateMutability || "";
654
+ const isView = mut === "view" || mut === "pure";
655
+
656
+ lines.push("");
657
+ lines.push(" /**");
658
+ lines.push(` * ${name}`);
659
+ const fnDoc = docs && docs.functions && typeof docs.functions[name] === "string" ? docs.functions[name] : "";
660
+ if (fnDoc && fnDoc.trim()) {
661
+ lines.push(" *");
662
+ for (const line of fnDoc.split(/\r?\n/g)) {
663
+ const t = line.trim();
664
+ if (!t) continue;
665
+ lines.push(` * ${t}`);
666
+ }
667
+ }
668
+ for (const p of inputs) {
669
+ const pName = _safeIdent(p.name || "arg");
670
+ lines.push(` * @param {${_solParamToJsDoc(p, "input", tupleReg)}} ${pName}`);
671
+ }
672
+ if (isView) {
673
+ if (outputs.length === 0) {
674
+ lines.push(` * @returns {Promise<void>}`);
675
+ } else if (outputs.length === 1) {
676
+ lines.push(` * @returns {Promise<${_solParamToJsDoc(outputs[0], "output", tupleReg)}>} `);
677
+ } else {
678
+ lines.push(
679
+ ` * @returns {Promise<[${outputs.map((o) => _solParamToJsDoc(o, "output", tupleReg)).join(", ")}]>}`,
680
+ );
681
+ }
682
+ } else {
683
+ lines.push(` * @returns {Promise<import("quantumcoin").ContractTransactionResponse>}`);
684
+ }
685
+ lines.push(" */");
686
+
687
+ if (isView) {
688
+ lines.push(` async ${name}(${argsNames}) {`);
689
+ lines.push(` const res = await this.call(${JSON.stringify(name)}, [${argsNames}]);`);
690
+ if (outputs.length === 0) {
691
+ lines.push(` return;`);
692
+ } else if (outputs.length === 1) {
693
+ lines.push(` return Array.isArray(res) ? res[0] : res;`);
694
+ } else {
695
+ lines.push(` return res;`);
696
+ }
697
+ lines.push(` }`);
698
+ } else {
699
+ lines.push(` async ${name}(${argsNames}${argsNames ? ", " : ""}overrides) {`);
700
+ lines.push(` return this.send(${JSON.stringify(name)}, [${argsNames}], overrides);`);
701
+ lines.push(` }`);
702
+ }
703
+ }
704
+
705
+ lines.push(`}`);
706
+ lines.push("");
707
+ lines.push(`module.exports = { ${contractName} };`);
708
+ lines.push("");
709
+ return lines.join("\n");
710
+ }
711
+
712
+ function _renderContractDts({ contractName, abi }) {
713
+ const functions = (abi || []).filter((f) => f && f.type === "function");
714
+ const tupleReg = _collectTupleRegistry(contractName, abi);
715
+ const txFns = functions.filter((f) => (f.stateMutability || "") !== "view" && (f.stateMutability || "") !== "pure");
716
+ const lines = [];
717
+ lines.push(`// Auto-generated by sdkgen`);
718
+ lines.push(`import { Contract, ContractRunner, ContractTransactionResponse, TransactionResponse } from "quantumcoin";`);
719
+ lines.push(`import type * as Types from "./types";`);
720
+ lines.push("");
721
+ const tupleDefs = _renderTupleTypeDefs(contractName, abi, tupleReg).trimEnd();
722
+ if (tupleDefs) {
723
+ lines.push(tupleDefs);
724
+ lines.push("");
725
+ }
726
+ lines.push(`export declare class ${contractName} extends Contract {`);
727
+ lines.push(` static readonly abi: readonly any[];`);
728
+ lines.push(` static readonly bytecode: string;`);
729
+ lines.push(` static connect(address: string, runner?: ContractRunner): ${contractName};`);
730
+ lines.push(` constructor(address: string, runner?: ContractRunner, _deployTx?: TransactionResponse);`);
731
+ if (txFns.length) {
732
+ lines.push(` readonly populateTransaction: {`);
733
+ for (const fn of txFns) {
734
+ const name = fn.name;
735
+ const inputs = fn.inputs || [];
736
+ const argsSig = inputs
737
+ .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
738
+ .join(", ");
739
+ lines.push(
740
+ ` ${name}(${argsSig}${argsSig ? ", " : ""}overrides?: any): Promise<import("quantumcoin").TransactionRequest>;`,
741
+ );
742
+ }
743
+ lines.push(` };`);
744
+ }
745
+
746
+ for (const fn of functions) {
747
+ const name = fn.name;
748
+ const inputs = fn.inputs || [];
749
+ const outputs = fn.outputs || [];
750
+
751
+ const argsSig = inputs.map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`).join(", ");
752
+
753
+ const mut = fn.stateMutability || "";
754
+ const isView = mut === "view" || mut === "pure";
755
+
756
+ let returnTs;
757
+ if (isView) {
758
+ if (outputs.length === 0) returnTs = "Promise<void>";
759
+ else if (outputs.length === 1) returnTs = `Promise<${_solParamToTs(outputs[0], "output", tupleReg)}>`;
760
+ else returnTs = `Promise<[${outputs.map((o) => _solParamToTs(o, "output", tupleReg)).join(", ")}]>`;
761
+ } else {
762
+ returnTs = "Promise<ContractTransactionResponse>";
763
+ }
764
+
765
+ if (isView) {
766
+ lines.push(` ${name}(${argsSig}): ${returnTs};`);
767
+ } else {
768
+ lines.push(` ${name}(${argsSig}${argsSig ? ", " : ""}overrides?: any): ${returnTs};`);
769
+ }
770
+ }
771
+
772
+ lines.push(`}`);
773
+ return lines.join("\n") + "\n";
774
+ }
775
+
776
+ function _renderFactoryTs({ contractName, abi }) {
777
+ const factoryName = `${contractName}__factory`;
778
+ const ctor = _findConstructor(abi);
779
+ const ctorInputs = (ctor && ctor.inputs) || [];
780
+ const tupleReg = _collectTupleRegistry(contractName, abi);
781
+
782
+ const factoryTsLines = [];
783
+ factoryTsLines.push(`// Auto-generated by sdkgen`);
784
+ factoryTsLines.push(`import { ContractFactory, ContractRunner, getCreateAddress } from "quantumcoin";`);
785
+ factoryTsLines.push(`import { ${contractName} } from "./${contractName}";`);
786
+ factoryTsLines.push(`import type * as Types from "./types";`);
787
+
788
+ // Import tuple input types used by the constructor (if any)
789
+ const ctorTupleTypes = new Set();
790
+ const visit = (p) => {
791
+ const t = String(p && p.type ? p.type : "");
792
+ const arr = _parseArray(t);
793
+ if (arr) return visit({ ...(p || {}), type: arr.inner });
794
+ if (t === "tuple") {
795
+ const key = _tupleKey(p);
796
+ const base = tupleReg.byKey.get(key);
797
+ if (base) ctorTupleTypes.add(`${base}Input`);
798
+ const comps = Array.isArray(p && p.components) ? p.components : [];
799
+ for (const c of comps) visit(c);
800
+ }
801
+ };
802
+ for (const p of ctorInputs) visit(p);
803
+ if (ctorTupleTypes.size) {
804
+ factoryTsLines.push(`import type { ${Array.from(ctorTupleTypes).sort().join(", ")} } from "./${contractName}";`);
805
+ }
806
+ factoryTsLines.push(``);
807
+ factoryTsLines.push(`export class ${factoryName} extends ContractFactory {`);
808
+ factoryTsLines.push(` constructor(runner: ContractRunner) {`);
809
+ factoryTsLines.push(` super(${contractName}.abi as any, ${contractName}.bytecode as any, runner as any);`);
810
+ factoryTsLines.push(` }`);
811
+ factoryTsLines.push(``);
812
+
813
+ // Typed deploy method (uses constructor args + optional overrides)
814
+ const deployArgsSig = ctorInputs
815
+ .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
816
+ .join(", ");
817
+ const deployArgsNames = ctorInputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
818
+
819
+ factoryTsLines.push(` async deploy(${deployArgsSig}${deployArgsSig ? ", " : ""}overrides?: any): Promise<${contractName}> {`);
820
+ factoryTsLines.push(` const signer: any = (this as any).signer;`);
821
+ factoryTsLines.push(` if (!signer) { throw new Error("missing signer"); }`);
822
+ factoryTsLines.push(` const from: string = signer.getAddress ? await signer.getAddress() : signer.address;`);
823
+ factoryTsLines.push(` const provider: any = signer.provider;`);
824
+ factoryTsLines.push(` if (!provider || !provider.getTransactionCount) { throw new Error("missing provider"); }`);
825
+ factoryTsLines.push(` let nonce: number;`);
826
+ factoryTsLines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
827
+ factoryTsLines.push(` const address = getCreateAddress({ from, nonce });`);
828
+ factoryTsLines.push(` const txReq: any = this.getDeployTransaction(${deployArgsNames});`);
829
+ factoryTsLines.push(` const tx = await signer.sendTransaction({ ...txReq, ...(overrides || {}), nonce });`);
830
+ factoryTsLines.push(` return new ${contractName}(address, signer as any, tx as any);`);
831
+ factoryTsLines.push(` }`);
832
+ factoryTsLines.push(``);
833
+
834
+ factoryTsLines.push(` static connect(address: string, runner?: ContractRunner): ${contractName} {`);
835
+ factoryTsLines.push(` return ${contractName}.connect(address, runner);`);
836
+ factoryTsLines.push(` }`);
837
+ factoryTsLines.push(`}`);
838
+
839
+ return factoryTsLines.join("\n") + "\n";
840
+ }
841
+
842
+ function _renderFactoryJs({ contractName, abi }) {
843
+ const factoryName = `${contractName}__factory`;
844
+ const ctor = _findConstructor(abi);
845
+ const ctorInputs = (ctor && ctor.inputs) || [];
846
+ const deployArgsNames = ctorInputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
847
+
848
+ const lines = [];
849
+ lines.push(`// Auto-generated by sdkgen`);
850
+ lines.push(`const { ContractFactory, getCreateAddress } = require("quantumcoin");`);
851
+ lines.push(`const { ${contractName} } = require("./${contractName}");`);
852
+ lines.push("");
853
+ lines.push(`class ${factoryName} extends ContractFactory {`);
854
+ lines.push(` constructor(runner) {`);
855
+ lines.push(` super(${contractName}.abi, ${contractName}.bytecode, runner);`);
856
+ lines.push(` }`);
857
+ lines.push("");
858
+ lines.push(` async deploy(${deployArgsNames}${deployArgsNames ? ", " : ""}overrides) {`);
859
+ lines.push(` const signer = this.signer;`);
860
+ lines.push(` if (!signer) { throw new Error("missing signer"); }`);
861
+ lines.push(` const from = signer.getAddress ? await signer.getAddress() : signer.address;`);
862
+ lines.push(` const provider = signer.provider;`);
863
+ lines.push(` if (!provider || !provider.getTransactionCount) { throw new Error("missing provider"); }`);
864
+ lines.push(` let nonce;`);
865
+ lines.push(` try { nonce = await provider.getTransactionCount(from, "pending"); } catch { nonce = await provider.getTransactionCount(from, "latest"); }`);
866
+ lines.push(` const address = getCreateAddress({ from, nonce });`);
867
+ lines.push(` const txReq = this.getDeployTransaction(${deployArgsNames});`);
868
+ lines.push(` const tx = await signer.sendTransaction({ ...txReq, ...(overrides || {}), nonce });`);
869
+ lines.push(` return new ${contractName}(address, signer, tx);`);
870
+ lines.push(` }`);
871
+ lines.push("");
872
+ lines.push(` static connect(address, runner) {`);
873
+ lines.push(` return ${contractName}.connect(address, runner);`);
874
+ lines.push(` }`);
875
+ lines.push(`}`);
876
+ lines.push("");
877
+ lines.push(`module.exports = { ${factoryName} };`);
878
+ lines.push("");
879
+ return lines.join("\n");
880
+ }
881
+
882
+ function _renderFactoryDts({ contractName, abi }) {
883
+ const factoryName = `${contractName}__factory`;
884
+ const ctor = _findConstructor(abi);
885
+ const ctorInputs = (ctor && ctor.inputs) || [];
886
+ const tupleReg = _collectTupleRegistry(contractName, abi);
887
+ const deployArgsSig = ctorInputs.map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`).join(", ");
888
+
889
+ const lines = [];
890
+ lines.push(`// Auto-generated by sdkgen`);
891
+ lines.push(`import { ContractFactory, ContractRunner } from "quantumcoin";`);
892
+ lines.push(`import { ${contractName} } from "./${contractName}";`);
893
+ lines.push(`import type * as Types from "./types";`);
894
+
895
+ const ctorTupleTypes = new Set();
896
+ const visit = (p) => {
897
+ const t = String(p && p.type ? p.type : "");
898
+ const arr = _parseArray(t);
899
+ if (arr) return visit({ ...(p || {}), type: arr.inner });
900
+ if (t === "tuple") {
901
+ const key = _tupleKey(p);
902
+ const base = tupleReg.byKey.get(key);
903
+ if (base) ctorTupleTypes.add(`${base}Input`);
904
+ const comps = Array.isArray(p && p.components) ? p.components : [];
905
+ for (const c of comps) visit(c);
906
+ }
907
+ };
908
+ for (const p of ctorInputs) visit(p);
909
+ if (ctorTupleTypes.size) {
910
+ lines.push(`import type { ${Array.from(ctorTupleTypes).sort().join(", ")} } from "./${contractName}";`);
911
+ }
912
+ lines.push("");
913
+ lines.push(`export declare class ${factoryName} extends ContractFactory {`);
914
+ lines.push(` constructor(runner: ContractRunner);`);
915
+ lines.push(` deploy(${deployArgsSig}${deployArgsSig ? ", " : ""}overrides?: any): Promise<${contractName}>;`);
916
+ lines.push(` static connect(address: string, runner?: ContractRunner): ${contractName};`);
917
+ lines.push(`}`);
918
+ return lines.join("\n") + "\n";
919
+ }
920
+
921
+ function _renderIndexTs(contractNames) {
922
+ const lines = [];
923
+ lines.push(`// Auto-generated by sdkgen`);
924
+ lines.push(`export * from "./types";`);
925
+ for (const name of contractNames) {
926
+ lines.push(`export * from "./${name}";`);
927
+ lines.push(`export * from "./${name}__factory";`);
928
+ }
929
+ return lines.join("\n") + "\n";
930
+ }
931
+
932
+ function _renderIndexJs(contractNames) {
933
+ const lines = [];
934
+ lines.push(`// Auto-generated by sdkgen`);
935
+ lines.push("");
936
+ lines.push(`Object.assign(exports, require("./types"));`);
937
+ for (const name of contractNames) {
938
+ lines.push(`exports.${name} = require("./${name}").${name};`);
939
+ lines.push(`exports.${name}__factory = require("./${name}__factory").${name}__factory;`);
940
+ }
941
+ lines.push("");
942
+ return lines.join("\n");
943
+ }
944
+
945
+ function _renderIndexDts(contractNames) {
946
+ const lines = [];
947
+ lines.push(`// Auto-generated by sdkgen`);
948
+ lines.push(`export * from "./types";`);
949
+ for (const name of contractNames) {
950
+ lines.push(`export * from "./${name}";`);
951
+ lines.push(`export * from "./${name}__factory";`);
952
+ }
953
+ return lines.join("\n") + "\n";
954
+ }
955
+
956
+ /**
957
+ * Generate a transactional e2e test file (JavaScript) for the typed contract package.
958
+ * The test deploys the contract with constructor args (if any) and invokes one write method.
959
+ *
960
+ * @param {{ contractName: string, abi: any[] }} opts
961
+ * @returns {string}
962
+ */
963
+ function generateTransactionalTestJs(opts) {
964
+ const { contractName, abi } = opts;
965
+ const factoryName = `${contractName}__factory`;
966
+ const ctor = _findConstructor(abi);
967
+ const ctorInputs = ctor.inputs || [];
968
+
969
+ const ctorArgsExpr = ctorInputs.map((i) => _solTypeToTestValueExpr(i)).join(", ");
970
+
971
+ // ---------------------------------------------------------------------------
972
+ // ERC-20 style assertions (optional)
973
+ // ---------------------------------------------------------------------------
974
+ const fnByName = (name) => (abi || []).find((f) => f && f.type === "function" && f.name === name);
975
+ const nameFn = fnByName("name");
976
+ const supplyFn = fnByName("totalSupply") || fnByName("supply");
977
+ const balanceOfFn = fnByName("balanceOf");
978
+
979
+ const isViewNoArgs = (f) =>
980
+ !!(
981
+ f &&
982
+ f.type === "function" &&
983
+ (f.stateMutability === "view" || f.stateMutability === "pure") &&
984
+ (f.inputs || []).length === 0
985
+ );
986
+ const isViewBalanceOf = (f) =>
987
+ !!(
988
+ f &&
989
+ f.type === "function" &&
990
+ (f.stateMutability === "view" || f.stateMutability === "pure") &&
991
+ (f.inputs || []).length === 1 &&
992
+ (f.inputs[0].type || "") === "address"
993
+ );
994
+ const isStringOut = (f) => !!(f && (f.outputs || []).length === 1 && (f.outputs[0].type || "") === "string");
995
+ const isUintOut = (f) => !!(f && (f.outputs || []).length === 1 && String(f.outputs[0].type || "").startsWith("uint"));
996
+
997
+ const hasErc20Surface =
998
+ isViewNoArgs(nameFn) && isStringOut(nameFn) && isViewNoArgs(supplyFn) && isUintOut(supplyFn) && isViewBalanceOf(balanceOfFn) && isUintOut(balanceOfFn);
999
+
1000
+ // Common ERC-20 constructor: (string name, string symbol, uint256 initialSupply)
1001
+ const isErc20Ctor =
1002
+ ctorInputs.length === 3 &&
1003
+ (ctorInputs[0].type || "") === "string" &&
1004
+ (ctorInputs[1].type || "") === "string" &&
1005
+ String(ctorInputs[2].type || "").startsWith("uint");
1006
+
1007
+ const erc20TokenName = "TestToken";
1008
+ const erc20TokenSymbol = "TT";
1009
+ const erc20InitialSupply = 1000;
1010
+ const deployArgsExpr = hasErc20Surface && isErc20Ctor ? `${JSON.stringify(erc20TokenName)}, ${JSON.stringify(erc20TokenSymbol)}, ${erc20InitialSupply}` : ctorArgsExpr;
1011
+ const supplyMethodName = supplyFn && typeof supplyFn.name === "string" ? supplyFn.name : "totalSupply";
1012
+ const erc20Assertions =
1013
+ hasErc20Surface && isErc20Ctor
1014
+ ? `// ERC-20 assertions (name / supply / balanceOf)
1015
+ const nm = await contract.name();
1016
+ assert.equal(nm, ${JSON.stringify(erc20TokenName)});
1017
+
1018
+ // Generated wrappers already unwrap single-return values to a hard type (bigint for uints).
1019
+ const supply = await contract.${supplyMethodName}();
1020
+ assert.equal(supply, ${erc20InitialSupply}n);
1021
+
1022
+ // Generated wrappers already unwrap single-return values to a hard type (bigint for uints).
1023
+ const bal = await contract.balanceOf(wallet.address);
1024
+ assert.equal(bal, ${erc20InitialSupply}n);
1025
+ `
1026
+ : "";
1027
+
1028
+ // Pick first view function with no inputs for state reads (optional).
1029
+ const viewNoArg = (abi || []).find(
1030
+ (f) => f && f.type === "function" && (f.stateMutability === "view" || f.stateMutability === "pure") && (f.inputs || []).length === 0,
1031
+ );
1032
+
1033
+ // Pick first state-changing function with supported params (prefer 1-arg numeric setter patterns).
1034
+ const writeFn =
1035
+ (abi || []).find((f) => {
1036
+ if (!f || f.type !== "function") return false;
1037
+ if (f.stateMutability === "view" || f.stateMutability === "pure") return false;
1038
+ if (!_allSupportedParams(f.inputs)) return false;
1039
+ return f.name === "set" && (f.inputs || []).length === 1 && (f.inputs[0].type || "").startsWith("uint");
1040
+ }) ||
1041
+ (abi || []).find(
1042
+ (f) => f && f.type === "function" && !(f.stateMutability === "view" || f.stateMutability === "pure") && _allSupportedParams(f.inputs),
1043
+ );
1044
+
1045
+ const writeName = writeFn ? writeFn.name : null;
1046
+ const writeArgsExpr =
1047
+ writeFn && (writeFn.inputs || []).length === 1 && (writeFn.inputs[0].type || "").startsWith("uint")
1048
+ ? "456"
1049
+ : writeFn
1050
+ ? (writeFn.inputs || []).map((i) => _solTypeToTestValueExpr(i)).join(", ")
1051
+ : "";
1052
+
1053
+ const canAssertValueChange =
1054
+ !!(
1055
+ viewNoArg &&
1056
+ (viewNoArg.outputs || []).length === 1 &&
1057
+ typeof viewNoArg.outputs[0].type === "string" &&
1058
+ (viewNoArg.outputs[0].type.startsWith("uint") || viewNoArg.outputs[0].type.startsWith("int")) &&
1059
+ writeFn &&
1060
+ (writeFn.inputs || []).length === 1 &&
1061
+ typeof writeFn.inputs[0].type === "string" &&
1062
+ (writeFn.inputs[0].type.startsWith("uint") || writeFn.inputs[0].type.startsWith("int"))
1063
+ );
1064
+
1065
+ return `/**
1066
+ * @testCategory e2e
1067
+ * @blockchainRequired write
1068
+ * @description Auto-generated transactional tests for ${contractName}
1069
+ *
1070
+ * WARNING:
1071
+ * - This test uses a HARDCODED TEST WALLET (encrypted JSON + passphrase).
1072
+ * - It assumes the wallet has funds on the target network.
1073
+ * - It will broadcast real transactions and change chain state.
1074
+ */
1075
+
1076
+ const { describe, it } = require("node:test");
1077
+ const assert = require("node:assert/strict");
1078
+
1079
+ const { Initialize } = require("quantumcoin/config");
1080
+ const { getProvider, Wallet } = require("quantumcoin");
1081
+
1082
+ // NOTE: this test file lives at test/e2e/*.js, so package root is two levels up.
1083
+ // Require the package root so it works for both TS (dist) and JS (src) packages.
1084
+ const { ${contractName}, ${factoryName} } = require("../..");
1085
+
1086
+ // Hardcoded test wallet (test-only; never use for real funds)
1087
+ const TEST_WALLET_ENCRYPTED_JSON =
1088
+ ${JSON.stringify(
1089
+ "{\"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}",
1090
+ )};
1091
+ const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
1092
+
1093
+ describe("${contractName} transactional", () => {
1094
+ it("deploys and invokes contract", async (t) => {
1095
+ const rpcUrl = process.env.QC_RPC_URL;
1096
+ if (!rpcUrl) {
1097
+ t.skip("QC_RPC_URL not provided");
1098
+ return;
1099
+ }
1100
+
1101
+ const chainId = process.env.QC_CHAIN_ID ? Number(process.env.QC_CHAIN_ID) : 123123;
1102
+ await Initialize(null);
1103
+
1104
+ const provider = getProvider(rpcUrl, chainId);
1105
+ const wallet = Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
1106
+
1107
+ const factory = new ${factoryName}(wallet);
1108
+ // Build deploy transaction + estimate gas (best-effort).
1109
+ const deployTxReq = factory.getDeployTransaction(${deployArgsExpr});
1110
+ let deployGasLimit = 600000;
1111
+ try {
1112
+ const est = await provider.estimateGas({ from: wallet.address, data: deployTxReq.data });
1113
+ deployGasLimit = Number(est + 200_000n);
1114
+ } catch {
1115
+ // Keep fallback. Some RPCs do not support estimateGas for create.
1116
+ deployGasLimit = 6_000_000;
1117
+ }
1118
+ // Some large contracts underestimate gas; apply a sane floor based on bytecode size.
1119
+ const bytecodeSize = (${contractName}.bytecode || "").length;
1120
+ if (bytecodeSize > 20000 && deployGasLimit < 6_000_000) deployGasLimit = 6_000_000;
1121
+
1122
+ const contract = await factory.deploy(${deployArgsExpr}${deployArgsExpr ? ", " : ""}{ gasLimit: deployGasLimit });
1123
+
1124
+ const deployTx = contract.deployTransaction();
1125
+ assert.ok(deployTx && deployTx.hash);
1126
+ const deployReceipt = await deployTx.wait(1, 600_000);
1127
+ assert.ok(deployReceipt);
1128
+ assert.ok(deployReceipt.blockNumber != null);
1129
+
1130
+ const code = await provider.getCode(contract.target, "latest");
1131
+ assert.ok(code && code !== "0x");
1132
+
1133
+ ${erc20Assertions ? erc20Assertions : `// (no ERC-20 surface detected for extra assertions)`}
1134
+
1135
+ ${viewNoArg ? `// Basic view call
1136
+ const before = await contract.${viewNoArg.name}();
1137
+ void before;` : `// No zero-arg view method detected; deployment is still validated.`}
1138
+
1139
+ ${writeName ? `// Write call + wait
1140
+ const tx = await contract.${writeName}(${writeArgsExpr}${writeArgsExpr ? ", " : ""}{ gasLimit: 200000 });
1141
+ await tx.wait(1, 600_000);` : `// No supported write method detected for auto invocation.`}
1142
+
1143
+ ${canAssertValueChange ? `// Assert value change (best-effort; normalize to BigInt)
1144
+ const after = await contract.${viewNoArg.name}();
1145
+ assert.equal(after, 456n);` : `// No compatible getter+setter pair detected for value assertions.`}
1146
+ }, { timeout: 900_000 });
1147
+ });
1148
+ `;
1149
+ }
1150
+
1151
+ /**
1152
+ * For a single contract ABI, compute deploy args and optional view/write method for e2e.
1153
+ * @param {{ contractName: string, abi: any[] }} opts
1154
+ * @returns {{ ctorArgsExpr: string, deployArgsExpr: string, viewName: string | null, writeName: string | null, writeArgsExpr: string }}
1155
+ */
1156
+ function _getContractTestMeta(opts) {
1157
+ const { contractName, abi } = opts;
1158
+ const ctor = _findConstructor(abi);
1159
+ const ctorInputs = ctor.inputs || [];
1160
+ const ctorArgsExpr = ctorInputs.map((i) => _solTypeToTestValueExpr(i)).join(", ");
1161
+ const viewNoArg = (abi || []).find(
1162
+ (f) => f && f.type === "function" && (f.stateMutability === "view" || f.stateMutability === "pure") && (f.inputs || []).length === 0,
1163
+ );
1164
+ const writeFn =
1165
+ (abi || []).find(
1166
+ (f) =>
1167
+ f && f.type === "function" && !(f.stateMutability === "view" || f.stateMutability === "pure") && f.name === "set" &&
1168
+ (f.inputs || []).length === 1 &&
1169
+ (f.inputs[0].type || "").startsWith("uint") &&
1170
+ _allSupportedParams(f.inputs),
1171
+ ) ||
1172
+ (abi || []).find(
1173
+ (f) => f && f.type === "function" && !(f.stateMutability === "view" || f.stateMutability === "pure") && _allSupportedParams(f.inputs),
1174
+ );
1175
+ const writeArgsExpr =
1176
+ writeFn && (writeFn.inputs || []).length === 1 && (writeFn.inputs[0].type || "").startsWith("uint")
1177
+ ? "456"
1178
+ : writeFn
1179
+ ? (writeFn.inputs || []).map((i) => _solTypeToTestValueExpr(i)).join(", ")
1180
+ : "";
1181
+ return {
1182
+ ctorArgsExpr,
1183
+ deployArgsExpr: ctorArgsExpr,
1184
+ viewName: viewNoArg ? viewNoArg.name : null,
1185
+ writeName: writeFn ? writeFn.name : null,
1186
+ writeArgsExpr,
1187
+ };
1188
+ }
1189
+
1190
+ /**
1191
+ * Generate a single transactional e2e test that deploys and invokes methods on ALL contracts.
1192
+ * Used when the package has multiple contracts so one test exercises every contract.
1193
+ *
1194
+ * @param {{ artifacts: Array<{ contractName: string, abi: any[] }> }} opts
1195
+ * @returns {string}
1196
+ */
1197
+ function generateAllContractsTransactionalTestJs(opts) {
1198
+ const { artifacts } = opts;
1199
+ if (!artifacts || artifacts.length < 2) {
1200
+ throw new Error("generateAllContractsTransactionalTestJs requires at least 2 artifacts");
1201
+ }
1202
+
1203
+ const requireNames = [];
1204
+ for (const a of artifacts) {
1205
+ requireNames.push(a.contractName, `${a.contractName}__factory`);
1206
+ }
1207
+ const requireLine = `const { ${requireNames.join(", ")} } = require("../..");`;
1208
+
1209
+ const blocks = [];
1210
+ for (const a of artifacts) {
1211
+ const meta = _getContractTestMeta({ contractName: a.contractName, abi: a.abi });
1212
+ const factoryName = `${a.contractName}__factory`;
1213
+ const deployRhs =
1214
+ meta.ctorArgsExpr
1215
+ ? `await (new ${factoryName}(wallet)).deploy(${meta.deployArgsExpr}, { gasLimit: deployGasLimit })`
1216
+ : `await (new ${factoryName}(wallet)).deploy({ gasLimit: deployGasLimit })`;
1217
+ let invokeBlock = "";
1218
+ if (meta.viewName) {
1219
+ invokeBlock += `const _v = await ${a.contractName}Inst.${meta.viewName}();\n void _v;\n`;
1220
+ }
1221
+ if (meta.writeName) {
1222
+ invokeBlock += `const _tx = await ${a.contractName}Inst.${meta.writeName}(${meta.writeArgsExpr}${meta.writeArgsExpr ? ", " : ""}{ gasLimit: 200000 });\n await _tx.wait(1, 600_000);\n`;
1223
+ }
1224
+ blocks.push({
1225
+ contractName: a.contractName,
1226
+ factoryName,
1227
+ deployRhs,
1228
+ invokeBlock: invokeBlock || `// No view/write method detected for ${a.contractName}\n`,
1229
+ });
1230
+ }
1231
+
1232
+ const deployAndInvokeBlocks = blocks
1233
+ .map(
1234
+ (b) => `
1235
+ let ${b.contractName}Inst = ${b.deployRhs};
1236
+ const _deployTx${b.contractName} = ${b.contractName}Inst.deployTransaction();
1237
+ assert.ok(_deployTx${b.contractName} && _deployTx${b.contractName}.hash);
1238
+ await _deployTx${b.contractName}.wait(1, 600_000);
1239
+ ${b.invokeBlock.trim().split("\n").join("\n ")}`,
1240
+ )
1241
+ .join("\n");
1242
+
1243
+ const firstMeta = _getContractTestMeta({ contractName: artifacts[0].contractName, abi: artifacts[0].abi });
1244
+ const firstCtorArgs = firstMeta.ctorArgsExpr ? firstMeta.ctorArgsExpr + ", " : "";
1245
+
1246
+ return `/**
1247
+ * @testCategory e2e
1248
+ * @blockchainRequired write
1249
+ * @description Auto-generated test that deploys and invokes all contracts in this package.
1250
+ *
1251
+ * WARNING:
1252
+ * - This test uses a HARDCODED TEST WALLET (encrypted JSON + passphrase).
1253
+ * - It assumes the wallet has funds on the target network.
1254
+ * - It will broadcast real transactions and change chain state.
1255
+ */
1256
+
1257
+ const { describe, it } = require("node:test");
1258
+ const assert = require("node:assert/strict");
1259
+
1260
+ const { Initialize } = require("quantumcoin/config");
1261
+ const { getProvider, Wallet } = require("quantumcoin");
1262
+
1263
+ ${requireLine}
1264
+
1265
+ // Hardcoded test wallet (test-only; never use for real funds)
1266
+ const TEST_WALLET_ENCRYPTED_JSON =
1267
+ ${JSON.stringify(
1268
+ "{\"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}",
1269
+ )};
1270
+ const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
1271
+
1272
+ describe("all contracts", () => {
1273
+ it("deploys and invokes all contracts", async (t) => {
1274
+ const rpcUrl = process.env.QC_RPC_URL;
1275
+ if (!rpcUrl) {
1276
+ t.skip("QC_RPC_URL not provided");
1277
+ return;
1278
+ }
1279
+
1280
+ const chainId = process.env.QC_CHAIN_ID ? Number(process.env.QC_CHAIN_ID) : 123123;
1281
+ await Initialize(null);
1282
+
1283
+ const provider = getProvider(rpcUrl, chainId);
1284
+ const wallet = Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
1285
+
1286
+ let deployGasLimit = 600000;
1287
+ try {
1288
+ const firstFactory = new ${artifacts[0].contractName}__factory(wallet);
1289
+ const sampleReq = firstFactory.getDeployTransaction(${firstMeta.ctorArgsExpr || ""});
1290
+ const est = await provider.estimateGas({ from: wallet.address, data: sampleReq.data });
1291
+ deployGasLimit = Number(est + 200_000n);
1292
+ } catch {
1293
+ deployGasLimit = 6_000_000;
1294
+ }
1295
+ ${deployAndInvokeBlocks}
1296
+ }, { timeout: ${Math.max(900_000, 300_000 * artifacts.length)}});
1297
+ });
1298
+ `;
1299
+ }
1300
+
1301
+ /**
1302
+ * @typedef {Object} GenerateOptions
1303
+ * @property {string} abiPath
1304
+ * @property {string} binPath
1305
+ * @property {string} outDir
1306
+ * @property {string} contractName
1307
+ * @property {string=} packageName
1308
+ * @property {boolean=} createPackage
1309
+ * @property {Record<string,string>=} dependencies
1310
+ */
1311
+
1312
+ /**
1313
+ * Generate multiple typed contract files.
1314
+ * @param {{ outDir: string, artifacts: Array<{ contractName: string, abi: any[], bytecode: string }> }} opts
1315
+ * @returns {{ contracts: Array<{ contractFile: string, factoryFile: string }>, typesFile: string, indexFile: string }}
1316
+ */
1317
+ function generateFromArtifacts(opts) {
1318
+ _ensureDir(opts.outDir);
1319
+ const lang = (opts && opts.lang) || "ts"; // "ts" | "js"
1320
+ if (lang !== "ts" && lang !== "js") {
1321
+ throw new Error(`Unsupported generator lang: ${lang}`);
1322
+ }
1323
+
1324
+ const contractNames = opts.artifacts.map((a) => a.contractName);
1325
+
1326
+ const typesFile = path.join(opts.outDir, lang === "ts" ? `types.ts` : `types.js`);
1327
+ const indexFile = path.join(opts.outDir, lang === "ts" ? `index.ts` : `index.js`);
1328
+
1329
+ fs.writeFileSync(typesFile, lang === "ts" ? _typesTs() : _typesJs(), "utf8");
1330
+
1331
+ // For JS generation, we also emit .d.ts files (TS declarations) so JS packages remain typed.
1332
+ const typesDtsFile = lang === "js" ? path.join(opts.outDir, `types.d.ts`) : null;
1333
+ const indexDtsFile = lang === "js" ? path.join(opts.outDir, `index.d.ts`) : null;
1334
+ if (lang === "js") {
1335
+ fs.writeFileSync(typesDtsFile, _typesDts(), "utf8");
1336
+ }
1337
+
1338
+ const contracts = [];
1339
+ for (const a of opts.artifacts) {
1340
+ const contractFile = path.join(opts.outDir, `${a.contractName}.${lang === "ts" ? "ts" : "js"}`);
1341
+ const factoryFile = path.join(opts.outDir, `${a.contractName}__factory.${lang === "ts" ? "ts" : "js"}`);
1342
+ const contractDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}.d.ts`) : null;
1343
+ const factoryDtsFile = lang === "js" ? path.join(opts.outDir, `${a.contractName}__factory.d.ts`) : null;
1344
+
1345
+ if (lang === "ts") {
1346
+ fs.writeFileSync(
1347
+ contractFile,
1348
+ _renderContractTs({ contractName: a.contractName, abi: a.abi, bytecode: a.bytecode, docs: a.docs || null }),
1349
+ "utf8",
1350
+ );
1351
+ fs.writeFileSync(factoryFile, _renderFactoryTs({ contractName: a.contractName, abi: a.abi }), "utf8");
1352
+ } else {
1353
+ fs.writeFileSync(
1354
+ contractFile,
1355
+ _renderContractJs({ contractName: a.contractName, abi: a.abi, bytecode: a.bytecode, docs: a.docs || null }),
1356
+ "utf8",
1357
+ );
1358
+ fs.writeFileSync(factoryFile, _renderFactoryJs({ contractName: a.contractName, abi: a.abi }), "utf8");
1359
+ }
1360
+
1361
+ if (lang === "js") {
1362
+ fs.writeFileSync(contractDtsFile, _renderContractDts({ contractName: a.contractName, abi: a.abi }), "utf8");
1363
+ fs.writeFileSync(factoryDtsFile, _renderFactoryDts({ contractName: a.contractName, abi: a.abi }), "utf8");
1364
+ }
1365
+ contracts.push({ contractFile, factoryFile });
1366
+ }
1367
+
1368
+ fs.writeFileSync(indexFile, lang === "ts" ? _renderIndexTs(contractNames) : _renderIndexJs(contractNames), "utf8");
1369
+ if (lang === "js") {
1370
+ fs.writeFileSync(indexDtsFile, _renderIndexDts(contractNames), "utf8");
1371
+ }
1372
+
1373
+ if (lang === "js") {
1374
+ return { contracts, typesFile, indexFile, typesDtsFile, indexDtsFile };
1375
+ }
1376
+ return { contracts, typesFile, indexFile };
1377
+ }
1378
+
1379
+ /**
1380
+ * Generate typed contract files.
1381
+ * @param {GenerateOptions} opts
1382
+ * @returns {{ contractFile: string, factoryFile: string, typesFile: string, indexFile: string }}
1383
+ */
1384
+ function generate(opts) {
1385
+ const abi = _readJson(opts.abiPath);
1386
+ const bytecodeRaw = fs.readFileSync(opts.binPath, "utf8").trim();
1387
+ const bytecode = bytecodeRaw.startsWith("0x") ? bytecodeRaw : "0x" + bytecodeRaw;
1388
+
1389
+ _ensureDir(opts.outDir);
1390
+ const contractName = opts.contractName;
1391
+
1392
+ const res = generateFromArtifacts({
1393
+ outDir: opts.outDir,
1394
+ artifacts: [{ contractName, abi, bytecode }],
1395
+ lang: opts.lang || "ts",
1396
+ });
1397
+
1398
+ const contractFile = res.contracts[0].contractFile;
1399
+ const factoryFile = res.contracts[0].factoryFile;
1400
+ return { contractFile, factoryFile, typesFile: res.typesFile, indexFile: res.indexFile };
1401
+ }
1402
+
1403
+ module.exports = { generate, generateFromArtifacts, generateTransactionalTestJs, generateAllContractsTransactionalTestJs };
1404
+