quantumcoin 7.0.1 → 7.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.gitignore +3 -0
  2. package/README-SDK.md +64 -10
  3. package/README.md +27 -4
  4. package/SPEC.md +3843 -0
  5. package/examples/AllSolidityTypes.sol +184 -0
  6. package/examples/SimpleIERC20.sol +74 -0
  7. package/examples/example-generator-sdk-js.js +95 -0
  8. package/examples/example-generator-sdk-ts.js +95 -0
  9. package/examples/example.js +2 -2
  10. package/examples/offline-signing.js +73 -0
  11. package/examples/package-lock.json +10 -1103
  12. package/examples/package.json +1 -2
  13. package/examples/read-operations.js +1 -2
  14. package/examples/sdk-generator-erc20.inline.json +251 -0
  15. package/examples/solidity-types.ts +43 -0
  16. package/generate-sdk.js +689 -87
  17. package/package.json +30 -9
  18. package/src/abi/interface.d.ts +18 -0
  19. package/src/abi/interface.js +247 -9
  20. package/src/abi/js-abi-coder.js +474 -0
  21. package/src/contract/contract-factory.d.ts +1 -1
  22. package/src/contract/contract-factory.js +14 -2
  23. package/src/contract/contract.d.ts +10 -1
  24. package/src/contract/contract.js +42 -0
  25. package/src/generator/index.js +1041 -63
  26. package/src/index.d.ts +16 -0
  27. package/src/providers/provider.d.ts +20 -11
  28. package/src/providers/provider.js +12 -0
  29. package/src/types/index.d.ts +462 -0
  30. package/src/types/index.js +9 -0
  31. package/test/e2e/all-solidity-types.dynamic.test.js +200 -0
  32. package/test/e2e/all-solidity-types.fixtures.js +231 -0
  33. package/test/e2e/all-solidity-types.generated-sdks.e2e.test.js +368 -0
  34. package/test/e2e/simple-erc20.generated-sdks.e2e.test.js +151 -0
  35. package/test/e2e/transactional.test.js +4 -4
  36. package/test/e2e/typed-generator.e2e.test.js +8 -6
  37. package/test/integration/ws-provider.test.js +1 -1
  38. package/test/unit/generate-contract-cli.test.js +2 -1
  39. package/test/unit/generate-sdk-artifacts-json.test.js +45 -0
  40. package/test/unit/generator.test.js +1 -0
  41. package/test/unit/populate-transaction.test.js +62 -0
  42. package/test/unit/solidity-types.test.js +46 -0
  43. package/test/unit/utils.test.js +1 -1
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * @fileoverview Typed contract generator (SPEC.md section 15).
3
3
  *
4
- * This generator creates TypeScript files from an ABI + bytecode.
4
+ * Supports generating:
5
+ * - TypeScript source (`.ts`)
6
+ * - JavaScript source (`.js`) + TypeScript declaration files (`.d.ts`)
7
+ *
5
8
  * It is designed to be invoked by `generate-sdk.js` (CLI) and
6
9
  * can also be imported as a library.
7
10
  */
@@ -17,22 +20,354 @@ function _readJson(filePath) {
17
20
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
18
21
  }
19
22
 
20
- function _solTypeToTs(type) {
21
- // Arrays
22
- if (type.endsWith("]")) {
23
- const inner = type.slice(0, type.lastIndexOf("["));
24
- return `Array<${_solTypeToTs(inner)}>`;
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"}`;
25
143
  }
26
144
 
27
- if (type === "address") return "string";
145
+ // Elementary types (hard typed)
146
+ if (type === "address") return m === "input" ? "Types.AddressLike" : "Types.SolAddress";
28
147
  if (type === "bool") return "boolean";
29
148
  if (type === "string") return "string";
30
- if (type.startsWith("uint") || type.startsWith("int")) return "bigint";
31
- if (type.startsWith("bytes")) return "string"; // BytesLike
32
- if (type === "tuple") return "any";
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)
33
173
  return "any";
34
174
  }
35
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
+
36
371
  function _cap(s) {
37
372
  return s ? s[0].toUpperCase() + s.slice(1) : s;
38
373
  }
@@ -46,18 +381,57 @@ function _findConstructor(abi) {
46
381
  return ctor || { type: "constructor", inputs: [] };
47
382
  }
48
383
 
49
- function _solTypeToTestValueExpr(type) {
50
- // Arrays
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)
51
389
  if (type.endsWith("]")) {
52
390
  const inner = type.slice(0, type.lastIndexOf("["));
53
- return `[${_solTypeToTestValueExpr(inner)}]`;
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}]`;
54
403
  }
404
+
55
405
  if (type === "address") return "wallet.address";
56
406
  if (type === "bool") return "true";
57
407
  if (type === "string") return JSON.stringify("hello");
58
- if (type === "bytes") return JSON.stringify("0x");
59
- if (type === "bytes32") return "encodeBytes32String(\"init\")";
60
- if (type.startsWith("uint") || type.startsWith("int")) return "123n";
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
+
61
435
  // Fallback
62
436
  return "undefined";
63
437
  }
@@ -80,18 +454,35 @@ function _allSupportedParams(inputs) {
80
454
  }
81
455
 
82
456
  function _typesTs() {
83
- return `// Auto-generated by quantumcoin-sdk-generator\n\n` +
84
- `export type AddressLike = string;\n` +
85
- `export type BytesLike = string;\n`;
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
+ );
86
473
  }
87
474
 
88
475
  function _renderContractTs({ contractName, abi, bytecode, docs }) {
89
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");
90
479
 
91
480
  const contractTsLines = [];
92
- contractTsLines.push(`// Auto-generated by quantumcoin-sdk-generator`);
481
+ contractTsLines.push(`// Auto-generated by sdkgen`);
93
482
  contractTsLines.push(`import { Contract, ContractTransactionResponse, ContractRunner, TransactionResponse } from "quantumcoin";`);
94
- contractTsLines.push(`import type { AddressLike } from "./types";`);
483
+ contractTsLines.push(`import type * as Types from "./types";`);
484
+ contractTsLines.push(``);
485
+ contractTsLines.push(_renderTupleTypeDefs(contractName, abi, tupleReg).trimEnd());
95
486
  contractTsLines.push(``);
96
487
  contractTsLines.push(`/**`);
97
488
  contractTsLines.push(` * ${contractName} - A typed contract interface for ${contractName}`);
@@ -116,6 +507,26 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
116
507
  contractTsLines.push(` super(address, ${contractName}.abi as any, runner as any, ${contractName}.bytecode);`);
117
508
  contractTsLines.push(` // @ts-expect-error internal attach`);
118
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
+ }
119
530
  contractTsLines.push(` }`);
120
531
 
121
532
  for (const fn of functions) {
@@ -123,7 +534,7 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
123
534
  const inputs = fn.inputs || [];
124
535
  const outputs = fn.outputs || [];
125
536
  const argsSig = inputs
126
- .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solTypeToTs(p.type)}`)
537
+ .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
127
538
  .join(", ");
128
539
  const argsNames = inputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
129
540
 
@@ -132,9 +543,9 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
132
543
 
133
544
  let returnTs;
134
545
  if (isView) {
135
- if (outputs.length === 0) returnTs = "Promise<any>";
136
- else if (outputs.length === 1) returnTs = `Promise<${_solTypeToTs(outputs[0].type)}>`;
137
- else returnTs = `Promise<[${outputs.map((o) => _solTypeToTs(o.type)).join(", ")}]>`;
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(", ")}]>`;
138
549
  } else {
139
550
  returnTs = "Promise<ContractTransactionResponse>";
140
551
  }
@@ -155,10 +566,15 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
155
566
  if (isView) {
156
567
  contractTsLines.push(` async ${name}(${argsSig}): ${returnTs} {`);
157
568
  contractTsLines.push(` const res = await this.call(${JSON.stringify(name)}, [${argsNames}]);`);
158
- if (outputs.length === 1) {
159
- contractTsLines.push(` return (Array.isArray(res) ? res[0] : res) as any;`);
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)};`);
160
574
  } else {
161
- contractTsLines.push(` return res as any;`);
575
+ contractTsLines.push(
576
+ ` return res as unknown as [${outputs.map((o) => _solParamToTs(o, "output", tupleReg)).join(", ")}];`,
577
+ );
162
578
  }
163
579
  contractTsLines.push(` }`);
164
580
  } else {
@@ -172,15 +588,221 @@ function _renderContractTs({ contractName, abi, bytecode, docs }) {
172
588
  return contractTsLines.join("\n") + "\n";
173
589
  }
174
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
+
175
776
  function _renderFactoryTs({ contractName, abi }) {
176
777
  const factoryName = `${contractName}__factory`;
177
778
  const ctor = _findConstructor(abi);
178
779
  const ctorInputs = (ctor && ctor.inputs) || [];
780
+ const tupleReg = _collectTupleRegistry(contractName, abi);
179
781
 
180
782
  const factoryTsLines = [];
181
- factoryTsLines.push(`// Auto-generated by quantumcoin-sdk-generator`);
783
+ factoryTsLines.push(`// Auto-generated by sdkgen`);
182
784
  factoryTsLines.push(`import { ContractFactory, ContractRunner, getCreateAddress } from "quantumcoin";`);
183
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
+ }
184
806
  factoryTsLines.push(``);
185
807
  factoryTsLines.push(`export class ${factoryName} extends ContractFactory {`);
186
808
  factoryTsLines.push(` constructor(runner: ContractRunner) {`);
@@ -190,7 +812,7 @@ function _renderFactoryTs({ contractName, abi }) {
190
812
 
191
813
  // Typed deploy method (uses constructor args + optional overrides)
192
814
  const deployArgsSig = ctorInputs
193
- .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solTypeToTs(p.type)}`)
815
+ .map((p, i) => `${_safeIdent(p.name || `arg${i}`)}: ${_solParamToTs(p, "input", tupleReg)}`)
194
816
  .join(", ");
195
817
  const deployArgsNames = ctorInputs.map((p, i) => _safeIdent(p.name || `arg${i}`)).join(", ");
196
818
 
@@ -217,9 +839,112 @@ function _renderFactoryTs({ contractName, abi }) {
217
839
  return factoryTsLines.join("\n") + "\n";
218
840
  }
219
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
+
220
921
  function _renderIndexTs(contractNames) {
221
922
  const lines = [];
222
- lines.push(`// Auto-generated by quantumcoin-sdk-generator`);
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`);
223
948
  lines.push(`export * from "./types";`);
224
949
  for (const name of contractNames) {
225
950
  lines.push(`export * from "./${name}";`);
@@ -241,7 +966,64 @@ function generateTransactionalTestJs(opts) {
241
966
  const ctor = _findConstructor(abi);
242
967
  const ctorInputs = ctor.inputs || [];
243
968
 
244
- const ctorArgsExpr = ctorInputs.map((i) => _solTypeToTestValueExpr(i.type)).join(", ");
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
+ : "";
245
1027
 
246
1028
  // Pick first view function with no inputs for state reads (optional).
247
1029
  const viewNoArg = (abi || []).find(
@@ -263,9 +1045,9 @@ function generateTransactionalTestJs(opts) {
263
1045
  const writeName = writeFn ? writeFn.name : null;
264
1046
  const writeArgsExpr =
265
1047
  writeFn && (writeFn.inputs || []).length === 1 && (writeFn.inputs[0].type || "").startsWith("uint")
266
- ? "456n"
1048
+ ? "456"
267
1049
  : writeFn
268
- ? (writeFn.inputs || []).map((i) => _solTypeToTestValueExpr(i.type)).join(", ")
1050
+ ? (writeFn.inputs || []).map((i) => _solTypeToTestValueExpr(i)).join(", ")
269
1051
  : "";
270
1052
 
271
1053
  const canAssertValueChange =
@@ -295,9 +1077,11 @@ const { describe, it } = require("node:test");
295
1077
  const assert = require("node:assert/strict");
296
1078
 
297
1079
  const { Initialize } = require("quantumcoin/config");
298
- const { JsonRpcProvider, Wallet, encodeBytes32String } = require("quantumcoin");
1080
+ const { JsonRpcProvider, Wallet } = require("quantumcoin");
299
1081
 
300
- const { ${contractName}, ${factoryName} } = require("../dist");
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("../..");
301
1085
 
302
1086
  // Hardcoded test wallet (test-only; never use for real funds)
303
1087
  const TEST_WALLET_ENCRYPTED_JSON =
@@ -306,17 +1090,9 @@ const TEST_WALLET_ENCRYPTED_JSON =
306
1090
  )};
307
1091
  const TEST_WALLET_PASSPHRASE = "QuantumCoinExample123!";
308
1092
 
309
- function getRpcUrl() {
310
- const env = process.env.QC_RPC_URL;
311
- if (env) return env;
312
- const arg = process.argv.find((a) => a.startsWith("--rpc=") || a.startsWith("--rpcUrl=") || a.startsWith("--rpc-url="));
313
- if (!arg) return null;
314
- return arg.split("=", 2)[1];
315
- }
316
-
317
1093
  describe("${contractName} transactional", () => {
318
1094
  it("deploys and invokes contract", async (t) => {
319
- const rpcUrl = getRpcUrl();
1095
+ const rpcUrl = process.env.QC_RPC_URL;
320
1096
  if (!rpcUrl) {
321
1097
  t.skip("QC_RPC_URL not provided");
322
1098
  return;
@@ -329,15 +1105,33 @@ describe("${contractName} transactional", () => {
329
1105
  const wallet = Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE, provider);
330
1106
 
331
1107
  const factory = new ${factoryName}(wallet);
332
- const contract = await factory.deploy(${ctorArgsExpr}${ctorArgsExpr ? ", " : ""}{ gasLimit: 600000 });
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 });
333
1123
 
334
1124
  const deployTx = contract.deployTransaction();
335
1125
  assert.ok(deployTx && deployTx.hash);
336
- await deployTx.wait(1, 600_000);
1126
+ const deployReceipt = await deployTx.wait(1, 600_000);
1127
+ assert.ok(deployReceipt);
1128
+ assert.ok(deployReceipt.blockNumber != null);
337
1129
 
338
1130
  const code = await provider.getCode(contract.target, "latest");
339
1131
  assert.ok(code && code !== "0x");
340
1132
 
1133
+ ${erc20Assertions ? erc20Assertions : `// (no ERC-20 surface detected for extra assertions)`}
1134
+
341
1135
  ${viewNoArg ? `// Basic view call
342
1136
  const before = await contract.${viewNoArg.name}();
343
1137
  void before;` : `// No zero-arg view method detected; deployment is still validated.`}
@@ -348,14 +1142,162 @@ describe("${contractName} transactional", () => {
348
1142
 
349
1143
  ${canAssertValueChange ? `// Assert value change (best-effort; normalize to BigInt)
350
1144
  const after = await contract.${viewNoArg.name}();
351
- const afterValue = Array.isArray(after) ? after[0] : after;
352
- const afterBI = typeof afterValue === "bigint" ? afterValue : BigInt(afterValue);
353
- assert.equal(afterBI, 456n);` : `// No compatible getter+setter pair detected for value assertions.`}
1145
+ assert.equal(after, 456n);` : `// No compatible getter+setter pair detected for value assertions.`}
354
1146
  }, { timeout: 900_000 });
355
1147
  });
356
1148
  `;
357
1149
  }
358
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
+
359
1301
  /**
360
1302
  * @typedef {Object} GenerateOptions
361
1303
  * @property {string} abiPath
@@ -374,28 +1316,63 @@ describe("${contractName} transactional", () => {
374
1316
  */
375
1317
  function generateFromArtifacts(opts) {
376
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
+ }
377
1323
 
378
1324
  const contractNames = opts.artifacts.map((a) => a.contractName);
379
1325
 
380
- const typesFile = path.join(opts.outDir, `types.ts`);
381
- const indexFile = path.join(opts.outDir, `index.ts`);
382
- fs.writeFileSync(typesFile, _typesTs(), "utf8");
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
+ }
383
1337
 
384
1338
  const contracts = [];
385
1339
  for (const a of opts.artifacts) {
386
- const contractFile = path.join(opts.outDir, `${a.contractName}.ts`);
387
- const factoryFile = path.join(opts.outDir, `${a.contractName}__factory.ts`);
388
- fs.writeFileSync(
389
- contractFile,
390
- _renderContractTs({ contractName: a.contractName, abi: a.abi, bytecode: a.bytecode, docs: a.docs || null }),
391
- "utf8",
392
- );
393
- fs.writeFileSync(factoryFile, _renderFactoryTs({ contractName: a.contractName, abi: a.abi }), "utf8");
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
+ }
394
1365
  contracts.push({ contractFile, factoryFile });
395
1366
  }
396
1367
 
397
- fs.writeFileSync(indexFile, _renderIndexTs(contractNames), "utf8");
1368
+ fs.writeFileSync(indexFile, lang === "ts" ? _renderIndexTs(contractNames) : _renderIndexJs(contractNames), "utf8");
1369
+ if (lang === "js") {
1370
+ fs.writeFileSync(indexDtsFile, _renderIndexDts(contractNames), "utf8");
1371
+ }
398
1372
 
1373
+ if (lang === "js") {
1374
+ return { contracts, typesFile, indexFile, typesDtsFile, indexDtsFile };
1375
+ }
399
1376
  return { contracts, typesFile, indexFile };
400
1377
  }
401
1378
 
@@ -415,6 +1392,7 @@ function generate(opts) {
415
1392
  const res = generateFromArtifacts({
416
1393
  outDir: opts.outDir,
417
1394
  artifacts: [{ contractName, abi, bytecode }],
1395
+ lang: opts.lang || "ts",
418
1396
  });
419
1397
 
420
1398
  const contractFile = res.contracts[0].contractFile;
@@ -422,5 +1400,5 @@ function generate(opts) {
422
1400
  return { contractFile, factoryFile, typesFile: res.typesFile, indexFile: res.indexFile };
423
1401
  }
424
1402
 
425
- module.exports = { generate, generateFromArtifacts, generateTransactionalTestJs };
1403
+ module.exports = { generate, generateFromArtifacts, generateTransactionalTestJs, generateAllContractsTransactionalTestJs };
426
1404