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.
- package/.gitignore +3 -0
- package/README-SDK.md +64 -10
- package/README.md +27 -4
- package/SPEC.md +3843 -0
- package/examples/AllSolidityTypes.sol +184 -0
- package/examples/SimpleIERC20.sol +74 -0
- package/examples/example-generator-sdk-js.js +95 -0
- package/examples/example-generator-sdk-ts.js +95 -0
- package/examples/example.js +2 -2
- package/examples/offline-signing.js +73 -0
- package/examples/package-lock.json +10 -1103
- package/examples/package.json +1 -2
- package/examples/read-operations.js +1 -2
- package/examples/sdk-generator-erc20.inline.json +251 -0
- package/examples/solidity-types.ts +43 -0
- package/generate-sdk.js +689 -87
- package/package.json +30 -9
- package/src/abi/interface.d.ts +18 -0
- package/src/abi/interface.js +247 -9
- package/src/abi/js-abi-coder.js +474 -0
- package/src/contract/contract-factory.d.ts +1 -1
- package/src/contract/contract-factory.js +14 -2
- package/src/contract/contract.d.ts +10 -1
- package/src/contract/contract.js +42 -0
- package/src/generator/index.js +1041 -63
- package/src/index.d.ts +16 -0
- package/src/providers/provider.d.ts +20 -11
- package/src/providers/provider.js +12 -0
- package/src/types/index.d.ts +462 -0
- package/src/types/index.js +9 -0
- package/test/e2e/all-solidity-types.dynamic.test.js +200 -0
- package/test/e2e/all-solidity-types.fixtures.js +231 -0
- package/test/e2e/all-solidity-types.generated-sdks.e2e.test.js +368 -0
- package/test/e2e/simple-erc20.generated-sdks.e2e.test.js +151 -0
- package/test/e2e/transactional.test.js +4 -4
- package/test/e2e/typed-generator.e2e.test.js +8 -6
- package/test/integration/ws-provider.test.js +1 -1
- package/test/unit/generate-contract-cli.test.js +2 -1
- package/test/unit/generate-sdk-artifacts-json.test.js +45 -0
- package/test/unit/generator.test.js +1 -0
- package/test/unit/populate-transaction.test.js +62 -0
- package/test/unit/solidity-types.test.js +46 -0
- package/test/unit/utils.test.js +1 -1
package/src/generator/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Typed contract generator (SPEC.md section 15).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
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(
|
|
50
|
-
|
|
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
|
-
|
|
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("
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
481
|
+
contractTsLines.push(`// Auto-generated by sdkgen`);
|
|
93
482
|
contractTsLines.push(`import { Contract, ContractTransactionResponse, ContractRunner, TransactionResponse } from "quantumcoin";`);
|
|
94
|
-
contractTsLines.push(`import type
|
|
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}`)}: ${
|
|
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<
|
|
136
|
-
else if (outputs.length === 1) returnTs = `Promise<${
|
|
137
|
-
else returnTs = `Promise<[${outputs.map((o) =>
|
|
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 ===
|
|
159
|
-
contractTsLines.push(`
|
|
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(
|
|
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
|
|
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}`)}: ${
|
|
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
|
|
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
|
|
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
|
-
? "
|
|
1048
|
+
? "456"
|
|
267
1049
|
: writeFn
|
|
268
|
-
? (writeFn.inputs || []).map((i) => _solTypeToTestValueExpr(i
|
|
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
|
|
1080
|
+
const { JsonRpcProvider, Wallet } = require("quantumcoin");
|
|
299
1081
|
|
|
300
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
387
|
-
const factoryFile = path.join(opts.outDir, `${a.contractName}__factory
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
|