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