@aztec/cli 0.8.9 → 0.8.11

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/src/index.ts CHANGED
@@ -11,9 +11,10 @@ import {
11
11
  import { StructType, decodeFunctionSignatureWithParameterNames } from '@aztec/foundation/abi';
12
12
  import { JsonStringify } from '@aztec/foundation/json-rpc';
13
13
  import { DebugLogger, LogFn } from '@aztec/foundation/log';
14
+ import { sleep } from '@aztec/foundation/sleep';
14
15
  import { fileURLToPath } from '@aztec/foundation/url';
15
16
  import { compileContract, generateNoirInterface, generateTypescriptInterface } from '@aztec/noir-compiler/cli';
16
- import { CompleteAddress, ContractData, L2BlockL2Logs } from '@aztec/types';
17
+ import { CompleteAddress, ContractData, LogFilter } from '@aztec/types';
17
18
 
18
19
  import { createSecp256k1PeerId } from '@libp2p/peer-id-factory';
19
20
  import { Command, Option } from 'commander';
@@ -27,13 +28,18 @@ import { encodeArgs, parseStructString } from './encoding.js';
27
28
  import { unboxContract } from './unbox.js';
28
29
  import {
29
30
  deployAztecContracts,
30
- getAbiFunction,
31
- getContractAbi,
31
+ getContractArtifact,
32
32
  getExampleContractArtifacts,
33
+ getFunctionArtifact,
33
34
  getTxSender,
34
35
  parseAztecAddress,
35
36
  parseField,
36
37
  parseFields,
38
+ parseOptionalAztecAddress,
39
+ parseOptionalInteger,
40
+ parseOptionalLogId,
41
+ parseOptionalSelector,
42
+ parseOptionalTxHash,
37
43
  parsePartialAddress,
38
44
  parsePrivateKey,
39
45
  parsePublicKey,
@@ -150,18 +156,29 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
150
156
  createPrivateKeyOption('Private key for note encryption and transaction signing. Uses random by default.', false),
151
157
  )
152
158
  .addOption(pxeOption)
153
- .action(async options => {
154
- const client = await createCompatibleClient(options.rpcUrl, debugLogger);
155
- const privateKey = options.privateKey ?? GrumpkinScalar.random();
159
+ // `options.wait` is default true. Passing `--no-wait` will set it to false.
160
+ // https://github.com/tj/commander.js#other-option-types-negatable-boolean-and-booleanvalue
161
+ .option('--no-wait', 'Skip waiting for the contract to be deployed. Print the hash of deployment transaction')
162
+ .action(async ({ rpcUrl, privateKey, wait }) => {
163
+ const client = await createCompatibleClient(rpcUrl, debugLogger);
164
+ const actualPrivateKey = privateKey ?? GrumpkinScalar.random();
156
165
 
157
- const account = getSchnorrAccount(client, privateKey, privateKey, accountCreationSalt);
158
- const wallet = await account.waitDeploy();
159
- const { address, publicKey, partialAddress } = wallet.getCompleteAddress();
166
+ const account = getSchnorrAccount(client, actualPrivateKey, actualPrivateKey, accountCreationSalt);
167
+ const { address, publicKey, partialAddress } = await account.getCompleteAddress();
168
+ const tx = await account.deploy();
169
+ const txHash = await tx.getTxHash();
170
+ debugLogger(`Account contract tx sent with hash ${txHash}`);
171
+ if (wait) {
172
+ log(`\nWaiting for account contract deployment...`);
173
+ await tx.wait();
174
+ } else {
175
+ log(`\nAccount deployment transaction hash: ${txHash}\n`);
176
+ }
160
177
 
161
- log(`\nCreated new account:\n`);
178
+ log(`\nNew account:\n`);
162
179
  log(`Address: ${address.toString()}`);
163
180
  log(`Public key: ${publicKey.toString()}`);
164
- if (!options.privateKey) log(`Private key: ${privateKey.toString(true)}`);
181
+ if (!privateKey) log(`Private key: ${actualPrivateKey.toString(true)}`);
165
182
  log(`Partial address: ${partialAddress.toString()}`);
166
183
  });
167
184
 
@@ -169,8 +186,8 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
169
186
  .command('deploy')
170
187
  .description('Deploys a compiled Aztec.nr contract to Aztec.')
171
188
  .argument(
172
- '<abi>',
173
- "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts",
189
+ '<artifact>',
190
+ "A compiled Aztec.nr contract's artifact in JSON format or name of a contract artifact exported by @aztec/noir-contracts",
174
191
  )
175
192
  .option('-a, --args <constructorArgs...>', 'Contract constructor arguments', [])
176
193
  .addOption(pxeOption)
@@ -187,18 +204,18 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
187
204
  // `options.wait` is default true. Passing `--no-wait` will set it to false.
188
205
  // https://github.com/tj/commander.js#other-option-types-negatable-boolean-and-booleanvalue
189
206
  .option('--no-wait', 'Skip waiting for the contract to be deployed. Print the hash of deployment transaction')
190
- .action(async (abiPath, { rpcUrl, publicKey, args: rawArgs, salt, wait }) => {
191
- const contractAbi = await getContractAbi(abiPath, log);
192
- const constructorAbi = contractAbi.functions.find(({ name }) => name === 'constructor');
207
+ .action(async (artifactPath, { rpcUrl, publicKey, args: rawArgs, salt, wait }) => {
208
+ const contractArtifact = await getContractArtifact(artifactPath, log);
209
+ const constructorArtifact = contractArtifact.functions.find(({ name }) => name === 'constructor');
193
210
 
194
211
  const client = await createCompatibleClient(rpcUrl, debugLogger);
195
- const deployer = new ContractDeployer(contractAbi, client, publicKey);
212
+ const deployer = new ContractDeployer(contractArtifact, client, publicKey);
196
213
 
197
- const constructor = getAbiFunction(contractAbi, 'constructor');
214
+ const constructor = getFunctionArtifact(contractArtifact, 'constructor');
198
215
  if (!constructor) throw new Error(`Constructor not found in contract ABI`);
199
216
 
200
217
  debugLogger(`Input arguments: ${rawArgs.map((x: any) => `"${x}"`).join(', ')}`);
201
- const args = encodeArgs(rawArgs, constructorAbi!.parameters);
218
+ const args = encodeArgs(rawArgs, constructorArtifact!.parameters);
202
219
  debugLogger(`Encoded arguments: ${args.join(', ')}`);
203
220
 
204
221
  const tx = deployer.deploy(...args).send({ contractAddressSalt: salt });
@@ -208,7 +225,8 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
208
225
  const deployed = await tx.wait();
209
226
  log(`\nContract deployed at ${deployed.contractAddress!.toString()}\n`);
210
227
  } else {
211
- log(`\nDeployment transaction hash: ${txHash}\n`);
228
+ log(`\nContract Address: ${tx.completeContractAddress?.address.toString() ?? 'N/A'}`);
229
+ log(`Deployment transaction hash: ${txHash}\n`);
212
230
  }
213
231
  });
214
232
 
@@ -277,22 +295,58 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
277
295
 
278
296
  program
279
297
  .command('get-logs')
280
- .description('Gets all the unencrypted logs from L2 blocks in the range specified.')
281
- .option('-f, --from <blockNum>', 'Initial block number for getting logs (defaults to 1).')
282
- .option('-l, --limit <blockCount>', 'How many blocks to fetch (defaults to 100).')
298
+ .description('Gets all the unencrypted logs from an intersection of all the filter params.')
299
+ .option('-tx, --tx-hash <txHash>', 'A transaction hash to get the receipt for.', parseOptionalTxHash)
300
+ .option(
301
+ '-fb, --from-block <blockNum>',
302
+ 'Initial block number for getting logs (defaults to 1).',
303
+ parseOptionalInteger,
304
+ )
305
+ .option('-tb, --to-block <blockNum>', 'Up to which block to fetch logs (defaults to latest).', parseOptionalInteger)
306
+ .option('-al --after-log <logId>', 'ID of a log after which to fetch the logs.', parseOptionalLogId)
307
+ .option('-ca, --contract-address <address>', 'Contract address to filter logs by.', parseOptionalAztecAddress)
308
+ .option('-s, --selector <hex string>', 'Event selector to filter logs by.', parseOptionalSelector)
283
309
  .addOption(pxeOption)
284
- .action(async options => {
285
- const { from, limit } = options;
286
- const fromBlock = from ? parseInt(from) : 1;
287
- const limitCount = limit ? parseInt(limit) : 100;
310
+ .option('--follow', 'If set, will keep polling for new logs until interrupted.')
311
+ .action(async ({ txHash, fromBlock, toBlock, afterLog, contractAddress, selector, rpcUrl, follow }) => {
312
+ const pxe = await createCompatibleClient(rpcUrl, debugLogger);
288
313
 
289
- const client = await createCompatibleClient(options.rpcUrl, debugLogger);
290
- const logs = await client.getUnencryptedLogs(fromBlock, limitCount);
291
- if (!logs.length) {
292
- log(`No logs found in blocks ${fromBlock} to ${fromBlock + limitCount}`);
314
+ if (follow) {
315
+ if (txHash) throw Error('Cannot use --follow with --tx-hash');
316
+ if (toBlock) throw Error('Cannot use --follow with --to-block');
317
+ }
318
+
319
+ const filter: LogFilter = { txHash, fromBlock, toBlock, afterLog, contractAddress, selector };
320
+
321
+ const fetchLogs = async () => {
322
+ const response = await pxe.getUnencryptedLogs(filter);
323
+ const logs = response.logs;
324
+
325
+ if (!logs.length) {
326
+ const filterOptions = Object.entries(filter)
327
+ .filter(([, value]) => value !== undefined)
328
+ .map(([key, value]) => `${key}: ${value}`)
329
+ .join(', ');
330
+ if (!follow) log(`No logs found for filter: {${filterOptions}}`);
331
+ } else {
332
+ if (!follow && !filter.afterLog) log('Logs found: \n');
333
+ logs.forEach(unencryptedLog => log(unencryptedLog.toHumanReadable()));
334
+ // Set the continuation parameter for the following requests
335
+ filter.afterLog = logs[logs.length - 1].id;
336
+ }
337
+ return response.maxLogsHit;
338
+ };
339
+
340
+ if (follow) {
341
+ log('Fetching logs...');
342
+ while (true) {
343
+ const maxLogsHit = await fetchLogs();
344
+ if (!maxLogsHit) await sleep(1000);
345
+ }
293
346
  } else {
294
- log('Logs found: \n');
295
- L2BlockL2Logs.unrollLogs(logs).forEach(fnLog => log(`${fnLog.toString('ascii')}\n`));
347
+ while (await fetchLogs()) {
348
+ // Keep fetching logs until we reach the end.
349
+ }
296
350
  }
297
351
  });
298
352
 
@@ -385,7 +439,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
385
439
  .argument('<functionName>', 'Name of function to execute')
386
440
  .option('-a, --args [functionArgs...]', 'Function arguments', [])
387
441
  .requiredOption(
388
- '-c, --contract-abi <fileLocation>',
442
+ '-c, --contract-artifact <fileLocation>',
389
443
  "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts",
390
444
  )
391
445
  .requiredOption('-ca, --contract-address <address>', 'Aztec address of the contract.', parseAztecAddress)
@@ -393,14 +447,19 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
393
447
  .addOption(pxeOption)
394
448
  .option('--no-wait', 'Print transaction hash without waiting for it to be mined')
395
449
  .action(async (functionName, options) => {
396
- const { functionArgs, contractAbi } = await prepTx(options.contractAbi, functionName, options.args, log);
450
+ const { functionArgs, contractArtifact } = await prepTx(
451
+ options.contractArtifact,
452
+ functionName,
453
+ options.args,
454
+ log,
455
+ );
397
456
  const { contractAddress, privateKey } = options;
398
457
 
399
458
  const client = await createCompatibleClient(options.rpcUrl, debugLogger);
400
459
  const wallet = await getSchnorrAccount(client, privateKey, privateKey, accountCreationSalt).getWallet();
401
- const contract = await Contract.at(contractAddress, contractAbi, wallet);
460
+ const contract = await Contract.at(contractAddress, contractArtifact, wallet);
402
461
  const tx = contract.methods[functionName](...functionArgs).send();
403
- log(`Transaction hash: ${(await tx.getTxHash()).toString()}`);
462
+ log(`\nTransaction hash: ${(await tx.getTxHash()).toString()}`);
404
463
  if (options.wait) {
405
464
  await tx.wait();
406
465
 
@@ -411,7 +470,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
411
470
  log(`Block number: ${receipt.blockNumber}`);
412
471
  log(`Block hash: ${receipt.blockHash?.toString('hex')}`);
413
472
  } else {
414
- log('\nTransaction pending. Check status with get-tx-receipt');
473
+ log('Transaction pending. Check status with get-tx-receipt');
415
474
  }
416
475
  });
417
476
 
@@ -423,19 +482,24 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
423
482
  .argument('<functionName>', 'Name of function to call')
424
483
  .option('-a, --args [functionArgs...]', 'Function arguments', [])
425
484
  .requiredOption(
426
- '-c, --contract-abi <fileLocation>',
485
+ '-c, --contract-artifact <fileLocation>',
427
486
  "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts",
428
487
  )
429
488
  .requiredOption('-ca, --contract-address <address>', 'Aztec address of the contract.', parseAztecAddress)
430
489
  .option('-f, --from <string>', 'Aztec address of the caller. If empty, will use the first account from RPC.')
431
490
  .addOption(pxeOption)
432
491
  .action(async (functionName, options) => {
433
- const { functionArgs, contractAbi } = await prepTx(options.contractAbi, functionName, options.args, log);
492
+ const { functionArgs, contractArtifact } = await prepTx(
493
+ options.contractArtifact,
494
+ functionName,
495
+ options.args,
496
+ log,
497
+ );
434
498
 
435
- const fnAbi = getAbiFunction(contractAbi, functionName);
436
- if (fnAbi.parameters.length !== options.args.length) {
499
+ const fnArtifact = getFunctionArtifact(contractArtifact, functionName);
500
+ if (fnArtifact.parameters.length !== options.args.length) {
437
501
  throw Error(
438
- `Invalid number of args passed. Expected ${fnAbi.parameters.length}; Received: ${options.args.length}`,
502
+ `Invalid number of args passed. Expected ${fnArtifact.parameters.length}; Received: ${options.args.length}`,
439
503
  );
440
504
  }
441
505
  const client = await createCompatibleClient(options.rpcUrl, debugLogger);
@@ -465,13 +529,13 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
465
529
  .description("Helper for parsing an encoded string into a contract's parameter struct.")
466
530
  .argument('<encodedString>', 'The encoded hex string')
467
531
  .requiredOption(
468
- '-c, --contract-abi <fileLocation>',
532
+ '-c, --contract-artifact <fileLocation>',
469
533
  "A compiled Aztec.nr contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts",
470
534
  )
471
535
  .requiredOption('-p, --parameter <parameterName>', 'The name of the struct parameter to decode into')
472
536
  .action(async (encodedString, options) => {
473
- const contractAbi = await getContractAbi(options.contractAbi, log);
474
- const parameterAbitype = contractAbi.functions
537
+ const contractArtifact = await getContractArtifact(options.contractArtifact, log);
538
+ const parameterAbitype = contractArtifact.functions
475
539
  .map(({ parameters }) => parameters)
476
540
  .flat()
477
541
  .find(({ name, type }) => name === options.parameter && type.kind === 'struct');
@@ -533,16 +597,16 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
533
597
  .command('inspect-contract')
534
598
  .description('Shows list of external callable functions for a contract')
535
599
  .argument(
536
- '<contractAbiFile>',
537
- `A compiled Noir contract's ABI in JSON format or name of a contract ABI exported by @aztec/noir-contracts`,
600
+ '<contractArtifactFile>',
601
+ `A compiled Noir contract's artifact in JSON format or name of a contract artifact exported by @aztec/noir-contracts`,
538
602
  )
539
- .action(async (contractAbiFile: string) => {
540
- const contractAbi = await getContractAbi(contractAbiFile, debugLogger);
541
- const contractFns = contractAbi.functions.filter(
603
+ .action(async (contractArtifactFile: string) => {
604
+ const contractArtifact = await getContractArtifact(contractArtifactFile, debugLogger);
605
+ const contractFns = contractArtifact.functions.filter(
542
606
  f => !f.isInternal && f.name !== 'compute_note_hash_and_nullifier',
543
607
  );
544
608
  if (contractFns.length === 0) {
545
- log(`No external functions found for contract ${contractAbi.name}`);
609
+ log(`No external functions found for contract ${contractArtifact.name}`);
546
610
  }
547
611
  for (const fn of contractFns) {
548
612
  const signature = decodeFunctionSignatureWithParameterNames(fn.name, fn.parameters);
package/src/test/mocks.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { ABIParameterVisibility, ContractAbi, FunctionType } from '@aztec/foundation/abi';
1
+ import { ABIParameterVisibility, ContractArtifact, FunctionType } from '@aztec/foundation/abi';
2
2
 
3
- export const mockContractAbi: ContractAbi = {
3
+ export const mockContractArtifact: ContractArtifact = {
4
4
  name: 'MockContract',
5
5
  functions: [
6
6
  {
@@ -61,4 +61,5 @@ export const mockContractAbi: ContractAbi = {
61
61
  bytecode: 'mockBytecode',
62
62
  },
63
63
  ],
64
+ events: [],
64
65
  };
package/src/utils.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { AztecAddress, Fr, GrumpkinScalar, PXE, Point, TxHash } from '@aztec/aztec.js';
1
+ import { AztecAddress, Fr, FunctionSelector, GrumpkinScalar, PXE, Point, TxHash } from '@aztec/aztec.js';
2
2
  import { L1ContractArtifactsForDeployment, createEthereumChain, deployL1Contracts } from '@aztec/ethereum';
3
- import { ContractAbi } from '@aztec/foundation/abi';
3
+ import { ContractArtifact } from '@aztec/foundation/abi';
4
4
  import { DebugLogger, LogFn } from '@aztec/foundation/log';
5
5
  import {
6
6
  ContractDeploymentEmitterAbi,
@@ -14,6 +14,7 @@ import {
14
14
  RollupAbi,
15
15
  RollupBytecode,
16
16
  } from '@aztec/l1-artifacts';
17
+ import { LogId } from '@aztec/types';
17
18
 
18
19
  import { InvalidArgumentError } from 'commander';
19
20
  import fs from 'fs';
@@ -25,17 +26,17 @@ import { encodeArgs } from './encoding.js';
25
26
  * Helper type to dynamically import contracts.
26
27
  */
27
28
  interface ArtifactsType {
28
- [key: string]: ContractAbi;
29
+ [key: string]: ContractArtifact;
29
30
  }
30
31
 
31
32
  /**
32
33
  * Helper to get an ABI function or throw error if it doesn't exist.
33
- * @param abi - Contract's ABI in JSON format.
34
+ * @param artifact - Contract's build artifact in JSON format.
34
35
  * @param fnName - Function name to be found.
35
36
  * @returns The function's ABI.
36
37
  */
37
- export function getAbiFunction(abi: ContractAbi, fnName: string) {
38
- const fn = abi.functions.find(({ name }) => name === fnName);
38
+ export function getFunctionArtifact(artifact: ContractArtifact, fnName: string) {
39
+ const fn = artifact.functions.find(({ name }) => name === fnName);
39
40
  if (!fn) {
40
41
  throw Error(`Function ${fnName} not found in contract ABI.`);
41
42
  }
@@ -95,14 +96,14 @@ export async function getExampleContractArtifacts() {
95
96
  /**
96
97
  * Reads a file and converts it to an Aztec Contract ABI.
97
98
  * @param fileDir - The directory of the compiled contract ABI.
98
- * @returns The parsed ContractABI.
99
+ * @returns The parsed contract artifact.
99
100
  */
100
- export async function getContractAbi(fileDir: string, log: LogFn) {
101
+ export async function getContractArtifact(fileDir: string, log: LogFn) {
101
102
  // first check if it's a noir-contracts example
102
103
  let contents: string;
103
104
  const artifacts = await getExampleContractArtifacts();
104
105
  if (artifacts[fileDir]) {
105
- return artifacts[fileDir] as ContractAbi;
106
+ return artifacts[fileDir] as ContractArtifact;
106
107
  }
107
108
 
108
109
  try {
@@ -112,14 +113,14 @@ export async function getContractAbi(fileDir: string, log: LogFn) {
112
113
  }
113
114
 
114
115
  // if not found, try reading as path directly
115
- let contractAbi: ContractAbi;
116
+ let contractArtifact: ContractArtifact;
116
117
  try {
117
- contractAbi = JSON.parse(contents) as ContractAbi;
118
+ contractArtifact = JSON.parse(contents) as ContractArtifact;
118
119
  } catch (err) {
119
120
  log('Invalid file used. Please try again.');
120
121
  throw err;
121
122
  }
122
- return contractAbi;
123
+ return contractArtifact;
123
124
  }
124
125
 
125
126
  /**
@@ -150,18 +151,17 @@ export async function getTxSender(pxe: PXE, _from?: string) {
150
151
  /**
151
152
  * Performs necessary checks, conversions & operations to call a contract fn from the CLI.
152
153
  * @param contractFile - Directory of the compiled contract ABI.
153
- * @param contractAddress - Aztec Address of the contract.
154
154
  * @param functionName - Name of the function to be called.
155
155
  * @param _functionArgs - Arguments to call the function with.
156
156
  * @param log - Logger instance that will output to the CLI
157
157
  * @returns Formatted contract address, function arguments and caller's aztec address.
158
158
  */
159
159
  export async function prepTx(contractFile: string, functionName: string, _functionArgs: string[], log: LogFn) {
160
- const contractAbi = await getContractAbi(contractFile, log);
161
- const functionAbi = getAbiFunction(contractAbi, functionName);
162
- const functionArgs = encodeArgs(_functionArgs, functionAbi.parameters);
160
+ const contractArtifact = await getContractArtifact(contractFile, log);
161
+ const functionArtifact = getFunctionArtifact(contractArtifact, functionName);
162
+ const functionArgs = encodeArgs(_functionArgs, functionArtifact.parameters);
163
163
 
164
- return { functionArgs, contractAbi };
164
+ return { functionArgs, contractArtifact };
165
165
  }
166
166
 
167
167
  /**
@@ -198,9 +198,10 @@ export function parseSaltFromHexString(str: string): Fr {
198
198
  }
199
199
 
200
200
  /**
201
- * Parses an AztecAddress from a string. Throws InvalidArgumentError if the string is not a valid.
202
- * @param address - A serialised Aztec address
201
+ * Parses an AztecAddress from a string.
202
+ * @param address - A serialized Aztec address
203
203
  * @returns An Aztec address
204
+ * @throws InvalidArgumentError if the input string is not valid.
204
205
  */
205
206
  export function parseAztecAddress(address: string): AztecAddress {
206
207
  try {
@@ -211,9 +212,71 @@ export function parseAztecAddress(address: string): AztecAddress {
211
212
  }
212
213
 
213
214
  /**
214
- * Parses a TxHash from a string. Throws InvalidArgumentError if the string is not a valid.
215
+ * Parses an AztecAddress from a string.
216
+ * @param address - A serialized Aztec address
217
+ * @returns An Aztec address
218
+ * @throws InvalidArgumentError if the input string is not valid.
219
+ */
220
+ export function parseOptionalAztecAddress(address: string): AztecAddress | undefined {
221
+ if (!address) {
222
+ return undefined;
223
+ }
224
+ return parseAztecAddress(address);
225
+ }
226
+
227
+ /**
228
+ * Parses an optional log ID string into a LogId object.
229
+ *
230
+ * @param logId - The log ID string to parse.
231
+ * @returns The parsed LogId object, or undefined if the log ID is missing or empty.
232
+ */
233
+ export function parseOptionalLogId(logId: string): LogId | undefined {
234
+ if (!logId) {
235
+ return undefined;
236
+ }
237
+ return LogId.fromString(logId);
238
+ }
239
+
240
+ /**
241
+ * Parses a selector from a string.
242
+ * @param selector - A serialized selector.
243
+ * @returns A selector.
244
+ * @throws InvalidArgumentError if the input string is not valid.
245
+ */
246
+ export function parseOptionalSelector(selector: string): FunctionSelector | undefined {
247
+ if (!selector) {
248
+ return undefined;
249
+ }
250
+ try {
251
+ return FunctionSelector.fromString(selector);
252
+ } catch {
253
+ throw new InvalidArgumentError(`Invalid selector: ${selector}`);
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Parses a string into an integer or returns undefined if the input is falsy.
259
+ *
260
+ * @param value - The string to parse into an integer.
261
+ * @returns The parsed integer, or undefined if the input string is falsy.
262
+ * @throws If the input is not a valid integer.
263
+ */
264
+ export function parseOptionalInteger(value: string): number | undefined {
265
+ if (!value) {
266
+ return undefined;
267
+ }
268
+ const parsed = Number(value);
269
+ if (!Number.isInteger(parsed)) {
270
+ throw new InvalidArgumentError('Invalid integer.');
271
+ }
272
+ return parsed;
273
+ }
274
+
275
+ /**
276
+ * Parses a TxHash from a string.
215
277
  * @param txHash - A transaction hash
216
278
  * @returns A TxHash instance
279
+ * @throws InvalidArgumentError if the input string is not valid.
217
280
  */
218
281
  export function parseTxHash(txHash: string): TxHash {
219
282
  try {
@@ -224,9 +287,24 @@ export function parseTxHash(txHash: string): TxHash {
224
287
  }
225
288
 
226
289
  /**
227
- * Parses a public key from a string. Throws InvalidArgumentError if the string is not a valid.
290
+ * Parses an optional TxHash from a string.
291
+ * Calls parseTxHash internally.
292
+ * @param txHash - A transaction hash
293
+ * @returns A TxHash instance, or undefined if the input string is falsy.
294
+ * @throws InvalidArgumentError if the input string is not valid.
295
+ */
296
+ export function parseOptionalTxHash(txHash: string): TxHash | undefined {
297
+ if (!txHash) {
298
+ return undefined;
299
+ }
300
+ return parseTxHash(txHash);
301
+ }
302
+
303
+ /**
304
+ * Parses a public key from a string.
228
305
  * @param publicKey - A public key
229
306
  * @returns A Point instance
307
+ * @throws InvalidArgumentError if the input string is not valid.
230
308
  */
231
309
  export function parsePublicKey(publicKey: string): Point {
232
310
  try {
@@ -237,9 +315,10 @@ export function parsePublicKey(publicKey: string): Point {
237
315
  }
238
316
 
239
317
  /**
240
- * Parses a partial address from a string. Throws InvalidArgumentError if the string is not a valid.
318
+ * Parses a partial address from a string.
241
319
  * @param address - A partial address
242
320
  * @returns A Fr instance
321
+ * @throws InvalidArgumentError if the input string is not valid.
243
322
  */
244
323
  export function parsePartialAddress(address: string): Fr {
245
324
  try {
@@ -250,9 +329,10 @@ export function parsePartialAddress(address: string): Fr {
250
329
  }
251
330
 
252
331
  /**
253
- * Parses a private key from a string. Throws InvalidArgumentError if the string is not a valid.
332
+ * Parses a private key from a string.
254
333
  * @param privateKey - A string
255
334
  * @returns A private key
335
+ * @throws InvalidArgumentError if the input string is not valid.
256
336
  */
257
337
  export function parsePrivateKey(privateKey: string): GrumpkinScalar {
258
338
  try {
@@ -269,9 +349,10 @@ export function parsePrivateKey(privateKey: string): GrumpkinScalar {
269
349
  }
270
350
 
271
351
  /**
272
- * Parses a field from a string. Throws InvalidArgumentError if the string is not a valid field value.
352
+ * Parses a field from a string.
273
353
  * @param field - A string representing the field.
274
354
  * @returns A field.
355
+ * @throws InvalidArgumentError if the input string is not valid.
275
356
  */
276
357
  export function parseField(field: string): Fr {
277
358
  try {