@aztec/builder 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +28 -0
  2. package/dest/cli/codegen.d.ts +10 -0
  3. package/dest/cli/codegen.d.ts.map +1 -0
  4. package/dest/cli/codegen.js +71 -0
  5. package/dest/cli/update/common.d.ts +17 -0
  6. package/dest/cli/update/common.d.ts.map +1 -0
  7. package/dest/cli/update/common.js +2 -0
  8. package/dest/cli/update/github.d.ts +4 -0
  9. package/dest/cli/update/github.d.ts.map +1 -0
  10. package/dest/cli/update/github.js +4 -0
  11. package/dest/cli/update/noir.d.ts +10 -0
  12. package/dest/cli/update/noir.d.ts.map +1 -0
  13. package/dest/cli/update/noir.js +47 -0
  14. package/dest/cli/update/npm.d.ts +34 -0
  15. package/dest/cli/update/npm.d.ts.map +1 -0
  16. package/dest/cli/update/npm.js +125 -0
  17. package/dest/cli/update/update.d.ts +3 -0
  18. package/dest/cli/update/update.d.ts.map +1 -0
  19. package/dest/cli/update/update.js +58 -0
  20. package/dest/cli/update/utils.d.ts +14 -0
  21. package/dest/cli/update/utils.d.ts.map +1 -0
  22. package/dest/cli/update/utils.js +43 -0
  23. package/dest/cli.d.ts +3 -0
  24. package/dest/cli.d.ts.map +1 -0
  25. package/dest/cli.js +51 -0
  26. package/dest/contract-interface-gen/typescript.d.ts +9 -0
  27. package/dest/contract-interface-gen/typescript.d.ts.map +1 -0
  28. package/dest/contract-interface-gen/typescript.js +278 -0
  29. package/dest/index.d.ts +2 -0
  30. package/dest/index.d.ts.map +1 -0
  31. package/dest/index.js +2 -0
  32. package/dest/mocked_keys.d.ts +2 -0
  33. package/dest/mocked_keys.d.ts.map +1 -0
  34. package/dest/mocked_keys.js +2 -0
  35. package/dest/utils.d.ts +10 -0
  36. package/dest/utils.d.ts.map +1 -0
  37. package/dest/utils.js +32 -0
  38. package/package.json +97 -0
  39. package/src/cli/codegen.ts +91 -0
  40. package/src/cli/update/common.ts +16 -0
  41. package/src/cli/update/github.ts +3 -0
  42. package/src/cli/update/noir.ts +57 -0
  43. package/src/cli/update/npm.ts +154 -0
  44. package/src/cli/update/update.ts +79 -0
  45. package/src/cli/update/utils.ts +50 -0
  46. package/src/cli.ts +60 -0
  47. package/src/contract-interface-gen/typescript.ts +311 -0
  48. package/src/fixtures/test_contract/Nargo.toml +8 -0
  49. package/src/fixtures/test_contract/src/main.nr +11 -0
  50. package/src/fixtures/test_lib/Nargo.toml +7 -0
  51. package/src/fixtures/test_lib/src/lib.nr +1 -0
  52. package/src/fixtures/test_lib/src/module/foo.nr +3 -0
  53. package/src/fixtures/test_lib/src/module.nr +1 -0
  54. package/src/fixtures/test_lib.zip +0 -0
  55. package/src/index.ts +1 -0
  56. package/src/mocked_keys.ts +2 -0
  57. package/src/utils.ts +33 -0
@@ -0,0 +1,154 @@
1
+ import { type LogFn } from '@aztec/foundation/log';
2
+
3
+ import { spawnSync } from 'child_process';
4
+ import { existsSync } from 'fs';
5
+ import { readFile } from 'fs/promises';
6
+ import { join, relative, resolve } from 'path';
7
+ import { type SemVer, parse } from 'semver';
8
+
9
+ import { type DependencyChanges } from './common.js';
10
+ import { atomicUpdateFile } from './utils.js';
11
+
12
+ const deprecatedNpmPackages = new Set<string>(['@aztec/cli', '@aztec/aztec-sandbox']);
13
+ const npmDeprecationMessage = `
14
+ The following packages have been deprecated and will no longer be updated on the npm registry:
15
+ ${Array.from(deprecatedNpmPackages)
16
+ .map(pkg => ` - ${pkg}`)
17
+ .join('\n')}
18
+ Remove them from package.json
19
+ `;
20
+
21
+ /**
22
+ * Looks up a package.json file and returns its contents
23
+ * @param projectPath - Path to Nodejs project
24
+ * @returns The parsed package.json
25
+ */
26
+ export async function readPackageJson(projectPath: string): Promise<{
27
+ /** dependencies */
28
+ dependencies?: Record<string, string>;
29
+ /** devDependencies */
30
+ devDependencies?: Record<string, string>;
31
+ }> {
32
+ const configFilepath = resolve(join(projectPath, 'package.json'));
33
+ const pkg = JSON.parse(await readFile(configFilepath, 'utf-8'));
34
+
35
+ return pkg;
36
+ }
37
+
38
+ /**
39
+ * Queries the npm registry for the latest version of a package
40
+ * @param packageName - The package to query
41
+ * @param distTag - The distribution tag
42
+ * @returns The latest version of the package on that distribution tag
43
+ */
44
+ export async function getNewestVersion(packageName: string, distTag = 'latest'): Promise<SemVer> {
45
+ const url = new URL(packageName, 'https://registry.npmjs.org/');
46
+ const response = await fetch(url);
47
+ if (!response.ok) {
48
+ throw new Error(`Failed to fetch ${url}`);
49
+ }
50
+
51
+ const body = await response.json();
52
+ const latestVersion = parse(body['dist-tags'][distTag]);
53
+ if (!latestVersion) {
54
+ throw new Error(`Failed to get latest version from registry`);
55
+ }
56
+
57
+ return latestVersion;
58
+ }
59
+
60
+ /**
61
+ * Updates a project's \@aztec/* dependencies to the specific version
62
+ * @param projectPath - Path to Nodejs project
63
+ * @param aztecVersion - The version to update to
64
+ * @returns True if the project was updated
65
+ */
66
+ export async function updateAztecDeps(
67
+ projectPath: string,
68
+ aztecVersion: SemVer,
69
+ log: LogFn,
70
+ ): Promise<DependencyChanges> {
71
+ const pkg = await readPackageJson(projectPath);
72
+ const changes: DependencyChanges = {
73
+ file: resolve(join(projectPath, 'package.json')),
74
+ dependencies: [],
75
+ };
76
+
77
+ log(`Updating @aztec packages to ${aztecVersion} in ${relative(process.cwd(), changes.file)}`);
78
+ const version = aztecVersion.version;
79
+
80
+ let detectedDeprecatedPackages = false;
81
+
82
+ for (const depType of ['dependencies', 'devDependencies'] as const) {
83
+ const dependencies = pkg[depType];
84
+ if (!dependencies) {
85
+ continue;
86
+ }
87
+
88
+ for (const name of Object.keys(dependencies)) {
89
+ if (!name.startsWith('@aztec/')) {
90
+ continue;
91
+ }
92
+
93
+ // different release schedule
94
+ if (name === '@aztec/aztec-ui') {
95
+ continue;
96
+ }
97
+
98
+ if (deprecatedNpmPackages.has(name)) {
99
+ detectedDeprecatedPackages = true;
100
+ continue;
101
+ }
102
+
103
+ if (dependencies[name] !== version) {
104
+ changes.dependencies.push({
105
+ name,
106
+ from: dependencies[name],
107
+ to: version,
108
+ });
109
+
110
+ dependencies[name] = version;
111
+ }
112
+ }
113
+ }
114
+
115
+ if (detectedDeprecatedPackages) {
116
+ log(npmDeprecationMessage);
117
+ }
118
+
119
+ if (changes.dependencies.length > 0) {
120
+ const contents = JSON.stringify(pkg, null, 2) + '\n';
121
+ await atomicUpdateFile(resolve(join(projectPath, 'package.json')), contents);
122
+ }
123
+
124
+ return changes;
125
+ }
126
+
127
+ /**
128
+ * Updates a project's yarn.lock or package-lock.json
129
+ * @param projectPath - Path to Nodejs project
130
+ */
131
+ export function updateLockfile(projectPath: string, log: LogFn): void {
132
+ const isNpm = existsSync(resolve(join(projectPath, 'package-lock.json')));
133
+ const isYarn = existsSync(resolve(join(projectPath, 'yarn.lock')));
134
+ const isPnpm = existsSync(resolve(join(projectPath, 'pnpm-lock.yaml')));
135
+
136
+ if (isPnpm) {
137
+ spawnSync('pnpm', ['install'], {
138
+ cwd: projectPath,
139
+ stdio: 'inherit',
140
+ });
141
+ } else if (isYarn) {
142
+ spawnSync('yarn', ['install'], {
143
+ cwd: projectPath,
144
+ stdio: 'inherit',
145
+ });
146
+ } else if (isNpm) {
147
+ spawnSync('npm', ['install'], {
148
+ cwd: projectPath,
149
+ stdio: 'inherit',
150
+ });
151
+ } else {
152
+ log(`No lockfile found in ${projectPath}. Skipping lockfile update...`);
153
+ }
154
+ }
@@ -0,0 +1,79 @@
1
+ /* eslint-disable jsdoc/require-jsdoc */
2
+ import { type LogFn } from '@aztec/foundation/log';
3
+
4
+ import { relative, resolve } from 'path';
5
+ import { parse } from 'semver';
6
+
7
+ import { type DependencyChanges } from './common.js';
8
+ import { GITHUB_TAG_PREFIX } from './github.js';
9
+ import { updateAztecNr } from './noir.js';
10
+ import { getNewestVersion, updateAztecDeps, updateLockfile } from './npm.js';
11
+
12
+ const AZTECJS_PACKAGE = '@aztec/aztec.js';
13
+ const UPDATE_DOCS_URL = 'https://docs.aztec.network/developers/updating';
14
+
15
+ export async function update(
16
+ projectPath: string,
17
+ contracts: string[],
18
+ pxeUrl: string,
19
+ aztecVersion: string,
20
+ log: LogFn,
21
+ ): Promise<void> {
22
+ const targetAztecVersion =
23
+ aztecVersion === 'latest' ? await getNewestVersion(AZTECJS_PACKAGE, 'latest') : parse(aztecVersion);
24
+
25
+ if (!targetAztecVersion) {
26
+ throw new Error(`Invalid aztec version ${aztecVersion}`);
27
+ }
28
+
29
+ const projectDependencyChanges: DependencyChanges[] = [];
30
+ try {
31
+ const npmChanges = await updateAztecDeps(resolve(process.cwd(), projectPath), targetAztecVersion, log);
32
+ if (npmChanges.dependencies.length > 0) {
33
+ updateLockfile(projectPath, log);
34
+ }
35
+
36
+ projectDependencyChanges.push(npmChanges);
37
+ } catch (err) {
38
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
39
+ log(`No package.json found in ${projectPath}. Skipping npm update...`);
40
+ } else {
41
+ throw err;
42
+ }
43
+ }
44
+
45
+ for (const contract of contracts) {
46
+ try {
47
+ projectDependencyChanges.push(
48
+ await updateAztecNr(
49
+ resolve(process.cwd(), projectPath, contract),
50
+ `${GITHUB_TAG_PREFIX}-v${targetAztecVersion.version}`,
51
+ log,
52
+ ),
53
+ );
54
+ } catch (err) {
55
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
56
+ log(`No Nargo.toml found in ${relative(process.cwd(), contract)}. Skipping...`);
57
+ } else {
58
+ throw err;
59
+ }
60
+ }
61
+ }
62
+
63
+ log(`To update Docker containers follow instructions at ${UPDATE_DOCS_URL}`);
64
+
65
+ projectDependencyChanges.forEach(changes => {
66
+ printChanges(changes, log);
67
+ });
68
+ }
69
+
70
+ function printChanges(changes: DependencyChanges, log: LogFn): void {
71
+ log(`\nIn ${relative(process.cwd(), changes.file)}:`);
72
+ if (changes.dependencies.length === 0) {
73
+ log(' No changes');
74
+ } else {
75
+ changes.dependencies.forEach(({ name, from, to }) => {
76
+ log(` Updated ${name} from ${from} to ${to}`);
77
+ });
78
+ }
79
+ }
@@ -0,0 +1,50 @@
1
+ import { type NoirPackageConfig } from '@aztec/foundation/noir';
2
+
3
+ import TOML from '@iarna/toml';
4
+ import { CommanderError } from 'commander';
5
+ import { rename, writeFile } from 'fs/promises';
6
+
7
+ /**
8
+ * Updates a file in place atomically.
9
+ * @param filePath - Path to file
10
+ * @param contents - New contents to write
11
+ */
12
+ export async function atomicUpdateFile(filePath: string, contents: string) {
13
+ const tmpFilepath = filePath + '.tmp';
14
+ try {
15
+ await writeFile(tmpFilepath, contents, {
16
+ // let's crash if the tmp file already exists
17
+ flag: 'wx',
18
+ });
19
+ await rename(tmpFilepath, filePath);
20
+ } catch (e) {
21
+ if (e instanceof Error && 'code' in e && e.code === 'EEXIST') {
22
+ const commanderError = new CommanderError(
23
+ 1,
24
+ e.code,
25
+ `Temporary file already exists: ${tmpFilepath}. Delete this file and try again.`,
26
+ );
27
+ commanderError.nestedError = e.message;
28
+ throw commanderError;
29
+ } else {
30
+ throw e;
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Pretty prints Nargo.toml contents to a string
37
+ * @param config - Nargo.toml contents
38
+ * @returns The Nargo.toml contents as a string
39
+ */
40
+ export function prettyPrintNargoToml(config: NoirPackageConfig): string {
41
+ const withoutDependencies = Object.fromEntries(Object.entries(config).filter(([key]) => key !== 'dependencies'));
42
+
43
+ const partialToml = TOML.stringify(withoutDependencies);
44
+ const dependenciesToml = Object.entries(config.dependencies).map(([name, dep]) => {
45
+ const depToml = TOML.stringify.value(dep);
46
+ return `${name} = ${depToml}`;
47
+ });
48
+
49
+ return partialToml + '\n[dependencies]\n' + dependenciesToml.join('\n') + '\n';
50
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { createConsoleLogger } from '@aztec/foundation/log';
3
+
4
+ import { Command, Option } from 'commander';
5
+ import { lookup } from 'dns/promises';
6
+ import { dirname } from 'path';
7
+
8
+ const program = new Command();
9
+ const log = createConsoleLogger('aztec:compiler-cli');
10
+
11
+ /**
12
+ * If we can successfully resolve 'host.docker.internal', then we are running in a container, and we should treat
13
+ * localhost as being host.docker.internal.
14
+ */
15
+ const getLocalhost = () =>
16
+ lookup('host.docker.internal')
17
+ .then(() => 'host.docker.internal')
18
+ .catch(() => 'localhost');
19
+
20
+ const LOCALHOST = await getLocalhost();
21
+
22
+ const main = async () => {
23
+ const pxeOption = new Option('-u, --rpc-url <string>', 'URL of the PXE')
24
+ .env('PXE_URL')
25
+ .default(`http://${LOCALHOST}:8080`)
26
+ .makeOptionMandatory(true);
27
+
28
+ program.name('aztec-builder');
29
+ program
30
+ .command('codegen')
31
+ .argument('<noir-abi-path>', 'Path to the Noir ABI or project dir.')
32
+ .option('-o, --outdir <path>', 'Output folder for the generated code.')
33
+ .option('--force', 'Force code generation even when the contract has not changed.')
34
+ .description('Validates and generates an Aztec Contract ABI from Noir ABI.')
35
+ .action(async (noirAbiPath: string, { outdir, force }) => {
36
+ const { generateCode } = await import('./cli/codegen.js');
37
+ generateCode(outdir || dirname(noirAbiPath), noirAbiPath, { force });
38
+ });
39
+
40
+ program
41
+ .command('update')
42
+ .description('Updates Nodejs and Noir dependencies')
43
+ .argument('[projectPath]', 'Path to the project directory', process.cwd())
44
+ .option('--contract [paths...]', 'Paths to contracts to update dependencies', [])
45
+ .option('--aztec-version <semver>', 'The version to update Aztec packages to. Defaults to latest', 'latest')
46
+ .addOption(pxeOption)
47
+ .action(async (projectPath: string, options) => {
48
+ const { update } = await import('./cli/update/update.js');
49
+ const { contract, aztecVersion, rpcUrl } = options;
50
+ await update(projectPath, contract, rpcUrl, aztecVersion, log);
51
+ });
52
+
53
+ await program.parseAsync(process.argv);
54
+ };
55
+
56
+ main().catch(err => {
57
+ log(`Error running command`);
58
+ log(err);
59
+ process.exit(1);
60
+ });
@@ -0,0 +1,311 @@
1
+ import {
2
+ type ABIParameter,
3
+ type ContractArtifact,
4
+ type FunctionArtifact,
5
+ getDefaultInitializer,
6
+ isAztecAddressStruct,
7
+ isEthAddressStruct,
8
+ isFunctionSelectorStruct,
9
+ isWrappedFieldStruct,
10
+ } from '@aztec/foundation/abi';
11
+
12
+ /**
13
+ * Returns the corresponding typescript type for a given Noir type.
14
+ * @param type - The input Noir type.
15
+ * @returns An equivalent typescript type.
16
+ */
17
+ function abiTypeToTypescript(type: ABIParameter['type']): string {
18
+ switch (type.kind) {
19
+ case 'field':
20
+ return 'FieldLike';
21
+ case 'boolean':
22
+ return 'boolean';
23
+ case 'integer':
24
+ return '(bigint | number)';
25
+ case 'string':
26
+ return 'string';
27
+ case 'array':
28
+ return `${abiTypeToTypescript(type.type)}[]`;
29
+ case 'struct':
30
+ if (isEthAddressStruct(type)) {
31
+ return 'EthAddressLike';
32
+ }
33
+ if (isAztecAddressStruct(type)) {
34
+ return 'AztecAddressLike';
35
+ }
36
+ if (isFunctionSelectorStruct(type)) {
37
+ return 'FunctionSelectorLike';
38
+ }
39
+ if (isWrappedFieldStruct(type)) {
40
+ return 'WrappedFieldLike';
41
+ }
42
+ return `{ ${type.fields.map(f => `${f.name}: ${abiTypeToTypescript(f.type)}`).join(', ')} }`;
43
+ default:
44
+ throw new Error(`Unknown type ${type}`);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Generates the typescript code to represent a Noir parameter.
50
+ * @param param - A Noir parameter with name and type.
51
+ * @returns The corresponding ts code.
52
+ */
53
+ function generateParameter(param: ABIParameter) {
54
+ return `${param.name}: ${abiTypeToTypescript(param.type)}`;
55
+ }
56
+
57
+ /**
58
+ * Generates the typescript code to represent a Noir function as a type.
59
+ * @param param - A Noir function.
60
+ * @returns The corresponding ts code.
61
+ */
62
+ function generateMethod(entry: FunctionArtifact) {
63
+ const args = entry.parameters.map(generateParameter).join(', ');
64
+ return `
65
+ /** ${entry.name}(${entry.parameters.map(p => `${p.name}: ${p.type.kind}`).join(', ')}) */
66
+ ${entry.name}: ((${args}) => ContractFunctionInteraction) & Pick<ContractMethod, 'selector'>;`;
67
+ }
68
+
69
+ /**
70
+ * Generates a deploy method for this contract.
71
+ * @param input - Build artifact of the contract.
72
+ * @returns A type-safe deploy method in ts.
73
+ */
74
+ function generateDeploy(input: ContractArtifact) {
75
+ const ctor = getDefaultInitializer(input);
76
+ const args = (ctor?.parameters ?? []).map(generateParameter).join(', ');
77
+ const contractName = `${input.name}Contract`;
78
+ const artifactName = `${contractName}Artifact`;
79
+
80
+ return `
81
+ /**
82
+ * Creates a tx to deploy a new instance of this contract.
83
+ */
84
+ public static deploy(wallet: Wallet, ${args}) {
85
+ return new DeployMethod<${contractName}>(Fr.ZERO, wallet, ${artifactName}, ${contractName}.at, Array.from(arguments).slice(1));
86
+ }
87
+
88
+ /**
89
+ * Creates a tx to deploy a new instance of this contract using the specified public keys hash to derive the address.
90
+ */
91
+ public static deployWithPublicKeysHash(publicKeysHash: Fr, wallet: Wallet, ${args}) {
92
+ return new DeployMethod<${contractName}>(publicKeysHash, wallet, ${artifactName}, ${contractName}.at, Array.from(arguments).slice(2));
93
+ }
94
+
95
+ /**
96
+ * Creates a tx to deploy a new instance of this contract using the specified constructor method.
97
+ */
98
+ public static deployWithOpts<M extends keyof ${contractName}['methods']>(
99
+ opts: { publicKeysHash?: Fr; method?: M; wallet: Wallet },
100
+ ...args: Parameters<${contractName}['methods'][M]>
101
+ ) {
102
+ return new DeployMethod<${contractName}>(
103
+ opts.publicKeysHash ?? Fr.ZERO,
104
+ opts.wallet,
105
+ ${artifactName},
106
+ ${contractName}.at,
107
+ Array.from(arguments).slice(1),
108
+ opts.method ?? 'constructor',
109
+ );
110
+ }
111
+ `;
112
+ }
113
+
114
+ /**
115
+ * Generates the constructor by supplying the ABI to the parent class so the user doesn't have to.
116
+ * @param name - Name of the contract to derive the ABI name from.
117
+ * @returns A constructor method.
118
+ * @remarks The constructor is private because we want to force the user to use the create method.
119
+ */
120
+ function generateConstructor(name: string) {
121
+ return `
122
+ private constructor(
123
+ instance: ContractInstanceWithAddress,
124
+ wallet: Wallet,
125
+ ) {
126
+ super(instance, ${name}ContractArtifact, wallet);
127
+ }
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
+ * @remarks We don't use constructor directly because of the async `wallet.getContractData` call.
136
+ */
137
+ function generateAt(name: string) {
138
+ return `
139
+ /**
140
+ * Creates a contract instance.
141
+ * @param address - The deployed contract's address.
142
+ * @param wallet - The wallet to use when interacting with the contract.
143
+ * @returns A promise that resolves to a new Contract instance.
144
+ */
145
+ public static async at(
146
+ address: AztecAddress,
147
+ wallet: Wallet,
148
+ ) {
149
+ return Contract.at(address, ${name}Contract.artifact, wallet) as Promise<${name}Contract>;
150
+ }`;
151
+ }
152
+
153
+ /**
154
+ * Generates a static getter for the contract's artifact.
155
+ * @param name - Name of the contract used to derive name of the artifact import.
156
+ */
157
+ function generateArtifactGetter(name: string) {
158
+ const artifactName = `${name}ContractArtifact`;
159
+ return `
160
+ /**
161
+ * Returns this contract's artifact.
162
+ */
163
+ public static get artifact(): ContractArtifact {
164
+ return ${artifactName};
165
+ }
166
+ `;
167
+ }
168
+
169
+ /**
170
+ * Generates statements for importing the artifact from json and re-exporting it.
171
+ * @param name - Name of the contract.
172
+ * @param artifactImportPath - Path to load the ABI from.
173
+ * @returns Code.
174
+ */
175
+ function generateAbiStatement(name: string, artifactImportPath: string) {
176
+ const stmts = [
177
+ `import ${name}ContractArtifactJson from '${artifactImportPath}' assert { type: 'json' };`,
178
+ `export const ${name}ContractArtifact = loadContractArtifact(${name}ContractArtifactJson as NoirCompiledContract);`,
179
+ ];
180
+ return stmts.join('\n');
181
+ }
182
+
183
+ /**
184
+ * Generates a getter for the contract's storage layout.
185
+ * @param input - The contract artifact.
186
+ */
187
+ function generateStorageLayoutGetter(input: ContractArtifact) {
188
+ const entries = Object.entries(input.storageLayout);
189
+
190
+ if (entries.length === 0) {
191
+ return '';
192
+ }
193
+
194
+ const storageFieldsUnionType = entries.map(([name]) => `'${name}'`).join(' | ');
195
+ const layout = entries
196
+ .map(
197
+ ([name, { slot, typ }]) =>
198
+ `${name}: {
199
+ slot: new Fr(${slot.toBigInt()}n),
200
+ typ: "${typ}",
201
+ }`,
202
+ )
203
+ .join(',\n');
204
+
205
+ return `public static get storage(): ContractStorageLayout<${storageFieldsUnionType}> {
206
+ return {
207
+ ${layout}
208
+ } as ContractStorageLayout<${storageFieldsUnionType}>;
209
+ }
210
+ `;
211
+ }
212
+
213
+ /**
214
+ * Generates a getter for the contract notes
215
+ * @param input - The contract artifact.
216
+ */
217
+ function generateNotesGetter(input: ContractArtifact) {
218
+ const entries = Object.entries(input.notes);
219
+
220
+ if (entries.length === 0) {
221
+ return '';
222
+ }
223
+
224
+ const notesUnionType = entries.map(([name]) => `'${name}'`).join(' | ');
225
+ const noteMetadata = entries
226
+ .map(
227
+ ([name, { id }]) =>
228
+ `${name}: {
229
+ id: new Fr(${id.toBigInt()}n),
230
+ }`,
231
+ )
232
+ .join(',\n');
233
+
234
+ return `public static get notes(): ContractNotes<${notesUnionType}> {
235
+ return {
236
+ ${noteMetadata}
237
+ } as ContractNotes<${notesUnionType}>;
238
+ }
239
+ `;
240
+ }
241
+
242
+ /**
243
+ * Generates the typescript code to represent a contract.
244
+ * @param input - The compiled Noir artifact.
245
+ * @param artifactImportPath - Optional path to import the artifact (if not set, will be required in the constructor).
246
+ * @returns The corresponding ts code.
247
+ */
248
+ export function generateTypescriptContractInterface(input: ContractArtifact, artifactImportPath?: string) {
249
+ const methods = input.functions.filter(f => !f.isInternal).map(generateMethod);
250
+ const deploy = artifactImportPath && generateDeploy(input);
251
+ const ctor = artifactImportPath && generateConstructor(input.name);
252
+ const at = artifactImportPath && generateAt(input.name);
253
+ const artifactStatement = artifactImportPath && generateAbiStatement(input.name, artifactImportPath);
254
+ const artifactGetter = artifactImportPath && generateArtifactGetter(input.name);
255
+ const storageLayoutGetter = artifactImportPath && generateStorageLayoutGetter(input);
256
+ const notesGetter = artifactImportPath && generateNotesGetter(input);
257
+
258
+ return `
259
+ /* Autogenerated file, do not edit! */
260
+
261
+ /* eslint-disable */
262
+ import {
263
+ AztecAddress,
264
+ AztecAddressLike,
265
+ CompleteAddress,
266
+ Contract,
267
+ ContractArtifact,
268
+ ContractBase,
269
+ ContractFunctionInteraction,
270
+ ContractInstanceWithAddress,
271
+ ContractMethod,
272
+ ContractStorageLayout,
273
+ ContractNotes,
274
+ DeployMethod,
275
+ EthAddress,
276
+ EthAddressLike,
277
+ FieldLike,
278
+ Fr,
279
+ FunctionSelectorLike,
280
+ loadContractArtifact,
281
+ NoirCompiledContract,
282
+ Point,
283
+ PublicKey,
284
+ Wallet,
285
+ WrappedFieldLike,
286
+ } from '@aztec/aztec.js';
287
+ ${artifactStatement}
288
+
289
+ /**
290
+ * Type-safe interface for contract ${input.name};
291
+ */
292
+ export class ${input.name}Contract extends ContractBase {
293
+ ${ctor}
294
+
295
+ ${at}
296
+
297
+ ${deploy}
298
+
299
+ ${artifactGetter}
300
+
301
+ ${storageLayoutGetter}
302
+
303
+ ${notesGetter}
304
+
305
+ /** Type-safe wrappers for the public methods exposed by the contract. */
306
+ public override methods!: {
307
+ ${methods.join('\n')}
308
+ };
309
+ }
310
+ `;
311
+ }
@@ -0,0 +1,8 @@
1
+ [package]
2
+ name = "test"
3
+ authors = [""]
4
+ compiler_version = ">=0.18.0"
5
+ type = "contract"
6
+
7
+ [dependencies]
8
+ test = { path = "../test_lib" }
@@ -0,0 +1,11 @@
1
+ contract TestContract {
2
+ use dep::test::module::foo;
3
+
4
+ fn constructor(param: Field, pub_param: pub Field) -> pub [Field; 2] {
5
+ [foo::bar(param), param + pub_param]
6
+ }
7
+
8
+ open fn openFunction() -> pub Field {
9
+ 42
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ [package]
2
+ name = "testlib"
3
+ authors = [""]
4
+ compiler_version = ">=0.18.0"
5
+ type = "lib"
6
+
7
+ [dependencies]
@@ -0,0 +1 @@
1
+ mod module;
@@ -0,0 +1,3 @@
1
+ pub fn bar(param: Field) -> Field {
2
+ dep::std::hash::pedersen_hash([param])
3
+ }
@@ -0,0 +1 @@
1
+ mod foo;
Binary file
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { generateTypescriptContractInterface } from './contract-interface-gen/typescript.js';