@aztec-labs/builder 6.0.0-nightly.20260829

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/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # Aztec builder
2
+
3
+ The Aztec builder generates typescript classes for Noir contract, as well as Aztec.nr interfaces for calling external functions.
4
+ It can also be used to update aztec project dependencies.
5
+
6
+ ## Installation
7
+
8
+ To install the package, run:
9
+
10
+ ```bash
11
+ yarn add @aztec-labs/builder
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ To run the tool, first install the package and then run:
17
+
18
+ ```bash
19
+ yarn aztec-builder --help
20
+ ```
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYmluL2NsaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/bin/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { createConsoleLogger } from '@aztec-labs/foundation/log';
3
+ import { Command } from 'commander';
4
+ import { injectCommands as injectBuilderCommands } from '../index.js';
5
+ const log = createConsoleLogger('aztec:builder');
6
+ const main = async ()=>{
7
+ const program = new Command('aztec-builder');
8
+ injectBuilderCommands(program);
9
+ await program.parseAsync(process.argv);
10
+ // I force exit here because spawnSync in npm.ts just blocks the process from exiting. Spent a bit of time debugging
11
+ // it without success and I think it doesn't make sense to invest more time in this.
12
+ process.exit(0);
13
+ };
14
+ main().catch((err)=>{
15
+ log(`Error running command`);
16
+ log(err);
17
+ process.exit(1);
18
+ });
@@ -0,0 +1,9 @@
1
+ /** Generate code options */
2
+ export type GenerateCodeOptions = {
3
+ force?: boolean;
4
+ };
5
+ /**
6
+ * Generates Noir interface or Typescript interface for a folder or single file from a Noir compilation artifact.
7
+ */
8
+ export declare function generateCode(outputPath: string, fileOrDirPath: string, opts?: GenerateCodeOptions): Promise<(string | undefined)[]>;
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29kZWdlbi5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbnRyYWN0LWludGVyZmFjZS1nZW4vY29kZWdlbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFXQSw0QkFBNEI7QUFDNUIsTUFBTSxNQUFNLG1CQUFtQixHQUFHO0lBQUUsS0FBSyxDQUFDLEVBQUUsT0FBTyxDQUFBO0NBQUUsQ0FBQztBQUV0RDs7R0FFRztBQUNILHdCQUFzQixZQUFZLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxhQUFhLEVBQUUsTUFBTSxFQUFFLElBQUksR0FBRSxtQkFBd0IsbUNBa0IzRyJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/contract-interface-gen/codegen.ts"],"names":[],"mappings":"AAWA,4BAA4B;AAC5B,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtD;;GAEG;AACH,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,GAAE,mBAAwB,mCAkB3G"}
@@ -0,0 +1,92 @@
1
+ /* eslint-disable no-console */ import { loadContractArtifact } from '@aztec-labs/stdlib/abi';
2
+ import crypto from 'crypto';
3
+ import { access, mkdir, readFile, readdir, stat, writeFile } from 'fs/promises';
4
+ import path from 'path';
5
+ import { generateTypescriptContractInterface } from './typescript.js';
6
+ const cacheFilePath = './codegenCache.json';
7
+ let cache = {};
8
+ /**
9
+ * Generates Noir interface or Typescript interface for a folder or single file from a Noir compilation artifact.
10
+ */ export async function generateCode(outputPath, fileOrDirPath, opts = {}) {
11
+ await readCache();
12
+ const results = [];
13
+ const stats = await stat(fileOrDirPath);
14
+ if (stats.isDirectory()) {
15
+ const files = (await readdir(fileOrDirPath, {
16
+ recursive: true,
17
+ encoding: 'utf-8'
18
+ })).filter((file)=>file.endsWith('.json') && !file.startsWith('debug_'));
19
+ for (const file of files){
20
+ const fullPath = path.join(fileOrDirPath, file);
21
+ results.push(await generateFromNoirAbi(outputPath, fullPath, opts));
22
+ }
23
+ } else if (stats.isFile()) {
24
+ results.push(await generateFromNoirAbi(outputPath, fileOrDirPath, opts));
25
+ }
26
+ await writeCache();
27
+ return results;
28
+ }
29
+ /**
30
+ * Generates Noir interface or Typescript interface for a single file Noir compilation artifact.
31
+ */ async function generateFromNoirAbi(outputPath, noirAbiPath, opts = {}) {
32
+ const fileName = path.basename(noirAbiPath);
33
+ const currentHash = await generateFileHash(noirAbiPath);
34
+ const cachedInstance = isCacheValid(fileName, currentHash);
35
+ if (cachedInstance && !opts.force) {
36
+ console.log(`${fileName} has not changed. Skipping generation.`);
37
+ return `${outputPath}/${cachedInstance.contractName}.ts`;
38
+ }
39
+ const file = await readFile(noirAbiPath, 'utf8');
40
+ const contract = JSON.parse(file);
41
+ if (!Array.isArray(contract.functions)) {
42
+ console.log(`${fileName} is not a contract artifact. Skipping.`);
43
+ return;
44
+ }
45
+ const aztecAbi = loadContractArtifact(contract);
46
+ await mkdir(outputPath, {
47
+ recursive: true
48
+ });
49
+ let relativeArtifactPath = path.relative(outputPath, noirAbiPath);
50
+ if (relativeArtifactPath === path.basename(noirAbiPath)) {
51
+ // Prepend ./ for local import if the folder is the same
52
+ relativeArtifactPath = `./${relativeArtifactPath}`;
53
+ }
54
+ const tsWrapper = await generateTypescriptContractInterface(aztecAbi, relativeArtifactPath);
55
+ const outputFilePath = `${outputPath}/${aztecAbi.name}.ts`;
56
+ await writeFile(outputFilePath, tsWrapper);
57
+ updateCache(fileName, aztecAbi.name, currentHash);
58
+ return outputFilePath;
59
+ }
60
+ async function generateFileHash(filePath) {
61
+ const fileBuffer = await readFile(filePath);
62
+ const hashSum = crypto.createHash('sha256');
63
+ hashSum.update(fileBuffer);
64
+ const hex = hashSum.digest('hex');
65
+ return hex;
66
+ }
67
+ async function readCache() {
68
+ if (await exists(cacheFilePath)) {
69
+ const cacheRaw = await readFile(cacheFilePath, 'utf8');
70
+ cache = JSON.parse(cacheRaw);
71
+ }
72
+ }
73
+ async function writeCache() {
74
+ await writeFile(cacheFilePath, JSON.stringify(cache, null, 2), 'utf8');
75
+ }
76
+ function isCacheValid(contractName, currentHash) {
77
+ return cache[contractName]?.hash === currentHash && cache[contractName];
78
+ }
79
+ function updateCache(fileName, contractName, hash) {
80
+ cache[fileName] = {
81
+ contractName,
82
+ hash
83
+ };
84
+ }
85
+ async function exists(filePath) {
86
+ try {
87
+ await access(filePath);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
@@ -0,0 +1,2 @@
1
+ export { generateTypescriptContractInterface } from './typescript.js';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb250cmFjdC1pbnRlcmZhY2UtZ2VuL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxtQ0FBbUMsRUFBRSxNQUFNLGlCQUFpQixDQUFDIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract-interface-gen/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ export { generateTypescriptContractInterface } from './typescript.js';
@@ -0,0 +1,9 @@
1
+ import { type ContractArtifact } from '@aztec-labs/stdlib/abi';
2
+ /**
3
+ * Generates the typescript code to represent a contract.
4
+ * @param input - The compiled Noir artifact.
5
+ * @param artifactImportPath - Optional path to import the artifact (if not set, will be required in the constructor).
6
+ * @returns The corresponding ts code.
7
+ */
8
+ export declare function generateTypescriptContractInterface(input: ContractArtifact, artifactImportPath?: string): Promise<string>;
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXNjcmlwdC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbnRyYWN0LWludGVyZmFjZS1nZW4vdHlwZXNjcmlwdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBR0wsS0FBSyxnQkFBZ0IsRUFhdEIsTUFBTSx3QkFBd0IsQ0FBQztBQXVSaEM7Ozs7O0dBS0c7QUFDSCx3QkFBc0IsbUNBQW1DLENBQUMsS0FBSyxFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixDQUFDLEVBQUUsTUFBTSxtQkFrRDdHIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typescript.d.ts","sourceRoot":"","sources":["../../src/contract-interface-gen/typescript.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EAatB,MAAM,wBAAwB,CAAC;AAuRhC;;;;;GAKG;AACH,wBAAsB,mCAAmC,CAAC,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,EAAE,MAAM,mBAkD7G"}
@@ -0,0 +1,296 @@
1
+ import { EventSelector, decodeFunctionSignature, getAllFunctionAbis, getDefaultInitializer, isAztecAddressStruct, isBoundedVecStruct, isEthAddressStruct, isFunctionSelectorStruct, isOptionStruct, isPublicKeysStruct, isWrappedFieldStruct } from '@aztec-labs/stdlib/abi';
2
+ /**
3
+ * Returns the corresponding typescript type for a given Noir type.
4
+ * @param type - The input Noir type.
5
+ * @returns An equivalent typescript type.
6
+ */ function abiTypeToTypescript(type) {
7
+ switch(type.kind){
8
+ case 'field':
9
+ return 'FieldLike';
10
+ case 'boolean':
11
+ return 'boolean';
12
+ case 'integer':
13
+ return '(bigint | number)';
14
+ case 'string':
15
+ return 'string';
16
+ case 'array':
17
+ return `${abiTypeToTypescript(type.type)}[]`;
18
+ case 'struct':
19
+ if (isEthAddressStruct(type)) {
20
+ return 'EthAddressLike';
21
+ }
22
+ if (isAztecAddressStruct(type)) {
23
+ return 'AztecAddressLike';
24
+ }
25
+ if (isFunctionSelectorStruct(type)) {
26
+ return 'FunctionSelectorLike';
27
+ }
28
+ if (isWrappedFieldStruct(type)) {
29
+ return 'WrappedFieldLike';
30
+ }
31
+ if (isPublicKeysStruct(type)) {
32
+ // PublicKeys are special cased due to them being part of the preimage of contract addresses.
33
+ // The proper type is expected by the TS code that deals with the ContractInstanceRegistry protocol contract.
34
+ return 'PublicKeys';
35
+ }
36
+ if (isBoundedVecStruct(type)) {
37
+ // To make BoundedVec easier to work with, we expect a simple array on the input and then we encode it
38
+ // as a BoundedVec in the ArgumentsEncoder.
39
+ return `${abiTypeToTypescript(type.fields[0].type)}`;
40
+ }
41
+ if (isOptionStruct(type)) {
42
+ return `OptionLike<${abiTypeToTypescript(type.fields[1].type)}>`;
43
+ }
44
+ return `{ ${type.fields.map((f)=>`${f.name}: ${abiTypeToTypescript(f.type)}`).join(', ')} }`;
45
+ default:
46
+ throw new Error(`Unknown type ${type.kind}`);
47
+ }
48
+ }
49
+ /**
50
+ * Generates the typescript code to represent a Noir parameter.
51
+ * @param param - A Noir parameter with name and type.
52
+ * @returns The corresponding ts code.
53
+ */ function generateParameter(param) {
54
+ return `${param.name}: ${abiTypeToTypescript(param.type)}`;
55
+ }
56
+ /**
57
+ * Generates the typescript code to represent a Noir function as a type.
58
+ * @param param - A Noir function.
59
+ * @returns The corresponding ts code.
60
+ */ function generateMethod(entry) {
61
+ const args = entry.parameters.map(generateParameter).join(', ');
62
+ return `
63
+ /** ${entry.name}(${entry.parameters.map((p)=>`${p.name}: ${p.type.kind}`).join(', ')}) */
64
+ ${entry.name}: ((${args}) => ContractFunctionInteraction) & Pick<ContractMethod, 'selector'>;`;
65
+ }
66
+ /**
67
+ * Generates a deploy method for this contract.
68
+ * @param input - Build artifact of the contract.
69
+ * @returns A type-safe deploy method in ts.
70
+ */ function generateDeploy(input) {
71
+ const ctor = getDefaultInitializer(input);
72
+ const ctorParams = ctor?.parameters ?? [];
73
+ const args = ctorParams.map(generateParameter).join(', ');
74
+ const argNames = ctorParams.map((p)=>p.name).join(', ');
75
+ const argsForwarding = argNames ? `[${argNames}]` : '[]';
76
+ const contractName = `${input.name}Contract`;
77
+ const artifactName = `${contractName}Artifact`;
78
+ return `
79
+ /**
80
+ * Creates a tx to deploy a new instance of this contract.
81
+ * @param instantiation - Optional address-affecting parameters (salt, deployer / universalDeploy, publicKeys).
82
+ * Salt defaults to a random value; the deployer is locked lazily from the first send-time \`from\`.
83
+ */
84
+ public static deploy(wallet: Wallet, ${args ? `${args}, ` : ''}instantiation?: DeployInstantiationOptions) {
85
+ return DeployMethod.create<${contractName}>(
86
+ wallet,
87
+ {
88
+ artifact: ${artifactName},
89
+ postDeployCtor: (instance, wallet) => ${contractName}.at(instance.address, wallet),
90
+ args: ${argsForwarding},
91
+ },
92
+ instantiation,
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Creates a tx to deploy a new instance of this contract using the specified constructor method.
98
+ */
99
+ public static deployWithOpts<M extends keyof ${contractName}['methods']>(
100
+ opts: { method?: M; wallet: Wallet; instantiation?: DeployInstantiationOptions },
101
+ ...args: Parameters<${contractName}['methods'][M]>
102
+ ) {
103
+ return DeployMethod.create<${contractName}>(
104
+ opts.wallet,
105
+ {
106
+ artifact: ${artifactName},
107
+ postDeployCtor: (instance, wallet) => ${contractName}.at(instance.address, wallet),
108
+ args,
109
+ constructorNameOrArtifact: opts.method ?? 'constructor',
110
+ },
111
+ opts.instantiation,
112
+ );
113
+ }
114
+ `;
115
+ }
116
+ /**
117
+ * Generates the constructor by supplying the ABI to the parent class so the user doesn't have to.
118
+ * @param name - Name of the contract to derive the ABI name from.
119
+ * @returns A constructor method.
120
+ * @remarks The constructor is private because we want to force the user to use the at method.
121
+ */ function generateConstructor(name) {
122
+ return `
123
+ private constructor(
124
+ address: AztecAddress,
125
+ wallet: Wallet,
126
+ ) {
127
+ super(address, ${name}ContractArtifact, wallet);
128
+ }
129
+ `;
130
+ }
131
+ /**
132
+ * Generates the at method for this contract.
133
+ * @param name - Name of the contract to derive the ABI name from.
134
+ * @returns An at method.
135
+ */ function generateAt(name) {
136
+ return `
137
+ /**
138
+ * Creates a contract instance.
139
+ * @param address - The deployed contract's address.
140
+ * @param wallet - The wallet to use when interacting with the contract.
141
+ * @returns A new Contract instance.
142
+ */
143
+ public static at(
144
+ address: AztecAddress,
145
+ wallet: Wallet,
146
+ ): ${name}Contract {
147
+ return Contract.at(address, ${name}Contract.artifact, wallet) as ${name}Contract;
148
+ }`;
149
+ }
150
+ /**
151
+ * Generates static getters for the contract's artifact.
152
+ * @param name - Name of the contract used to derive name of the artifact import.
153
+ */ function generateArtifactGetters(name) {
154
+ const artifactName = `${name}ContractArtifact`;
155
+ return `
156
+ /**
157
+ * Returns this contract's artifact.
158
+ */
159
+ public static get artifact(): ContractArtifact {
160
+ return ${artifactName};
161
+ }
162
+
163
+ /**
164
+ * Returns this contract's artifact with public bytecode.
165
+ */
166
+ public static get artifactForPublic(): ContractArtifact {
167
+ return loadContractArtifactForPublic(${artifactName}Json as NoirCompiledContract);
168
+ }
169
+ `;
170
+ }
171
+ /**
172
+ * Generates statements for importing the artifact from json and re-exporting it.
173
+ * @param name - Name of the contract.
174
+ * @param artifactImportPath - Path to load the ABI from.
175
+ * @returns Code.
176
+ */ function generateAbiStatement(name, artifactImportPath) {
177
+ const stmts = [
178
+ `import ${name}ContractArtifactJson from '${artifactImportPath}' with { type: 'json' };`,
179
+ `export const ${name}ContractArtifact = loadContractArtifact(${name}ContractArtifactJson as NoirCompiledContract);`
180
+ ];
181
+ return stmts.join('\n');
182
+ }
183
+ /**
184
+ * Generates a getter for the contract's storage layout.
185
+ * @param input - The contract artifact.
186
+ */ function generateStorageLayoutGetter(input) {
187
+ const entries = Object.entries(input.storageLayout);
188
+ if (entries.length === 0) {
189
+ return '';
190
+ }
191
+ const storageFieldsUnionType = entries.map(([name])=>`'${name}'`).join(' | ');
192
+ const layout = entries.map(([name, { slot }])=>`${name}: {
193
+ slot: new Fr(${slot.toBigInt()}n),
194
+ }`).join(',\n');
195
+ return `public static get storage(): ContractStorageLayout<${storageFieldsUnionType}> {
196
+ return {
197
+ ${layout}
198
+ } as ContractStorageLayout<${storageFieldsUnionType}>;
199
+ }
200
+ `;
201
+ }
202
+ // events is of type AbiType
203
+ async function generateEvents(events) {
204
+ if (events === undefined) {
205
+ return {
206
+ events: '',
207
+ eventDefs: ''
208
+ };
209
+ }
210
+ const eventsMetadata = await Promise.all(events.map(async (event)=>{
211
+ const eventName = event.path.split('::').at(-1);
212
+ const eventDefProps = event.fields.map((field)=>`${field.name}: ${abiTypeToTypescript(field.type)}`);
213
+ const eventDef = `
214
+ export type ${eventName} = {
215
+ ${eventDefProps.join('\n')}
216
+ }
217
+ `;
218
+ const fieldNames = event.fields.map((field)=>`"${field.name}"`);
219
+ const eventType = `${eventName}: {abiType: AbiType, eventSelector: EventSelector, fieldNames: string[] }`;
220
+ // Reusing the decodeFunctionSignature
221
+ const eventSignature = decodeFunctionSignature(eventName, event.fields);
222
+ const eventSelector = await EventSelector.fromSignature(eventSignature);
223
+ const eventImpl = `${eventName}: {
224
+ abiType: ${JSON.stringify(event, null, 4)},
225
+ eventSelector: EventSelector.fromString("${eventSelector}"),
226
+ fieldNames: [${fieldNames}],
227
+ }`;
228
+ return {
229
+ eventDef,
230
+ eventType,
231
+ eventImpl
232
+ };
233
+ }));
234
+ return {
235
+ eventDefs: eventsMetadata.map(({ eventDef })=>eventDef).join('\n'),
236
+ events: `
237
+ public static get events(): { ${eventsMetadata.map(({ eventType })=>eventType).join(', ')} } {
238
+ return {
239
+ ${eventsMetadata.map(({ eventImpl })=>eventImpl).join(',\n')}
240
+ };
241
+ }
242
+ `
243
+ };
244
+ }
245
+ /**
246
+ * Generates the typescript code to represent a contract.
247
+ * @param input - The compiled Noir artifact.
248
+ * @param artifactImportPath - Optional path to import the artifact (if not set, will be required in the constructor).
249
+ * @returns The corresponding ts code.
250
+ */ export async function generateTypescriptContractInterface(input, artifactImportPath) {
251
+ const methods = getAllFunctionAbis(input).filter((f)=>!f.isOnlySelf).sort((a, b)=>a.name.localeCompare(b.name)).map(generateMethod);
252
+ const deploy = artifactImportPath && generateDeploy(input);
253
+ const ctor = artifactImportPath && generateConstructor(input.name);
254
+ const at = artifactImportPath && generateAt(input.name);
255
+ const artifactStatement = artifactImportPath && generateAbiStatement(input.name, artifactImportPath);
256
+ const artifactGetter = artifactImportPath && generateArtifactGetters(input.name);
257
+ const storageLayoutGetter = artifactImportPath && generateStorageLayoutGetter(input);
258
+ const { eventDefs, events } = await generateEvents(input.outputs.structs?.events);
259
+ return `
260
+ /* Autogenerated file, do not edit! */
261
+
262
+ /* eslint-disable */
263
+ import { AztecAddress, CompleteAddress } from '@aztec-labs/aztec.js/addresses';
264
+ import { type AbiType, type AztecAddressLike, type ContractArtifact, EventSelector, type EthAddressLike, type FieldLike, type FunctionSelectorLike, loadContractArtifact, loadContractArtifactForPublic, type NoirCompiledContract, type OptionLike, type U128Like, type WrappedFieldLike } from '@aztec-labs/aztec.js/abi';
265
+ import { Contract, ContractBase, ContractFunctionInteraction, type ContractMethod, type ContractStorageLayout, type DeployInstantiationOptions, DeployMethod } from '@aztec-labs/aztec.js/contracts';
266
+ import { EthAddress } from '@aztec-labs/aztec.js/addresses';
267
+ import { Fr, Point } from '@aztec-labs/aztec.js/fields';
268
+ import { type PublicKey, PublicKeys } from '@aztec-labs/aztec.js/keys';
269
+ import type { Wallet } from '@aztec-labs/aztec.js/wallet';
270
+ ${artifactStatement}
271
+
272
+ ${eventDefs}
273
+
274
+ /**
275
+ * Type-safe interface for contract ${input.name};
276
+ */
277
+ export class ${input.name}Contract extends ContractBase {
278
+ ${ctor}
279
+
280
+ ${at}
281
+
282
+ ${deploy}
283
+
284
+ ${artifactGetter}
285
+
286
+ ${storageLayoutGetter}
287
+
288
+ /** Type-safe wrappers for the public methods exposed by the contract. */
289
+ public declare methods: {
290
+ ${methods.join('\n')}
291
+ };
292
+
293
+ ${events}
294
+ }
295
+ `;
296
+ }
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare function injectCommands(program: Command): Command;
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFHekMsd0JBQWdCLGNBQWMsQ0FBQyxPQUFPLEVBQUUsT0FBTyxXQVk5QyJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,WAY9C"}
package/dest/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import { dirname } from 'path';
2
+ export function injectCommands(program) {
3
+ program.command('codegen').argument('<noir-abi-path>', 'Path to the Noir ABI or project dir.').option('-o, --outdir <path>', 'Output folder for the generated code.').option('-f, --force', 'Force code generation even when the contract has not changed.').description('Validates and generates an Aztec Contract ABI from Noir ABI.').action(async (noirAbiPath, { outdir, force })=>{
4
+ const { generateCode } = await import('./contract-interface-gen/codegen.js');
5
+ await generateCode(outdir || dirname(noirAbiPath), noirAbiPath, {
6
+ force
7
+ });
8
+ });
9
+ return program;
10
+ }
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@aztec-labs/builder",
3
+ "version": "6.0.0-nightly.20260829",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./dest/index.js",
7
+ "./cli": "./dest/bin/cli.js",
8
+ "./codegen": "./dest/contract-interface-gen/index.js"
9
+ },
10
+ "typedocOptions": {
11
+ "entryPoints": [
12
+ "./src/index.ts"
13
+ ],
14
+ "name": "Aztec builder",
15
+ "tsconfig": "./tsconfig.json"
16
+ },
17
+ "scripts": {
18
+ "build": "yarn clean && ../scripts/tsc.sh",
19
+ "build:dev": "../scripts/tsc.sh --watch",
20
+ "generate": "../scripts/tsc.sh",
21
+ "clean": "rm -rf ./dest .tsbuildinfo",
22
+ "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
23
+ },
24
+ "inherits": [
25
+ "../package.common.json"
26
+ ],
27
+ "jest": {
28
+ "moduleNameMapper": {
29
+ "^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
30
+ },
31
+ "moduleFileExtensions": [
32
+ "js",
33
+ "ts",
34
+ "cts"
35
+ ],
36
+ "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
37
+ "rootDir": "./src",
38
+ "transform": {
39
+ "^.+\\.tsx?$": [
40
+ "@swc/jest",
41
+ {
42
+ "jsc": {
43
+ "parser": {
44
+ "syntax": "typescript",
45
+ "decorators": true
46
+ },
47
+ "transform": {
48
+ "decoratorVersion": "2022-03"
49
+ }
50
+ }
51
+ }
52
+ ]
53
+ },
54
+ "extensionsToTreatAsEsm": [
55
+ ".ts"
56
+ ],
57
+ "reporters": [
58
+ "default"
59
+ ],
60
+ "testTimeout": 120000,
61
+ "setupFiles": [
62
+ "../../foundation/src/jest/setup.mjs"
63
+ ],
64
+ "testEnvironment": "../../foundation/src/jest/env.mjs",
65
+ "setupFilesAfterEnv": [
66
+ "../../foundation/src/jest/setupAfterEnv.mjs"
67
+ ]
68
+ },
69
+ "dependencies": {
70
+ "@aztec-labs/foundation": "6.0.0-nightly.20260829",
71
+ "@aztec-labs/stdlib": "6.0.0-nightly.20260829",
72
+ "commander": "^12.1.0"
73
+ },
74
+ "devDependencies": {
75
+ "@jest/globals": "^30.0.0",
76
+ "@types/jest": "^30.0.0",
77
+ "@types/node": "^22.15.17",
78
+ "@typescript/native-preview": "7.0.0-dev.20260113.1",
79
+ "jest": "^30.0.0",
80
+ "ts-node": "^10.9.1",
81
+ "typescript": "^5.3.3"
82
+ },
83
+ "files": [
84
+ "dest",
85
+ "src",
86
+ "!*.test.*"
87
+ ],
88
+ "types": "./dest/index.d.ts",
89
+ "engines": {
90
+ "node": ">=20.10"
91
+ }
92
+ }
package/src/bin/cli.ts ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { createConsoleLogger } from '@aztec-labs/foundation/log';
3
+ import { Command } from 'commander';
4
+
5
+ import { injectCommands as injectBuilderCommands } from '../index.js';
6
+
7
+ const log = createConsoleLogger('aztec:builder');
8
+
9
+ const main = async () => {
10
+ const program = new Command('aztec-builder');
11
+
12
+ injectBuilderCommands(program);
13
+ await program.parseAsync(process.argv);
14
+ // I force exit here because spawnSync in npm.ts just blocks the process from exiting. Spent a bit of time debugging
15
+ // it without success and I think it doesn't make sense to invest more time in this.
16
+ process.exit(0);
17
+ };
18
+
19
+ main().catch(err => {
20
+ log(`Error running command`);
21
+ log(err);
22
+ process.exit(1);
23
+ });
@@ -0,0 +1,111 @@
1
+ /* eslint-disable no-console */
2
+ import { loadContractArtifact } from '@aztec-labs/stdlib/abi';
3
+ import crypto from 'crypto';
4
+ import { access, mkdir, readFile, readdir, stat, writeFile } from 'fs/promises';
5
+ import path from 'path';
6
+
7
+ import { generateTypescriptContractInterface } from './typescript.js';
8
+
9
+ const cacheFilePath = './codegenCache.json';
10
+ let cache: Record<string, { contractName: string; hash: string }> = {};
11
+
12
+ /** Generate code options */
13
+ export type GenerateCodeOptions = { force?: boolean };
14
+
15
+ /**
16
+ * Generates Noir interface or Typescript interface for a folder or single file from a Noir compilation artifact.
17
+ */
18
+ export async function generateCode(outputPath: string, fileOrDirPath: string, opts: GenerateCodeOptions = {}) {
19
+ await readCache();
20
+ const results = [];
21
+ const stats = await stat(fileOrDirPath);
22
+
23
+ if (stats.isDirectory()) {
24
+ const files = (await readdir(fileOrDirPath, { recursive: true, encoding: 'utf-8' })).filter(
25
+ file => file.endsWith('.json') && !file.startsWith('debug_'),
26
+ );
27
+ for (const file of files) {
28
+ const fullPath = path.join(fileOrDirPath, file);
29
+ results.push(await generateFromNoirAbi(outputPath, fullPath, opts));
30
+ }
31
+ } else if (stats.isFile()) {
32
+ results.push(await generateFromNoirAbi(outputPath, fileOrDirPath, opts));
33
+ }
34
+ await writeCache();
35
+ return results;
36
+ }
37
+
38
+ /**
39
+ * Generates Noir interface or Typescript interface for a single file Noir compilation artifact.
40
+ */
41
+ async function generateFromNoirAbi(outputPath: string, noirAbiPath: string, opts: GenerateCodeOptions = {}) {
42
+ const fileName = path.basename(noirAbiPath);
43
+ const currentHash = await generateFileHash(noirAbiPath);
44
+ const cachedInstance = isCacheValid(fileName, currentHash);
45
+ if (cachedInstance && !opts.force) {
46
+ console.log(`${fileName} has not changed. Skipping generation.`);
47
+ return `${outputPath}/${cachedInstance.contractName}.ts`;
48
+ }
49
+
50
+ const file = await readFile(noirAbiPath, 'utf8');
51
+ const contract = JSON.parse(file);
52
+
53
+ if (!Array.isArray(contract.functions)) {
54
+ console.log(`${fileName} is not a contract artifact. Skipping.`);
55
+ return;
56
+ }
57
+
58
+ const aztecAbi = loadContractArtifact(contract);
59
+
60
+ await mkdir(outputPath, { recursive: true });
61
+
62
+ let relativeArtifactPath = path.relative(outputPath, noirAbiPath);
63
+ if (relativeArtifactPath === path.basename(noirAbiPath)) {
64
+ // Prepend ./ for local import if the folder is the same
65
+ relativeArtifactPath = `./${relativeArtifactPath}`;
66
+ }
67
+
68
+ const tsWrapper = await generateTypescriptContractInterface(aztecAbi, relativeArtifactPath);
69
+ const outputFilePath = `${outputPath}/${aztecAbi.name}.ts`;
70
+
71
+ await writeFile(outputFilePath, tsWrapper);
72
+
73
+ updateCache(fileName, aztecAbi.name, currentHash);
74
+ return outputFilePath;
75
+ }
76
+
77
+ async function generateFileHash(filePath: string) {
78
+ const fileBuffer = await readFile(filePath);
79
+ const hashSum = crypto.createHash('sha256');
80
+ hashSum.update(fileBuffer);
81
+ const hex = hashSum.digest('hex');
82
+ return hex;
83
+ }
84
+
85
+ async function readCache() {
86
+ if (await exists(cacheFilePath)) {
87
+ const cacheRaw = await readFile(cacheFilePath, 'utf8');
88
+ cache = JSON.parse(cacheRaw);
89
+ }
90
+ }
91
+
92
+ async function writeCache() {
93
+ await writeFile(cacheFilePath, JSON.stringify(cache, null, 2), 'utf8');
94
+ }
95
+
96
+ function isCacheValid(contractName: string, currentHash: string) {
97
+ return cache[contractName]?.hash === currentHash && cache[contractName];
98
+ }
99
+
100
+ function updateCache(fileName: string, contractName: string, hash: string): void {
101
+ cache[fileName] = { contractName, hash };
102
+ }
103
+
104
+ async function exists(filePath: string) {
105
+ try {
106
+ await access(filePath);
107
+ return true;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
@@ -0,0 +1 @@
1
+ export { generateTypescriptContractInterface } from './typescript.js';
@@ -0,0 +1,352 @@
1
+ import {
2
+ type ABIParameter,
3
+ type ABIVariable,
4
+ type ContractArtifact,
5
+ EventSelector,
6
+ type FunctionAbi,
7
+ decodeFunctionSignature,
8
+ getAllFunctionAbis,
9
+ getDefaultInitializer,
10
+ isAztecAddressStruct,
11
+ isBoundedVecStruct,
12
+ isEthAddressStruct,
13
+ isFunctionSelectorStruct,
14
+ isOptionStruct,
15
+ isPublicKeysStruct,
16
+ isWrappedFieldStruct,
17
+ } from '@aztec-labs/stdlib/abi';
18
+
19
+ /**
20
+ * Returns the corresponding typescript type for a given Noir type.
21
+ * @param type - The input Noir type.
22
+ * @returns An equivalent typescript type.
23
+ */
24
+ function abiTypeToTypescript(type: ABIParameter['type']): string {
25
+ switch (type.kind) {
26
+ case 'field':
27
+ return 'FieldLike';
28
+ case 'boolean':
29
+ return 'boolean';
30
+ case 'integer':
31
+ return '(bigint | number)';
32
+ case 'string':
33
+ return 'string';
34
+ case 'array':
35
+ return `${abiTypeToTypescript(type.type)}[]`;
36
+ case 'struct':
37
+ if (isEthAddressStruct(type)) {
38
+ return 'EthAddressLike';
39
+ }
40
+ if (isAztecAddressStruct(type)) {
41
+ return 'AztecAddressLike';
42
+ }
43
+ if (isFunctionSelectorStruct(type)) {
44
+ return 'FunctionSelectorLike';
45
+ }
46
+ if (isWrappedFieldStruct(type)) {
47
+ return 'WrappedFieldLike';
48
+ }
49
+ if (isPublicKeysStruct(type)) {
50
+ // PublicKeys are special cased due to them being part of the preimage of contract addresses.
51
+ // The proper type is expected by the TS code that deals with the ContractInstanceRegistry protocol contract.
52
+ return 'PublicKeys';
53
+ }
54
+ if (isBoundedVecStruct(type)) {
55
+ // To make BoundedVec easier to work with, we expect a simple array on the input and then we encode it
56
+ // as a BoundedVec in the ArgumentsEncoder.
57
+ return `${abiTypeToTypescript(type.fields[0].type)}`;
58
+ }
59
+ if (isOptionStruct(type)) {
60
+ return `OptionLike<${abiTypeToTypescript(type.fields[1].type)}>`;
61
+ }
62
+ return `{ ${type.fields.map(f => `${f.name}: ${abiTypeToTypescript(f.type)}`).join(', ')} }`;
63
+ default:
64
+ throw new Error(`Unknown type ${type.kind}`);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Generates the typescript code to represent a Noir parameter.
70
+ * @param param - A Noir parameter with name and type.
71
+ * @returns The corresponding ts code.
72
+ */
73
+ function generateParameter(param: ABIParameter) {
74
+ return `${param.name}: ${abiTypeToTypescript(param.type)}`;
75
+ }
76
+
77
+ /**
78
+ * Generates the typescript code to represent a Noir function as a type.
79
+ * @param param - A Noir function.
80
+ * @returns The corresponding ts code.
81
+ */
82
+ function generateMethod(entry: FunctionAbi) {
83
+ const args = entry.parameters.map(generateParameter).join(', ');
84
+ return `
85
+ /** ${entry.name}(${entry.parameters.map(p => `${p.name}: ${p.type.kind}`).join(', ')}) */
86
+ ${entry.name}: ((${args}) => ContractFunctionInteraction) & Pick<ContractMethod, 'selector'>;`;
87
+ }
88
+
89
+ /**
90
+ * Generates a deploy method for this contract.
91
+ * @param input - Build artifact of the contract.
92
+ * @returns A type-safe deploy method in ts.
93
+ */
94
+ function generateDeploy(input: ContractArtifact) {
95
+ const ctor = getDefaultInitializer(input);
96
+ const ctorParams = ctor?.parameters ?? [];
97
+ const args = ctorParams.map(generateParameter).join(', ');
98
+ const argNames = ctorParams.map(p => p.name).join(', ');
99
+ const argsForwarding = argNames ? `[${argNames}]` : '[]';
100
+ const contractName = `${input.name}Contract`;
101
+ const artifactName = `${contractName}Artifact`;
102
+
103
+ return `
104
+ /**
105
+ * Creates a tx to deploy a new instance of this contract.
106
+ * @param instantiation - Optional address-affecting parameters (salt, deployer / universalDeploy, publicKeys).
107
+ * Salt defaults to a random value; the deployer is locked lazily from the first send-time \`from\`.
108
+ */
109
+ public static deploy(wallet: Wallet, ${args ? `${args}, ` : ''}instantiation?: DeployInstantiationOptions) {
110
+ return DeployMethod.create<${contractName}>(
111
+ wallet,
112
+ {
113
+ artifact: ${artifactName},
114
+ postDeployCtor: (instance, wallet) => ${contractName}.at(instance.address, wallet),
115
+ args: ${argsForwarding},
116
+ },
117
+ instantiation,
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Creates a tx to deploy a new instance of this contract using the specified constructor method.
123
+ */
124
+ public static deployWithOpts<M extends keyof ${contractName}['methods']>(
125
+ opts: { method?: M; wallet: Wallet; instantiation?: DeployInstantiationOptions },
126
+ ...args: Parameters<${contractName}['methods'][M]>
127
+ ) {
128
+ return DeployMethod.create<${contractName}>(
129
+ opts.wallet,
130
+ {
131
+ artifact: ${artifactName},
132
+ postDeployCtor: (instance, wallet) => ${contractName}.at(instance.address, wallet),
133
+ args,
134
+ constructorNameOrArtifact: opts.method ?? 'constructor',
135
+ },
136
+ opts.instantiation,
137
+ );
138
+ }
139
+ `;
140
+ }
141
+
142
+ /**
143
+ * Generates the constructor by supplying the ABI to the parent class so the user doesn't have to.
144
+ * @param name - Name of the contract to derive the ABI name from.
145
+ * @returns A constructor method.
146
+ * @remarks The constructor is private because we want to force the user to use the at method.
147
+ */
148
+ function generateConstructor(name: string) {
149
+ return `
150
+ private constructor(
151
+ address: AztecAddress,
152
+ wallet: Wallet,
153
+ ) {
154
+ super(address, ${name}ContractArtifact, wallet);
155
+ }
156
+ `;
157
+ }
158
+
159
+ /**
160
+ * Generates the at method for this contract.
161
+ * @param name - Name of the contract to derive the ABI name from.
162
+ * @returns An at method.
163
+ */
164
+ function generateAt(name: string) {
165
+ return `
166
+ /**
167
+ * Creates a contract instance.
168
+ * @param address - The deployed contract's address.
169
+ * @param wallet - The wallet to use when interacting with the contract.
170
+ * @returns A new Contract instance.
171
+ */
172
+ public static at(
173
+ address: AztecAddress,
174
+ wallet: Wallet,
175
+ ): ${name}Contract {
176
+ return Contract.at(address, ${name}Contract.artifact, wallet) as ${name}Contract;
177
+ }`;
178
+ }
179
+
180
+ /**
181
+ * Generates static getters for the contract's artifact.
182
+ * @param name - Name of the contract used to derive name of the artifact import.
183
+ */
184
+ function generateArtifactGetters(name: string) {
185
+ const artifactName = `${name}ContractArtifact`;
186
+ return `
187
+ /**
188
+ * Returns this contract's artifact.
189
+ */
190
+ public static get artifact(): ContractArtifact {
191
+ return ${artifactName};
192
+ }
193
+
194
+ /**
195
+ * Returns this contract's artifact with public bytecode.
196
+ */
197
+ public static get artifactForPublic(): ContractArtifact {
198
+ return loadContractArtifactForPublic(${artifactName}Json as NoirCompiledContract);
199
+ }
200
+ `;
201
+ }
202
+
203
+ /**
204
+ * Generates statements for importing the artifact from json and re-exporting it.
205
+ * @param name - Name of the contract.
206
+ * @param artifactImportPath - Path to load the ABI from.
207
+ * @returns Code.
208
+ */
209
+ function generateAbiStatement(name: string, artifactImportPath: string) {
210
+ const stmts = [
211
+ `import ${name}ContractArtifactJson from '${artifactImportPath}' with { type: 'json' };`,
212
+ `export const ${name}ContractArtifact = loadContractArtifact(${name}ContractArtifactJson as NoirCompiledContract);`,
213
+ ];
214
+ return stmts.join('\n');
215
+ }
216
+
217
+ /**
218
+ * Generates a getter for the contract's storage layout.
219
+ * @param input - The contract artifact.
220
+ */
221
+ function generateStorageLayoutGetter(input: ContractArtifact) {
222
+ const entries = Object.entries(input.storageLayout);
223
+
224
+ if (entries.length === 0) {
225
+ return '';
226
+ }
227
+
228
+ const storageFieldsUnionType = entries.map(([name]) => `'${name}'`).join(' | ');
229
+ const layout = entries
230
+ .map(
231
+ ([name, { slot }]) =>
232
+ `${name}: {
233
+ slot: new Fr(${slot.toBigInt()}n),
234
+ }`,
235
+ )
236
+ .join(',\n');
237
+
238
+ return `public static get storage(): ContractStorageLayout<${storageFieldsUnionType}> {
239
+ return {
240
+ ${layout}
241
+ } as ContractStorageLayout<${storageFieldsUnionType}>;
242
+ }
243
+ `;
244
+ }
245
+
246
+ // events is of type AbiType
247
+ async function generateEvents(events: any[] | undefined) {
248
+ if (events === undefined) {
249
+ return { events: '', eventDefs: '' };
250
+ }
251
+
252
+ const eventsMetadata = await Promise.all(
253
+ events.map(async event => {
254
+ const eventName = event.path.split('::').at(-1);
255
+
256
+ const eventDefProps = event.fields.map(
257
+ (field: ABIVariable) => `${field.name}: ${abiTypeToTypescript(field.type)}`,
258
+ );
259
+ const eventDef = `
260
+ export type ${eventName} = {
261
+ ${eventDefProps.join('\n')}
262
+ }
263
+ `;
264
+
265
+ const fieldNames = event.fields.map((field: any) => `"${field.name}"`);
266
+ const eventType = `${eventName}: {abiType: AbiType, eventSelector: EventSelector, fieldNames: string[] }`;
267
+ // Reusing the decodeFunctionSignature
268
+ const eventSignature = decodeFunctionSignature(eventName, event.fields);
269
+ const eventSelector = await EventSelector.fromSignature(eventSignature);
270
+ const eventImpl = `${eventName}: {
271
+ abiType: ${JSON.stringify(event, null, 4)},
272
+ eventSelector: EventSelector.fromString("${eventSelector}"),
273
+ fieldNames: [${fieldNames}],
274
+ }`;
275
+
276
+ return {
277
+ eventDef,
278
+ eventType,
279
+ eventImpl,
280
+ };
281
+ }),
282
+ );
283
+
284
+ return {
285
+ eventDefs: eventsMetadata.map(({ eventDef }) => eventDef).join('\n'),
286
+ events: `
287
+ public static get events(): { ${eventsMetadata.map(({ eventType }) => eventType).join(', ')} } {
288
+ return {
289
+ ${eventsMetadata.map(({ eventImpl }) => eventImpl).join(',\n')}
290
+ };
291
+ }
292
+ `,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Generates the typescript code to represent a contract.
298
+ * @param input - The compiled Noir artifact.
299
+ * @param artifactImportPath - Optional path to import the artifact (if not set, will be required in the constructor).
300
+ * @returns The corresponding ts code.
301
+ */
302
+ export async function generateTypescriptContractInterface(input: ContractArtifact, artifactImportPath?: string) {
303
+ const methods = getAllFunctionAbis(input)
304
+ .filter(f => !f.isOnlySelf)
305
+ .sort((a, b) => a.name.localeCompare(b.name))
306
+ .map(generateMethod);
307
+ const deploy = artifactImportPath && generateDeploy(input);
308
+ const ctor = artifactImportPath && generateConstructor(input.name);
309
+ const at = artifactImportPath && generateAt(input.name);
310
+ const artifactStatement = artifactImportPath && generateAbiStatement(input.name, artifactImportPath);
311
+ const artifactGetter = artifactImportPath && generateArtifactGetters(input.name);
312
+ const storageLayoutGetter = artifactImportPath && generateStorageLayoutGetter(input);
313
+ const { eventDefs, events } = await generateEvents(input.outputs.structs?.events);
314
+
315
+ return `
316
+ /* Autogenerated file, do not edit! */
317
+
318
+ /* eslint-disable */
319
+ import { AztecAddress, CompleteAddress } from '@aztec-labs/aztec.js/addresses';
320
+ import { type AbiType, type AztecAddressLike, type ContractArtifact, EventSelector, type EthAddressLike, type FieldLike, type FunctionSelectorLike, loadContractArtifact, loadContractArtifactForPublic, type NoirCompiledContract, type OptionLike, type U128Like, type WrappedFieldLike } from '@aztec-labs/aztec.js/abi';
321
+ import { Contract, ContractBase, ContractFunctionInteraction, type ContractMethod, type ContractStorageLayout, type DeployInstantiationOptions, DeployMethod } from '@aztec-labs/aztec.js/contracts';
322
+ import { EthAddress } from '@aztec-labs/aztec.js/addresses';
323
+ import { Fr, Point } from '@aztec-labs/aztec.js/fields';
324
+ import { type PublicKey, PublicKeys } from '@aztec-labs/aztec.js/keys';
325
+ import type { Wallet } from '@aztec-labs/aztec.js/wallet';
326
+ ${artifactStatement}
327
+
328
+ ${eventDefs}
329
+
330
+ /**
331
+ * Type-safe interface for contract ${input.name};
332
+ */
333
+ export class ${input.name}Contract extends ContractBase {
334
+ ${ctor}
335
+
336
+ ${at}
337
+
338
+ ${deploy}
339
+
340
+ ${artifactGetter}
341
+
342
+ ${storageLayoutGetter}
343
+
344
+ /** Type-safe wrappers for the public methods exposed by the contract. */
345
+ public declare methods: {
346
+ ${methods.join('\n')}
347
+ };
348
+
349
+ ${events}
350
+ }
351
+ `;
352
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { Command } from 'commander';
2
+ import { dirname } from 'path';
3
+
4
+ export function injectCommands(program: Command) {
5
+ program
6
+ .command('codegen')
7
+ .argument('<noir-abi-path>', 'Path to the Noir ABI or project dir.')
8
+ .option('-o, --outdir <path>', 'Output folder for the generated code.')
9
+ .option('-f, --force', 'Force code generation even when the contract has not changed.')
10
+ .description('Validates and generates an Aztec Contract ABI from Noir ABI.')
11
+ .action(async (noirAbiPath: string, { outdir, force }) => {
12
+ const { generateCode } = await import('./contract-interface-gen/codegen.js');
13
+ await generateCode(outdir || dirname(noirAbiPath), noirAbiPath, { force });
14
+ });
15
+ return program;
16
+ }