@aztec/cli 3.0.0-nightly.20251026 → 3.0.0-nightly.20251031
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/dest/cmds/validator_keys/add.d.ts +5 -0
- package/dest/cmds/validator_keys/add.d.ts.map +1 -0
- package/dest/cmds/validator_keys/add.js +70 -0
- package/dest/cmds/validator_keys/generate_bls_keypair.d.ts +12 -0
- package/dest/cmds/validator_keys/generate_bls_keypair.d.ts.map +1 -0
- package/dest/cmds/validator_keys/generate_bls_keypair.js +26 -0
- package/dest/cmds/validator_keys/index.d.ts +4 -0
- package/dest/cmds/validator_keys/index.d.ts.map +1 -0
- package/dest/cmds/validator_keys/index.js +20 -0
- package/dest/cmds/validator_keys/new.d.ts +26 -0
- package/dest/cmds/validator_keys/new.d.ts.map +1 -0
- package/dest/cmds/validator_keys/new.js +60 -0
- package/dest/cmds/validator_keys/shared.d.ts +68 -0
- package/dest/cmds/validator_keys/shared.d.ts.map +1 -0
- package/dest/cmds/validator_keys/shared.js +271 -0
- package/dest/config/chain_l2_config.d.ts +2 -0
- package/dest/config/chain_l2_config.d.ts.map +1 -1
- package/dest/config/chain_l2_config.js +9 -1
- package/dest/utils/commands.d.ts +10 -1
- package/dest/utils/commands.d.ts.map +1 -1
- package/dest/utils/commands.js +30 -3
- package/package.json +28 -24
- package/src/cmds/validator_keys/add.ts +113 -0
- package/src/cmds/validator_keys/generate_bls_keypair.ts +33 -0
- package/src/cmds/validator_keys/index.ts +96 -0
- package/src/cmds/validator_keys/new.ts +120 -0
- package/src/cmds/validator_keys/shared.ts +321 -0
- package/src/config/chain_l2_config.ts +13 -1
- package/src/utils/commands.ts +41 -3
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { prettyPrintJSON } from '@aztec/cli/utils';
|
|
2
|
+
import { computeBn254G1PublicKeyCompressed, deriveBlsPrivateKey } from '@aztec/foundation/crypto';
|
|
3
|
+
import { createBn254Keystore } from '@aztec/foundation/crypto/bls/bn254_keystore';
|
|
4
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
|
+
import type { LogFn } from '@aztec/foundation/log';
|
|
6
|
+
import type { EthAccount, EthPrivateKey, ValidatorKeyStore } from '@aztec/node-keystore/types';
|
|
7
|
+
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
8
|
+
|
|
9
|
+
import { Wallet } from '@ethersproject/wallet';
|
|
10
|
+
import { constants as fsConstants, mkdirSync } from 'fs';
|
|
11
|
+
import { access, writeFile } from 'fs/promises';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { dirname, isAbsolute, join } from 'path';
|
|
14
|
+
import { mnemonicToAccount } from 'viem/accounts';
|
|
15
|
+
|
|
16
|
+
export type ValidatorSummary = { attesterEth?: string; attesterBls?: string; publisherEth?: string[] };
|
|
17
|
+
|
|
18
|
+
export type BuildValidatorsInput = {
|
|
19
|
+
validatorCount: number;
|
|
20
|
+
publisherCount?: number;
|
|
21
|
+
accountIndex: number;
|
|
22
|
+
baseAddressIndex: number;
|
|
23
|
+
mnemonic: string;
|
|
24
|
+
ikm?: string;
|
|
25
|
+
blsPath?: string;
|
|
26
|
+
blsOnly?: boolean;
|
|
27
|
+
feeRecipient: AztecAddress;
|
|
28
|
+
coinbase?: EthAddress;
|
|
29
|
+
remoteSigner?: string;
|
|
30
|
+
fundingAccount?: EthAddress;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function withValidatorIndex(path: string, index: number) {
|
|
34
|
+
const parts = path.split('/');
|
|
35
|
+
if (parts.length >= 4 && parts[0] === 'm' && parts[1] === '12381' && parts[2] === '3600') {
|
|
36
|
+
parts[3] = String(index);
|
|
37
|
+
return parts.join('/');
|
|
38
|
+
}
|
|
39
|
+
return path;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Compute a compressed BN254 G1 public key from a private key.
|
|
44
|
+
* @param privateKeyHex - Private key as 0x-prefixed hex string
|
|
45
|
+
* @returns Compressed G1 point (32 bytes with sign bit in MSB)
|
|
46
|
+
*/
|
|
47
|
+
export async function computeBlsPublicKeyCompressed(privateKeyHex: string): Promise<string> {
|
|
48
|
+
return await computeBn254G1PublicKeyCompressed(privateKeyHex);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function deriveEthAttester(
|
|
52
|
+
mnemonic: string,
|
|
53
|
+
baseAccountIndex: number,
|
|
54
|
+
addressIndex: number,
|
|
55
|
+
remoteSigner?: string,
|
|
56
|
+
): EthAccount | EthPrivateKey {
|
|
57
|
+
const acct = mnemonicToAccount(mnemonic, { accountIndex: baseAccountIndex, addressIndex });
|
|
58
|
+
return remoteSigner
|
|
59
|
+
? ({ address: acct.address as unknown as EthAddress, remoteSignerUrl: remoteSigner } as EthAccount)
|
|
60
|
+
: (('0x' + Buffer.from(acct.getHdKey().privateKey!).toString('hex')) as EthPrivateKey);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function buildValidatorEntries(input: BuildValidatorsInput) {
|
|
64
|
+
const {
|
|
65
|
+
validatorCount,
|
|
66
|
+
publisherCount = 0,
|
|
67
|
+
accountIndex,
|
|
68
|
+
baseAddressIndex,
|
|
69
|
+
mnemonic,
|
|
70
|
+
ikm,
|
|
71
|
+
blsPath,
|
|
72
|
+
blsOnly,
|
|
73
|
+
feeRecipient,
|
|
74
|
+
coinbase,
|
|
75
|
+
remoteSigner,
|
|
76
|
+
fundingAccount,
|
|
77
|
+
} = input;
|
|
78
|
+
|
|
79
|
+
const defaultBlsPath = 'm/12381/3600/0/0/0';
|
|
80
|
+
const summaries: ValidatorSummary[] = [];
|
|
81
|
+
|
|
82
|
+
const validators = await Promise.all(
|
|
83
|
+
Array.from({ length: validatorCount }, async (_unused, i) => {
|
|
84
|
+
const addressIndex = baseAddressIndex + i;
|
|
85
|
+
const basePath = blsPath ?? defaultBlsPath;
|
|
86
|
+
const perValidatorPath = withValidatorIndex(basePath, addressIndex);
|
|
87
|
+
|
|
88
|
+
const blsPrivKey = blsOnly || ikm || mnemonic ? deriveBlsPrivateKey(mnemonic, ikm, perValidatorPath) : undefined;
|
|
89
|
+
const blsPubCompressed = blsPrivKey ? await computeBlsPublicKeyCompressed(blsPrivKey) : undefined;
|
|
90
|
+
|
|
91
|
+
if (blsOnly) {
|
|
92
|
+
const attester = { bls: blsPrivKey! };
|
|
93
|
+
summaries.push({ attesterBls: blsPubCompressed });
|
|
94
|
+
return { attester, feeRecipient } as ValidatorKeyStore;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ethAttester = deriveEthAttester(mnemonic, accountIndex, addressIndex, remoteSigner);
|
|
98
|
+
const attester = blsPrivKey ? { eth: ethAttester, bls: blsPrivKey } : ethAttester;
|
|
99
|
+
|
|
100
|
+
let publisherField: EthAccount | EthPrivateKey | (EthAccount | EthPrivateKey)[] | undefined;
|
|
101
|
+
const publisherAddresses: string[] = [];
|
|
102
|
+
if (publisherCount > 0) {
|
|
103
|
+
const publishersBaseIndex = baseAddressIndex + validatorCount + i * publisherCount;
|
|
104
|
+
const publisherAccounts = Array.from({ length: publisherCount }, (_unused2, j) => {
|
|
105
|
+
const publisherAddressIndex = publishersBaseIndex + j;
|
|
106
|
+
const pubAcct = mnemonicToAccount(mnemonic, {
|
|
107
|
+
accountIndex,
|
|
108
|
+
addressIndex: publisherAddressIndex,
|
|
109
|
+
});
|
|
110
|
+
publisherAddresses.push(pubAcct.address as unknown as string);
|
|
111
|
+
return remoteSigner
|
|
112
|
+
? ({ address: pubAcct.address as unknown as EthAddress, remoteSignerUrl: remoteSigner } as EthAccount)
|
|
113
|
+
: (('0x' + Buffer.from(pubAcct.getHdKey().privateKey!).toString('hex')) as EthPrivateKey);
|
|
114
|
+
});
|
|
115
|
+
publisherField = publisherCount === 1 ? publisherAccounts[0] : publisherAccounts;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const acct = mnemonicToAccount(mnemonic, {
|
|
119
|
+
accountIndex,
|
|
120
|
+
addressIndex,
|
|
121
|
+
});
|
|
122
|
+
const attesterEthAddress = acct.address as unknown as string;
|
|
123
|
+
summaries.push({
|
|
124
|
+
attesterEth: attesterEthAddress,
|
|
125
|
+
attesterBls: blsPubCompressed,
|
|
126
|
+
publisherEth: publisherAddresses.length > 0 ? publisherAddresses : undefined,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
attester,
|
|
131
|
+
...(publisherField !== undefined ? { publisher: publisherField } : {}),
|
|
132
|
+
feeRecipient,
|
|
133
|
+
coinbase,
|
|
134
|
+
fundingAccount,
|
|
135
|
+
} as ValidatorKeyStore;
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return { validators, summaries };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function resolveKeystoreOutputPath(dataDir?: string, file?: string) {
|
|
143
|
+
const defaultDataDir = join(homedir(), '.aztec', 'keystore');
|
|
144
|
+
const resolvedDir = dataDir && dataDir.length > 0 ? dataDir : defaultDataDir;
|
|
145
|
+
let outputPath: string;
|
|
146
|
+
if (file && file.length > 0) {
|
|
147
|
+
outputPath = isAbsolute(file) ? file : join(resolvedDir, file);
|
|
148
|
+
} else {
|
|
149
|
+
let index = 1;
|
|
150
|
+
while (true) {
|
|
151
|
+
const candidate = join(resolvedDir, `key${index}.json`);
|
|
152
|
+
try {
|
|
153
|
+
await access(candidate, fsConstants.F_OK);
|
|
154
|
+
index += 1;
|
|
155
|
+
} catch {
|
|
156
|
+
outputPath = candidate;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { resolvedDir, outputPath: outputPath! };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function writeKeystoreFile(path: string, keystore: unknown) {
|
|
165
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
166
|
+
await writeFile(path, JSON.stringify(keystore, null, 2), { encoding: 'utf-8' });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function logValidatorSummaries(log: LogFn, summaries: ValidatorSummary[]) {
|
|
170
|
+
const lines: string[] = [];
|
|
171
|
+
for (let i = 0; i < summaries.length; i++) {
|
|
172
|
+
const v = summaries[i];
|
|
173
|
+
lines.push(`acc${i + 1}:`);
|
|
174
|
+
lines.push(` attester:`);
|
|
175
|
+
if (v.attesterEth) {
|
|
176
|
+
lines.push(` eth: ${v.attesterEth}`);
|
|
177
|
+
}
|
|
178
|
+
if (v.attesterBls) {
|
|
179
|
+
lines.push(` bls: ${v.attesterBls}`);
|
|
180
|
+
}
|
|
181
|
+
if (v.publisherEth && v.publisherEth.length > 0) {
|
|
182
|
+
lines.push(` publisher:`);
|
|
183
|
+
for (const addr of v.publisherEth) {
|
|
184
|
+
lines.push(` - ${addr}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (lines.length > 0) {
|
|
189
|
+
log(lines.join('\n'));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function maybePrintJson(log: LogFn, jsonFlag: boolean | undefined, obj: unknown) {
|
|
194
|
+
if (jsonFlag) {
|
|
195
|
+
log(prettyPrintJSON(obj as Record<string, any>));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Writes a BN254 keystore file for a BN254 BLS private key.
|
|
201
|
+
* Returns the absolute path to the written file.
|
|
202
|
+
*
|
|
203
|
+
* @param outDir - Directory to write the keystore file to
|
|
204
|
+
* @param fileNameBase - Base name for the keystore file (will be sanitized)
|
|
205
|
+
* @param password - Password for encrypting the private key
|
|
206
|
+
* @param privateKeyHex - Private key as 0x-prefixed hex string (32 bytes)
|
|
207
|
+
* @param pubkeyHex - Public key as hex string
|
|
208
|
+
* @param derivationPath - BIP-44 style derivation path
|
|
209
|
+
* @returns Absolute path to the written keystore file
|
|
210
|
+
*/
|
|
211
|
+
export async function writeBn254BlsKeystore(
|
|
212
|
+
outDir: string,
|
|
213
|
+
fileNameBase: string,
|
|
214
|
+
password: string,
|
|
215
|
+
privateKeyHex: string,
|
|
216
|
+
pubkeyHex: string,
|
|
217
|
+
derivationPath: string,
|
|
218
|
+
): Promise<string> {
|
|
219
|
+
mkdirSync(outDir, { recursive: true });
|
|
220
|
+
|
|
221
|
+
const keystore = createBn254Keystore(password, privateKeyHex, pubkeyHex, derivationPath);
|
|
222
|
+
|
|
223
|
+
const safeBase = fileNameBase.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
224
|
+
const outPath = join(outDir, `keystore-${safeBase}.json`);
|
|
225
|
+
await writeFile(outPath, JSON.stringify(keystore, null, 2), { encoding: 'utf-8' });
|
|
226
|
+
return outPath;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Replace plaintext BLS keys in validators with { path, password } pointing to BN254 keystore files. */
|
|
230
|
+
export async function writeBlsBn254ToFile(
|
|
231
|
+
validators: ValidatorKeyStore[],
|
|
232
|
+
options: { outDir: string; password: string },
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
for (let i = 0; i < validators.length; i++) {
|
|
235
|
+
const v = validators[i];
|
|
236
|
+
if (!v || typeof v !== 'object' || !('attester' in v)) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const att = (v as any).attester;
|
|
240
|
+
|
|
241
|
+
// Shapes: { bls: <hex> } or { eth: <ethAccount>, bls?: <hex> } or plain EthAccount
|
|
242
|
+
const blsKey: string | undefined = typeof att === 'object' && 'bls' in att ? (att as any).bls : undefined;
|
|
243
|
+
if (!blsKey || typeof blsKey !== 'string') {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const pub = await computeBlsPublicKeyCompressed(blsKey);
|
|
248
|
+
const path = 'm/12381/3600/0/0/0';
|
|
249
|
+
const fileBase = `${String(i + 1)}_${pub.slice(2, 18)}`;
|
|
250
|
+
const keystorePath = await writeBn254BlsKeystore(options.outDir, fileBase, options.password, blsKey, pub, path);
|
|
251
|
+
|
|
252
|
+
if (typeof att === 'object') {
|
|
253
|
+
(att as any).bls = { path: keystorePath, password: options.password };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Writes an Ethereum JSON V3 keystore using ethers, returns absolute path */
|
|
259
|
+
export async function writeEthJsonV3Keystore(
|
|
260
|
+
outDir: string,
|
|
261
|
+
fileNameBase: string,
|
|
262
|
+
password: string,
|
|
263
|
+
privateKeyHex: string,
|
|
264
|
+
): Promise<string> {
|
|
265
|
+
const safeBase = fileNameBase.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
266
|
+
mkdirSync(outDir, { recursive: true });
|
|
267
|
+
const wallet = new Wallet(privateKeyHex);
|
|
268
|
+
const json = await wallet.encrypt(password);
|
|
269
|
+
const outPath = join(outDir, `keystore-eth-${safeBase}.json`);
|
|
270
|
+
await writeFile(outPath, json, { encoding: 'utf-8' });
|
|
271
|
+
return outPath;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Replace plaintext ETH keys in validators with { path, password } pointing to JSON V3 files. */
|
|
275
|
+
export async function writeEthJsonV3ToFile(
|
|
276
|
+
validators: ValidatorKeyStore[],
|
|
277
|
+
options: { outDir: string; password: string },
|
|
278
|
+
): Promise<void> {
|
|
279
|
+
const maybeEncryptEth = async (account: any, label: string) => {
|
|
280
|
+
if (typeof account === 'string' && account.startsWith('0x') && account.length === 66) {
|
|
281
|
+
const fileBase = `${label}_${account.slice(2, 10)}`;
|
|
282
|
+
const p = await writeEthJsonV3Keystore(options.outDir, fileBase, options.password, account);
|
|
283
|
+
return { path: p, password: options.password };
|
|
284
|
+
}
|
|
285
|
+
return account;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
for (let i = 0; i < validators.length; i++) {
|
|
289
|
+
const v = validators[i];
|
|
290
|
+
if (!v || typeof v !== 'object') {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// attester may be string (eth), object with eth, or remote signer
|
|
295
|
+
const att = (v as any).attester;
|
|
296
|
+
if (typeof att === 'string') {
|
|
297
|
+
(v as any).attester = await maybeEncryptEth(att, `attester_${i + 1}`);
|
|
298
|
+
} else if (att && typeof att === 'object' && 'eth' in att) {
|
|
299
|
+
(att as any).eth = await maybeEncryptEth((att as any).eth, `attester_${i + 1}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// publisher can be single or array
|
|
303
|
+
if ('publisher' in v) {
|
|
304
|
+
const pub = (v as any).publisher;
|
|
305
|
+
if (Array.isArray(pub)) {
|
|
306
|
+
const out: any[] = [];
|
|
307
|
+
for (let j = 0; j < pub.length; j++) {
|
|
308
|
+
out.push(await maybeEncryptEth(pub[j], `publisher_${i + 1}_${j + 1}`));
|
|
309
|
+
}
|
|
310
|
+
(v as any).publisher = out;
|
|
311
|
+
} else if (pub !== undefined) {
|
|
312
|
+
(v as any).publisher = await maybeEncryptEth(pub, `publisher_${i + 1}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Optional fundingAccount within validator
|
|
317
|
+
if ('fundingAccount' in v) {
|
|
318
|
+
(v as any).fundingAccount = await maybeEncryptEth((v as any).fundingAccount, `funding_${i + 1}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -31,6 +31,8 @@ export type L2ChainConfig = L1ContractsConfig &
|
|
|
31
31
|
publicIncludeMetrics?: string[];
|
|
32
32
|
publicMetricsCollectorUrl?: string;
|
|
33
33
|
publicMetricsCollectFrom?: string[];
|
|
34
|
+
skipArchiverInitialSync?: boolean;
|
|
35
|
+
blobAllowEmptySources?: boolean;
|
|
34
36
|
|
|
35
37
|
// Setting the dbMapSize provides the default for every DB in the node.
|
|
36
38
|
// Then we explicitly override the sizes for the archiver and the larger trees.
|
|
@@ -294,6 +296,8 @@ export const testnetL2ChainConfig: L2ChainConfig = {
|
|
|
294
296
|
publicIncludeMetrics,
|
|
295
297
|
publicMetricsCollectorUrl: 'https://telemetry.alpha-testnet.aztec-labs.com/v1/metrics',
|
|
296
298
|
publicMetricsCollectFrom: ['sequencer'],
|
|
299
|
+
skipArchiverInitialSync: true,
|
|
300
|
+
blobAllowEmptySources: true,
|
|
297
301
|
|
|
298
302
|
// Deployment stuff
|
|
299
303
|
/** How many seconds an L1 slot lasts. */
|
|
@@ -443,7 +447,7 @@ export const devnetL2ChainConfig: L2ChainConfig = {
|
|
|
443
447
|
/** The target validator committee size. */
|
|
444
448
|
aztecTargetCommitteeSize: 1,
|
|
445
449
|
/** The number of epochs to lag behind the current epoch for validator selection. */
|
|
446
|
-
lagInEpochs:
|
|
450
|
+
lagInEpochs: 1,
|
|
447
451
|
/** The local ejection threshold for a validator. Stricter than ejectionThreshold but local to a specific rollup */
|
|
448
452
|
localEjectionThreshold: DefaultL1ContractsConfig.localEjectionThreshold,
|
|
449
453
|
/** The number of epochs after an epoch ends that proofs are still accepted. */
|
|
@@ -520,6 +524,14 @@ export function enrichEnvironmentWithChainConfig(networkName: NetworkNames) {
|
|
|
520
524
|
enrichVar('NULLIFIER_TREE_MAP_SIZE_KB', config.nullifierTreeMapSizeKb.toString());
|
|
521
525
|
enrichVar('PUBLIC_DATA_TREE_MAP_SIZE_KB', config.publicDataTreeMapSizeKb.toString());
|
|
522
526
|
|
|
527
|
+
if (config.skipArchiverInitialSync !== undefined) {
|
|
528
|
+
enrichVar('SKIP_ARCHIVER_INITIAL_SYNC', config.skipArchiverInitialSync.toString());
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (config.blobAllowEmptySources !== undefined) {
|
|
532
|
+
enrichVar('BLOB_ALLOW_EMPTY_SOURCES', config.blobAllowEmptySources.toString());
|
|
533
|
+
}
|
|
534
|
+
|
|
523
535
|
if (config.autoUpdate) {
|
|
524
536
|
enrichVar('AUTO_UPDATE', config.autoUpdate?.toString());
|
|
525
537
|
}
|
package/src/utils/commands.ts
CHANGED
|
@@ -112,6 +112,34 @@ export async function getTxSender(pxe: PXE, _from?: string) {
|
|
|
112
112
|
return from;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Parses and validates a hex string. Removes leading 0x if present, checks for hex validity,
|
|
117
|
+
* and enforces an optional minimum length.
|
|
118
|
+
* @param hex - The hex string to validate.
|
|
119
|
+
* @param minLen - Optional minimum length (in hex characters, after stripping '0x').
|
|
120
|
+
* @returns The normalized hex string (without leading 0x).
|
|
121
|
+
* @throws InvalidArgumentError if the string is not valid hex or does not meet the minimum length.
|
|
122
|
+
*/
|
|
123
|
+
// minLen is now interpreted as the minimum number of bytes (2 hex characters per byte)
|
|
124
|
+
export function parseHex(hex: string, minLen?: number): `0x${string}` {
|
|
125
|
+
const normalized = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
126
|
+
|
|
127
|
+
if (!/^[0-9a-fA-F]*$/.test(normalized)) {
|
|
128
|
+
throw new InvalidArgumentError('Invalid hex string');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (minLen !== undefined) {
|
|
132
|
+
const minHexLen = minLen * 2;
|
|
133
|
+
if (normalized.length < minHexLen) {
|
|
134
|
+
throw new InvalidArgumentError(
|
|
135
|
+
`Hex string is too short (length ${normalized.length}), minimum byte length is ${minLen} (hex chars: ${minHexLen})`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return `0x${normalized}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
115
143
|
/**
|
|
116
144
|
* Removes the leading 0x from a hex string. If no leading 0x is found the string is returned unchanged.
|
|
117
145
|
* @param hex - A hex string
|
|
@@ -166,7 +194,7 @@ export function parseAztecAddress(address: string): AztecAddress {
|
|
|
166
194
|
try {
|
|
167
195
|
return AztecAddress.fromString(address);
|
|
168
196
|
} catch {
|
|
169
|
-
throw new InvalidArgumentError(`Invalid address: ${address}`);
|
|
197
|
+
throw new InvalidArgumentError(`Invalid Aztec address: ${address}`);
|
|
170
198
|
}
|
|
171
199
|
}
|
|
172
200
|
|
|
@@ -180,7 +208,7 @@ export function parseEthereumAddress(address: string): EthAddress {
|
|
|
180
208
|
try {
|
|
181
209
|
return EthAddress.fromString(address);
|
|
182
210
|
} catch {
|
|
183
|
-
throw new InvalidArgumentError(`Invalid
|
|
211
|
+
throw new InvalidArgumentError(`Invalid Ethereumaddress: ${address}`);
|
|
184
212
|
}
|
|
185
213
|
}
|
|
186
214
|
|
|
@@ -234,7 +262,11 @@ export function parseOptionalSelector(selector: string): FunctionSelector | unde
|
|
|
234
262
|
* @returns The parsed integer, or undefined if the input string is falsy.
|
|
235
263
|
* @throws If the input is not a valid integer.
|
|
236
264
|
*/
|
|
237
|
-
export function parseOptionalInteger(
|
|
265
|
+
export function parseOptionalInteger(
|
|
266
|
+
value: string,
|
|
267
|
+
min: number = Number.MIN_SAFE_INTEGER,
|
|
268
|
+
max: number = Number.MAX_SAFE_INTEGER,
|
|
269
|
+
): number | undefined {
|
|
238
270
|
if (!value) {
|
|
239
271
|
return undefined;
|
|
240
272
|
}
|
|
@@ -242,6 +274,12 @@ export function parseOptionalInteger(value: string): number | undefined {
|
|
|
242
274
|
if (!Number.isInteger(parsed)) {
|
|
243
275
|
throw new InvalidArgumentError('Invalid integer.');
|
|
244
276
|
}
|
|
277
|
+
if (parsed < min) {
|
|
278
|
+
throw new InvalidArgumentError(`Value must be greater than ${min}.`);
|
|
279
|
+
}
|
|
280
|
+
if (parsed > max) {
|
|
281
|
+
throw new InvalidArgumentError(`Value must be less than ${max}.`);
|
|
282
|
+
}
|
|
245
283
|
return parsed;
|
|
246
284
|
}
|
|
247
285
|
|