@aztec/cli 0.7.10 → 0.8.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/README.md +19 -19
- package/dest/bin/index.js +0 -0
- package/dest/client.d.ts +9 -15
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +14 -22
- package/dest/encoding.d.ts.map +1 -1
- package/dest/encoding.js +4 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +134 -106
- package/dest/unbox.d.ts.map +1 -1
- package/dest/unbox.js +40 -11
- package/dest/utils.d.ts +61 -8
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +169 -19
- package/package.json +61 -10
- package/src/client.ts +13 -22
- package/src/encoding.ts +3 -1
- package/src/index.ts +167 -136
- package/src/unbox.ts +45 -10
- package/src/utils.ts +192 -25
- package/.eslintrc.cjs +0 -1
- package/.tsbuildinfo +0 -1
- package/dest/client.test.d.ts +0 -2
- package/dest/client.test.d.ts.map +0 -1
- package/dest/client.test.js +0 -23
- package/dest/test/utils.test.d.ts +0 -2
- package/dest/test/utils.test.d.ts.map +0 -1
- package/dest/test/utils.test.js +0 -104
- package/src/client.test.ts +0 -34
- package/src/test/utils.test.ts +0 -133
- package/tsconfig.json +0 -30
package/src/unbox.ts
CHANGED
|
@@ -40,7 +40,7 @@ async function isDirectoryNonEmpty(directoryPath: string): Promise<boolean> {
|
|
|
40
40
|
* @param localOutputPath - local path to copy to
|
|
41
41
|
*/
|
|
42
42
|
async function copyFolderFromGithub(data: JSZip, repositoryFolderPath: string, localOutputPath: string, log: LogFn) {
|
|
43
|
-
log(`Downloading from github: ${repositoryFolderPath}`);
|
|
43
|
+
log(`Downloading folder from github: ${repositoryFolderPath}`);
|
|
44
44
|
const repositoryDirectories = Object.values(data.files).filter(file => {
|
|
45
45
|
return file.dir && file.name.startsWith(repositoryFolderPath);
|
|
46
46
|
});
|
|
@@ -51,11 +51,11 @@ async function copyFolderFromGithub(data: JSZip, repositoryFolderPath: string, l
|
|
|
51
51
|
await fs.mkdir(targetPath, { recursive: true });
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const folderFiles = Object.values(data.files).filter(file => {
|
|
55
55
|
return !file.dir && file.name.startsWith(repositoryFolderPath);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
for (const file of
|
|
58
|
+
for (const file of folderFiles) {
|
|
59
59
|
const relativePath = file.name.replace(repositoryFolderPath, '');
|
|
60
60
|
const targetPath = `${localOutputPath}/${relativePath}`;
|
|
61
61
|
const content = await file.async('nodebuffer');
|
|
@@ -63,6 +63,27 @@ async function copyFolderFromGithub(data: JSZip, repositoryFolderPath: string, l
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @param data - in memory unzipped clone of a github repo
|
|
68
|
+
* @param repositoryFile - path of the file to copy from github repo
|
|
69
|
+
* @param localOutputPath - local path to copy the file to
|
|
70
|
+
*/
|
|
71
|
+
async function copyFileFromGithub(data: JSZip, repositoryFile: string, localOutputPath: string, log: LogFn) {
|
|
72
|
+
log(`Downloading file from github: ${repositoryFile}`);
|
|
73
|
+
|
|
74
|
+
const file = data.files[repositoryFile];
|
|
75
|
+
|
|
76
|
+
if (!file || file.dir) {
|
|
77
|
+
throw new Error(`File not found or it's a directory: ${repositoryFile}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const filename = path.basename(repositoryFile);
|
|
81
|
+
const targetPath = `${localOutputPath}/${filename}`;
|
|
82
|
+
|
|
83
|
+
const content = await file.async('nodebuffer');
|
|
84
|
+
await fs.writeFile(targetPath, content);
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
/**
|
|
67
88
|
* Not flexible at at all, but quick fix to download a noir smart contract from our
|
|
68
89
|
* monorepo on github. this will copy over the `yarn-projects/boxes/{contract_name}` folder
|
|
@@ -98,12 +119,19 @@ async function downloadContractAndBoxFromGithub(
|
|
|
98
119
|
const boxPath = `${repoDirectoryPrefix}/${BOXES_PATH}/${contractName}`;
|
|
99
120
|
await copyFolderFromGithub(data, boxPath, outputPath, log);
|
|
100
121
|
|
|
122
|
+
// the expected noir version is contained in
|
|
123
|
+
// aztec-packages/yarn-project/noir-compiler/src/noir-version.json
|
|
124
|
+
// copy it in and use to update the package.json script to install that version of noir
|
|
125
|
+
const noirVersionPath = `${repoDirectoryPrefix}/yarn-project/noir-compiler/src/noir-version.json`;
|
|
126
|
+
await copyFileFromGithub(data, noirVersionPath, outputPath, log);
|
|
127
|
+
|
|
101
128
|
const contractTargetDirectory = path.join(outputPath, 'src', 'contracts');
|
|
102
129
|
const boxContainsNoirSource = await isDirectoryNonEmpty(contractTargetDirectory);
|
|
103
130
|
if (boxContainsNoirSource) {
|
|
104
131
|
return;
|
|
105
132
|
} else {
|
|
106
133
|
// we used to support downloading from the noir contracts monorepo but now force box to contain source code
|
|
134
|
+
// This should never happen, because of the check we do initially on the box name.
|
|
107
135
|
throw Error(`Box ${contractName} does not contain noir source code.`);
|
|
108
136
|
}
|
|
109
137
|
}
|
|
@@ -200,16 +228,23 @@ async function updatePackageJsonVersions(packageVersion: string, outputPath: str
|
|
|
200
228
|
}
|
|
201
229
|
}
|
|
202
230
|
}
|
|
231
|
+
// read the `noir-version.json`, grab the expected noir version, and patch the noir install script
|
|
232
|
+
const noirVersionPath = path.join(outputPath, 'noir-version.json');
|
|
233
|
+
const noirVersionContent = await fs.readFile(noirVersionPath, 'utf-8');
|
|
234
|
+
const noirVersionJSON = JSON.parse(noirVersionContent);
|
|
235
|
+
const noirTag = noirVersionJSON.tag;
|
|
236
|
+
packageData.scripts['install:noir'] = packageData.scripts['install:noir'].replace('NOIR_VERSION', `${noirTag}`);
|
|
237
|
+
log(`Updated Noir version to: ${noirTag}`);
|
|
203
238
|
|
|
204
239
|
// modify the version of the sandbox to pull - it's set to "latest" version in the monorepo,
|
|
205
240
|
// but we need to replace with the same tagVersion as the cli and the other aztec npm packages
|
|
206
241
|
// similarly, make sure we spinup the sandbox with the same version.
|
|
207
242
|
packageData.scripts['install:sandbox'] = packageData.scripts['install:sandbox'].replace(
|
|
208
243
|
'latest',
|
|
209
|
-
|
|
244
|
+
`${packageVersion}`,
|
|
210
245
|
);
|
|
211
246
|
|
|
212
|
-
packageData.scripts['start:sandbox'] = packageData.scripts['start:sandbox'].replace('latest',
|
|
247
|
+
packageData.scripts['start:sandbox'] = packageData.scripts['start:sandbox'].replace('latest', `${packageVersion}`);
|
|
213
248
|
|
|
214
249
|
// Convert back to a string and write back to the package.json file
|
|
215
250
|
const updatedContent = JSON.stringify(packageData, null, 2);
|
|
@@ -262,7 +297,7 @@ export async function unboxContract(
|
|
|
262
297
|
packageVersion: string,
|
|
263
298
|
log: LogFn,
|
|
264
299
|
) {
|
|
265
|
-
const contractNames = ['private-token', 'blank'];
|
|
300
|
+
const contractNames = ['private-token', 'blank', 'blank-react'];
|
|
266
301
|
|
|
267
302
|
if (!contractNames.includes(contractName)) {
|
|
268
303
|
log(
|
|
@@ -276,17 +311,17 @@ export async function unboxContract(
|
|
|
276
311
|
|
|
277
312
|
const tag = `${GITHUB_TAG_PREFIX}-v${packageVersion}`;
|
|
278
313
|
// downloads the selected contract's relevant folder in @aztec/boxes/{contract_name}
|
|
279
|
-
// and the noir source code from `noir-contracts` into `${outputDirectoryName}/src/contracts`
|
|
280
|
-
// if not present in the box
|
|
281
314
|
await downloadContractAndBoxFromGithub(tag, contractName, outputPath, log);
|
|
282
315
|
// make adjustments for packaging to work as a standalone, as opposed to part of yarn workspace
|
|
283
|
-
// as in the monorepo source files
|
|
316
|
+
// as in the monorepo source files. replace things like "workspace^" with the actual version number
|
|
284
317
|
await updatePackagingConfigurations(packageVersion, tag, outputPath, log);
|
|
285
318
|
|
|
286
319
|
log('');
|
|
287
|
-
log(`${contractName} has been successfully
|
|
320
|
+
log(`${contractName} has been successfully initialized!`);
|
|
288
321
|
log('To get started, simply run the following commands:');
|
|
289
322
|
log(` cd ${outputDirectoryName}`);
|
|
290
323
|
log(' yarn');
|
|
324
|
+
log(' yarn start:sandbox');
|
|
325
|
+
log('And in another terminal in the same directory,');
|
|
291
326
|
log(' yarn start:dev');
|
|
292
327
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import { AztecAddress,
|
|
2
|
-
import { createEthereumChain, deployL1Contracts } from '@aztec/ethereum';
|
|
1
|
+
import { AztecAddress, Fr, GrumpkinScalar, PXE, Point, TxHash } from '@aztec/aztec.js';
|
|
2
|
+
import { L1ContractArtifactsForDeployment, createEthereumChain, deployL1Contracts } from '@aztec/ethereum';
|
|
3
3
|
import { ContractAbi } from '@aztec/foundation/abi';
|
|
4
4
|
import { DebugLogger, LogFn } from '@aztec/foundation/log';
|
|
5
|
+
import {
|
|
6
|
+
ContractDeploymentEmitterAbi,
|
|
7
|
+
ContractDeploymentEmitterBytecode,
|
|
8
|
+
InboxAbi,
|
|
9
|
+
InboxBytecode,
|
|
10
|
+
OutboxAbi,
|
|
11
|
+
OutboxBytecode,
|
|
12
|
+
RegistryAbi,
|
|
13
|
+
RegistryBytecode,
|
|
14
|
+
RollupAbi,
|
|
15
|
+
RollupBytecode,
|
|
16
|
+
} from '@aztec/l1-artifacts';
|
|
5
17
|
|
|
18
|
+
import { InvalidArgumentError } from 'commander';
|
|
6
19
|
import fs from 'fs';
|
|
7
20
|
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
8
21
|
|
|
9
22
|
import { encodeArgs } from './encoding.js';
|
|
10
23
|
|
|
11
|
-
export { createClient } from './client.js';
|
|
12
24
|
/**
|
|
13
25
|
* Helper type to dynamically import contracts.
|
|
14
26
|
*/
|
|
@@ -46,7 +58,29 @@ export async function deployAztecContracts(
|
|
|
46
58
|
) {
|
|
47
59
|
const account = !privateKey ? mnemonicToAccount(mnemonic!) : privateKeyToAccount(`0x${privateKey}`);
|
|
48
60
|
const chain = createEthereumChain(rpcUrl, apiKey);
|
|
49
|
-
|
|
61
|
+
const l1Artifacts: L1ContractArtifactsForDeployment = {
|
|
62
|
+
contractDeploymentEmitter: {
|
|
63
|
+
contractAbi: ContractDeploymentEmitterAbi,
|
|
64
|
+
contractBytecode: ContractDeploymentEmitterBytecode,
|
|
65
|
+
},
|
|
66
|
+
registry: {
|
|
67
|
+
contractAbi: RegistryAbi,
|
|
68
|
+
contractBytecode: RegistryBytecode,
|
|
69
|
+
},
|
|
70
|
+
inbox: {
|
|
71
|
+
contractAbi: InboxAbi,
|
|
72
|
+
contractBytecode: InboxBytecode,
|
|
73
|
+
},
|
|
74
|
+
outbox: {
|
|
75
|
+
contractAbi: OutboxAbi,
|
|
76
|
+
contractBytecode: OutboxBytecode,
|
|
77
|
+
},
|
|
78
|
+
rollup: {
|
|
79
|
+
contractAbi: RollupAbi,
|
|
80
|
+
contractBytecode: RollupBytecode,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
return await deployL1Contracts(chain.rpcUrl, account, chain.chainInfo, debugLogger, l1Artifacts);
|
|
50
84
|
}
|
|
51
85
|
|
|
52
86
|
/**
|
|
@@ -90,23 +124,23 @@ export async function getContractAbi(fileDir: string, log: LogFn) {
|
|
|
90
124
|
|
|
91
125
|
/**
|
|
92
126
|
* Utility to select a TX sender either from user input
|
|
93
|
-
* or from the first account that is found in
|
|
94
|
-
* @param
|
|
127
|
+
* or from the first account that is found in a PXE instance.
|
|
128
|
+
* @param pxe - The PXE instance that will be checked for an account.
|
|
95
129
|
* @param _from - The user input.
|
|
96
130
|
* @returns An Aztec address. Will throw if one can't be found in either options.
|
|
97
131
|
*/
|
|
98
|
-
export async function getTxSender(
|
|
132
|
+
export async function getTxSender(pxe: PXE, _from?: string) {
|
|
99
133
|
let from: AztecAddress;
|
|
100
134
|
if (_from) {
|
|
101
135
|
try {
|
|
102
136
|
from = AztecAddress.fromString(_from);
|
|
103
137
|
} catch {
|
|
104
|
-
throw new
|
|
138
|
+
throw new InvalidArgumentError(`Invalid option 'from' passed: ${_from}`);
|
|
105
139
|
}
|
|
106
140
|
} else {
|
|
107
|
-
const accounts = await
|
|
141
|
+
const accounts = await pxe.getRegisteredAccounts();
|
|
108
142
|
if (!accounts.length) {
|
|
109
|
-
throw new Error('No accounts found in
|
|
143
|
+
throw new Error('No accounts found in PXE instance.');
|
|
110
144
|
}
|
|
111
145
|
from = accounts[0].address;
|
|
112
146
|
}
|
|
@@ -116,28 +150,161 @@ export async function getTxSender(client: AztecRPC, _from?: string) {
|
|
|
116
150
|
/**
|
|
117
151
|
* Performs necessary checks, conversions & operations to call a contract fn from the CLI.
|
|
118
152
|
* @param contractFile - Directory of the compiled contract ABI.
|
|
119
|
-
* @param
|
|
153
|
+
* @param contractAddress - Aztec Address of the contract.
|
|
120
154
|
* @param functionName - Name of the function to be called.
|
|
121
155
|
* @param _functionArgs - Arguments to call the function with.
|
|
122
156
|
* @param log - Logger instance that will output to the CLI
|
|
123
157
|
* @returns Formatted contract address, function arguments and caller's aztec address.
|
|
124
158
|
*/
|
|
125
|
-
export async function prepTx(
|
|
126
|
-
contractFile: string,
|
|
127
|
-
_contractAddress: string,
|
|
128
|
-
functionName: string,
|
|
129
|
-
_functionArgs: string[],
|
|
130
|
-
log: LogFn,
|
|
131
|
-
) {
|
|
132
|
-
let contractAddress;
|
|
133
|
-
try {
|
|
134
|
-
contractAddress = AztecAddress.fromString(_contractAddress);
|
|
135
|
-
} catch {
|
|
136
|
-
throw new Error(`Unable to parse contract address ${_contractAddress}.`);
|
|
137
|
-
}
|
|
159
|
+
export async function prepTx(contractFile: string, functionName: string, _functionArgs: string[], log: LogFn) {
|
|
138
160
|
const contractAbi = await getContractAbi(contractFile, log);
|
|
139
161
|
const functionAbi = getAbiFunction(contractAbi, functionName);
|
|
140
162
|
const functionArgs = encodeArgs(_functionArgs, functionAbi.parameters);
|
|
141
163
|
|
|
142
|
-
return {
|
|
164
|
+
return { functionArgs, contractAbi };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Removes the leading 0x from a hex string. If no leading 0x is found the string is returned unchanged.
|
|
169
|
+
* @param hex - A hex string
|
|
170
|
+
* @returns A new string with leading 0x removed
|
|
171
|
+
*/
|
|
172
|
+
export const stripLeadingHex = (hex: string) => {
|
|
173
|
+
if (hex.length > 2 && hex.startsWith('0x')) {
|
|
174
|
+
return hex.substring(2);
|
|
175
|
+
}
|
|
176
|
+
return hex;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parses a hex encoded string to an Fr integer to be used as salt
|
|
181
|
+
* @param str - Hex encoded string
|
|
182
|
+
* @returns A integer to be used as salt
|
|
183
|
+
*/
|
|
184
|
+
export function parseSaltFromHexString(str: string): Fr {
|
|
185
|
+
const hex = stripLeadingHex(str);
|
|
186
|
+
|
|
187
|
+
// ensure it's a hex string
|
|
188
|
+
if (!hex.match(/^[0-9a-f]+$/i)) {
|
|
189
|
+
throw new InvalidArgumentError('Invalid hex string');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// pad it so that we may read it as a buffer.
|
|
193
|
+
// Buffer needs _exactly_ two hex characters per byte
|
|
194
|
+
const padded = hex.length % 2 === 1 ? '0' + hex : hex;
|
|
195
|
+
|
|
196
|
+
// finally, turn it into an integer
|
|
197
|
+
return Fr.fromBuffer(Buffer.from(padded, 'hex'));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Parses an AztecAddress from a string. Throws InvalidArgumentError if the string is not a valid.
|
|
202
|
+
* @param address - A serialised Aztec address
|
|
203
|
+
* @returns An Aztec address
|
|
204
|
+
*/
|
|
205
|
+
export function parseAztecAddress(address: string): AztecAddress {
|
|
206
|
+
try {
|
|
207
|
+
return AztecAddress.fromString(address);
|
|
208
|
+
} catch {
|
|
209
|
+
throw new InvalidArgumentError(`Invalid address: ${address}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parses a TxHash from a string. Throws InvalidArgumentError if the string is not a valid.
|
|
215
|
+
* @param txHash - A transaction hash
|
|
216
|
+
* @returns A TxHash instance
|
|
217
|
+
*/
|
|
218
|
+
export function parseTxHash(txHash: string): TxHash {
|
|
219
|
+
try {
|
|
220
|
+
return TxHash.fromString(txHash);
|
|
221
|
+
} catch {
|
|
222
|
+
throw new InvalidArgumentError(`Invalid transaction hash: ${txHash}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Parses a public key from a string. Throws InvalidArgumentError if the string is not a valid.
|
|
228
|
+
* @param publicKey - A public key
|
|
229
|
+
* @returns A Point instance
|
|
230
|
+
*/
|
|
231
|
+
export function parsePublicKey(publicKey: string): Point {
|
|
232
|
+
try {
|
|
233
|
+
return Point.fromString(publicKey);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
throw new InvalidArgumentError(`Invalid public key: ${publicKey}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Parses a partial address from a string. Throws InvalidArgumentError if the string is not a valid.
|
|
241
|
+
* @param address - A partial address
|
|
242
|
+
* @returns A Fr instance
|
|
243
|
+
*/
|
|
244
|
+
export function parsePartialAddress(address: string): Fr {
|
|
245
|
+
try {
|
|
246
|
+
return Fr.fromString(address);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
throw new InvalidArgumentError(`Invalid partial address: ${address}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Parses a private key from a string. Throws InvalidArgumentError if the string is not a valid.
|
|
254
|
+
* @param privateKey - A string
|
|
255
|
+
* @returns A private key
|
|
256
|
+
*/
|
|
257
|
+
export function parsePrivateKey(privateKey: string): GrumpkinScalar {
|
|
258
|
+
try {
|
|
259
|
+
const value = GrumpkinScalar.fromString(privateKey);
|
|
260
|
+
// most likely a badly formatted key was passed
|
|
261
|
+
if (value.isZero()) {
|
|
262
|
+
throw new Error('Private key must not be zero');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return value;
|
|
266
|
+
} catch (err) {
|
|
267
|
+
throw new InvalidArgumentError(`Invalid private key: ${privateKey}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Parses a field from a string. Throws InvalidArgumentError if the string is not a valid field value.
|
|
273
|
+
* @param field - A string representing the field.
|
|
274
|
+
* @returns A field.
|
|
275
|
+
*/
|
|
276
|
+
export function parseField(field: string): Fr {
|
|
277
|
+
try {
|
|
278
|
+
const isHex = field.startsWith('0x') || field.match(new RegExp(`^[0-9a-f]{${Fr.SIZE_IN_BYTES * 2}}$`, 'i'));
|
|
279
|
+
if (isHex) {
|
|
280
|
+
return Fr.fromString(field);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (['true', 'false'].includes(field)) {
|
|
284
|
+
return new Fr(field === 'true');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const isNumber = +field || field === '0';
|
|
288
|
+
if (isNumber) {
|
|
289
|
+
return new Fr(BigInt(field));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const isBigInt = field.endsWith('n');
|
|
293
|
+
if (isBigInt) {
|
|
294
|
+
return new Fr(BigInt(field.replace(/n$/, '')));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return new Fr(BigInt(field));
|
|
298
|
+
} catch (err) {
|
|
299
|
+
throw new InvalidArgumentError(`Invalid field: ${field}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Parses an array of strings to Frs.
|
|
305
|
+
* @param fields - An array of strings representing the fields.
|
|
306
|
+
* @returns An array of Frs.
|
|
307
|
+
*/
|
|
308
|
+
export function parseFields(fields: string[]): Fr[] {
|
|
309
|
+
return fields.map(parseField);
|
|
143
310
|
}
|
package/.eslintrc.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('@aztec/foundation/eslint');
|