@provablehq/sdk 0.10.5 → 0.10.6-rc.1

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 (67) hide show
  1. package/dist/mainnet/browser.cjs +927 -357
  2. package/dist/mainnet/browser.cjs.map +1 -1
  3. package/dist/mainnet/browser.d.cts +6 -4
  4. package/dist/mainnet/browser.d.ts +6 -4
  5. package/dist/mainnet/browser.js +923 -364
  6. package/dist/mainnet/browser.js.map +1 -1
  7. package/dist/mainnet/keys/keystore/indexeddb.d.cts +49 -0
  8. package/dist/mainnet/keys/keystore/indexeddb.d.ts +49 -0
  9. package/dist/mainnet/keys/provider/memory.d.cts +1 -1
  10. package/dist/mainnet/keys/provider/memory.d.ts +1 -1
  11. package/dist/mainnet/models/record-scanner/error.d.cts +1 -1
  12. package/dist/mainnet/models/record-scanner/error.d.ts +1 -1
  13. package/dist/mainnet/models/record-scanner/registrationResult.d.cts +1 -1
  14. package/dist/mainnet/models/record-scanner/registrationResult.d.ts +1 -1
  15. package/dist/mainnet/network-client.d.cts +17 -1
  16. package/dist/mainnet/network-client.d.ts +17 -1
  17. package/dist/mainnet/node.cjs +11 -0
  18. package/dist/mainnet/node.cjs.map +1 -1
  19. package/dist/mainnet/node.js +2 -2
  20. package/dist/mainnet/program-manager.d.cts +61 -1
  21. package/dist/mainnet/program-manager.d.ts +61 -1
  22. package/dist/mainnet/record-scanner.d.cts +2 -10
  23. package/dist/mainnet/record-scanner.d.ts +2 -10
  24. package/dist/mainnet/utils/index.d.cts +3 -0
  25. package/dist/mainnet/utils/index.d.ts +3 -0
  26. package/dist/mainnet/utils/logger.d.cts +30 -0
  27. package/dist/mainnet/utils/logger.d.ts +30 -0
  28. package/dist/mainnet/wasm.d.cts +1 -1
  29. package/dist/mainnet/wasm.d.ts +1 -1
  30. package/dist/testnet/browser.cjs +927 -357
  31. package/dist/testnet/browser.cjs.map +1 -1
  32. package/dist/testnet/browser.d.cts +6 -4
  33. package/dist/testnet/browser.d.ts +6 -4
  34. package/dist/testnet/browser.js +923 -364
  35. package/dist/testnet/browser.js.map +1 -1
  36. package/dist/testnet/keys/keystore/indexeddb.d.cts +49 -0
  37. package/dist/testnet/keys/keystore/indexeddb.d.ts +49 -0
  38. package/dist/testnet/keys/provider/memory.d.cts +1 -1
  39. package/dist/testnet/keys/provider/memory.d.ts +1 -1
  40. package/dist/testnet/models/record-scanner/error.d.cts +1 -1
  41. package/dist/testnet/models/record-scanner/error.d.ts +1 -1
  42. package/dist/testnet/models/record-scanner/registrationResult.d.cts +1 -1
  43. package/dist/testnet/models/record-scanner/registrationResult.d.ts +1 -1
  44. package/dist/testnet/network-client.d.cts +17 -1
  45. package/dist/testnet/network-client.d.ts +17 -1
  46. package/dist/testnet/node.cjs +11 -0
  47. package/dist/testnet/node.cjs.map +1 -1
  48. package/dist/testnet/node.js +2 -2
  49. package/dist/testnet/program-manager.d.cts +61 -1
  50. package/dist/testnet/program-manager.d.ts +61 -1
  51. package/dist/testnet/record-scanner.d.cts +2 -10
  52. package/dist/testnet/record-scanner.d.ts +2 -10
  53. package/dist/testnet/utils/index.d.cts +3 -0
  54. package/dist/testnet/utils/index.d.ts +3 -0
  55. package/dist/testnet/utils/logger.d.cts +30 -0
  56. package/dist/testnet/utils/logger.d.ts +30 -0
  57. package/dist/testnet/wasm.d.cts +1 -1
  58. package/dist/testnet/wasm.d.ts +1 -1
  59. package/package.json +2 -2
  60. package/dist/mainnet/models/record-scanner/registrationRequest.d.cts +0 -13
  61. package/dist/mainnet/models/record-scanner/registrationRequest.d.ts +0 -13
  62. package/dist/testnet/models/record-scanner/registrationRequest.d.cts +0 -13
  63. package/dist/testnet/models/record-scanner/registrationRequest.d.ts +0 -13
  64. /package/dist/mainnet/{utils.d.cts → utils/utils.d.cts} +0 -0
  65. /package/dist/mainnet/{utils.d.ts → utils/utils.d.ts} +0 -0
  66. /package/dist/testnet/{utils.d.cts → utils/utils.d.cts} +0 -0
  67. /package/dist/testnet/{utils.d.ts → utils/utils.d.ts} +0 -0
@@ -1,9 +1,409 @@
1
1
  import 'core-js/proposals/json-parse-with-source.js';
2
- import { ViewKey, ComputeKey, Address, PrivateKeyCiphertext, PrivateKey, RecordCiphertext, EncryptionToolkit, Group, Metadata, VerifyingKey, Program, Plaintext, Transaction, ProvingRequest, ProvingKey, RecordPlaintext, Field, Poseidon4, ProgramManager as ProgramManager$1, CallbackQuery, QueryOption, ExecutionRequest, stringToField, verifyFunctionExecution, Value, Proof, Signature } from '@provablehq/wasm/testnet.js';
3
- export { Address, Authorization, BHP1024, BHP256, BHP512, BHP768, Boolean, Ciphertext, ComputeKey, DynamicRecord, EncryptionToolkit, ExecutionRequest, ExecutionResponse, Field, Execution as FunctionExecution, GraphKey, Group, I128, I16, I32, I64, I8, OfflineQuery, Pedersen128, Pedersen64, Plaintext, Poseidon2, Poseidon4, Poseidon8, PrivateKey, PrivateKeyCiphertext, Program, ProgramManager as ProgramManagerBase, Proof, ProvingKey, ProvingRequest, RecordCiphertext, RecordPlaintext, Scalar, Signature, Transaction, Transition, U128, U16, U32, U64, U8, Value, VerifyingKey, ViewKey, getOrInitConsensusVersionTestHeights, initThreadPool, snarkVerify, snarkVerifyBatch, stringToField, verifyFunctionExecution } from '@provablehq/wasm/testnet.js';
2
+ import { ProvingKey, VerifyingKey, ViewKey, ComputeKey, Address, PrivateKeyCiphertext, PrivateKey, RecordCiphertext, EncryptionToolkit, Group, Metadata, Program, Plaintext, Transaction, ProvingRequest, RecordPlaintext, Field, Poseidon4, ProgramManager as ProgramManager$1, CallbackQuery, ProgramImports, QueryOption, ExecutionRequest, stringToField, verifyFunctionExecution, Value, Proof, Signature } from '@provablehq/wasm/testnet.js';
3
+ export { Address, Authorization, BHP1024, BHP256, BHP512, BHP768, Boolean, Ciphertext, ComputeKey, DynamicRecord, EncryptionToolkit, ExecutionRequest, ExecutionResponse, Field, Execution as FunctionExecution, GraphKey, Group, I128, I16, I32, I64, I8, OfflineQuery, Pedersen128, Pedersen64, Plaintext, Poseidon2, Poseidon4, Poseidon8, PrivateKey, PrivateKeyCiphertext, Program, ProgramImports as ProgramImportsBuilder, ProgramManager as ProgramManagerBase, Proof, ProvingKey, ProvingRequest, QueryOption, RecordCiphertext, RecordPlaintext, Scalar, Signature, Transaction, Transition, U128, U16, U32, U64, U8, Value, VerifyingKey, ViewKey, getOrInitConsensusVersionTestHeights, initThreadPool, snarkVerify, snarkVerifyBatch, stringToField, verifyFunctionExecution } from '@provablehq/wasm/testnet.js';
4
4
  import { cryptoBoxSeal } from '@serenity-kit/noble-sodium';
5
5
  import { base64, bech32m } from '@scure/base';
6
6
 
7
+ const LEVEL_PRIORITY = {
8
+ silent: 0,
9
+ error: 1,
10
+ warn: 2,
11
+ info: 3,
12
+ debug: 4,
13
+ };
14
+ let currentLevel = "info";
15
+ /**
16
+ * Set the SDK log level. Levels are hierarchical:
17
+ * - "silent" — suppress all SDK logging
18
+ * - "error" — only errors
19
+ * - "warn" — errors + warnings
20
+ * - "info" — errors + warnings + info (default)
21
+ * - "debug" — everything including debug traces
22
+ */
23
+ function setLogLevel(level) {
24
+ currentLevel = level;
25
+ }
26
+ /** Returns the current SDK log level. */
27
+ function getLogLevel() {
28
+ return currentLevel;
29
+ }
30
+ function shouldLog(level) {
31
+ return LEVEL_PRIORITY[level] <= LEVEL_PRIORITY[currentLevel];
32
+ }
33
+ /**
34
+ * SDK logger object following console.log/warn/error/debug syntax.
35
+ * Respects the current log level set via setLogLevel().
36
+ */
37
+ const logger = {
38
+ log(...args) {
39
+ if (shouldLog("info"))
40
+ console.log(...args);
41
+ },
42
+ warn(...args) {
43
+ if (shouldLog("warn"))
44
+ console.warn(...args);
45
+ },
46
+ error(...args) {
47
+ if (shouldLog("error"))
48
+ console.error(...args);
49
+ },
50
+ debug(...args) {
51
+ if (shouldLog("debug"))
52
+ console.debug(...args);
53
+ },
54
+ };
55
+
56
+ /**
57
+ * Error thrown when a key locator is invalid for filesystem use.
58
+ * Used to prevent path traversal and other filesystem injection when deriving paths from locators.
59
+ *
60
+ * @extends Error
61
+ */
62
+ class InvalidLocatorError extends Error {
63
+ locator;
64
+ reason;
65
+ /**
66
+ * @param message - Human-readable description of the validation failure.
67
+ * @param locator - The invalid locator string that failed validation.
68
+ * @param reason - Machine-readable reason code for the failure.
69
+ */
70
+ constructor(message, locator, reason) {
71
+ super(message);
72
+ this.locator = locator;
73
+ this.reason = reason;
74
+ this.name = "InvalidLocatorError";
75
+ Object.setPrototypeOf(this, InvalidLocatorError.prototype);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Error thrown when there is a mismatch between expected and actual key metadata.
81
+ * This can occur during verification of either the checksum or size of a key.
82
+ *
83
+ * @extends Error
84
+ */
85
+ class KeyVerificationError extends Error {
86
+ locator;
87
+ field;
88
+ expected;
89
+ actual;
90
+ /**
91
+ * Creates a new KeyVerificationError instance (error.name is "ChecksumMismatchError").
92
+ *
93
+ * @param {string} locator - The key locator where the mismatch occurred.
94
+ * @param {"checksum" | "size"} field - The field that failed verification (either "checksum" or "size").
95
+ * @param {string} expected - The expected value of the field.
96
+ * @param {string} actual - The actual value encountered.
97
+ */
98
+ constructor(locator, field, expected, actual) {
99
+ super(`Key verification ${locator} ${field} mismatch: expected ${expected}, got ${actual}`);
100
+ this.locator = locator;
101
+ this.field = field;
102
+ this.expected = expected;
103
+ this.actual = actual;
104
+ this.name = "ChecksumMismatchError";
105
+ Object.setPrototypeOf(this, KeyVerificationError.prototype);
106
+ }
107
+ }
108
+ /**
109
+ * Computes the SHA-256 checksum of a given set of bytes.
110
+ *
111
+ * @param {Uint8Array} bytes - The bytes to compute the checksum of.
112
+ */
113
+ async function sha256Hex(bytes) {
114
+ const hash = await crypto.subtle.digest("SHA-256", bytes);
115
+ return Array.from(new Uint8Array(hash))
116
+ .map((b) => b.toString(16).padStart(2, "0"))
117
+ .join("");
118
+ }
119
+
120
+ /**
121
+ * In-memory implementation of KeyVerifier that stores and verifies key fingerprints.
122
+ * Provides functionality to compute and verify cryptographic checksums of keys, storing them
123
+ * in memory for subsequent verification. This implementation is primarily used for testing
124
+ * and development purposes where persistence is not required.
125
+ *
126
+ * Key features:
127
+ * - Computes SHA-256 checksums and sizes for key bytes.
128
+ * - Stores key fingerprints in memory using string locators.
129
+ * - Verifies key bytes against stored or provided fingerprints.
130
+ *
131
+ * @implements {KeyVerifier}
132
+ */
133
+ class MemKeyVerifier {
134
+ keyStore = {};
135
+ /**
136
+ * Computes and optionally stores key metadata. If a keyFingerprint is provided, this function will verify the computed checksum against it before storing it.
137
+ *
138
+ * @param {KeyMetadata} keyMetadata - Object containing key bytes and optional verification data.
139
+ * @throws {KeyVerificationError} When provided keyFingerprint doesn't match computed values.
140
+ * @returns {Promise<KeyFingerprint>} Computed key metadata.
141
+ */
142
+ async computeKeyMetadata(keyMetadata) {
143
+ // Compute the metadata from the key bytes
144
+ const computedFingerprint = {
145
+ checksum: await sha256Hex(keyMetadata.keyBytes),
146
+ size: keyMetadata.keyBytes.length
147
+ };
148
+ // If a KeyFingerprint is provided, verify it matches computed values.
149
+ if (keyMetadata.fingerprint) {
150
+ if (keyMetadata.fingerprint.size !== computedFingerprint.size) {
151
+ throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "size", String(keyMetadata.fingerprint.size), String(computedFingerprint.size));
152
+ }
153
+ if (keyMetadata.fingerprint.checksum !== computedFingerprint.checksum) {
154
+ throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "checksum", keyMetadata.fingerprint.checksum, computedFingerprint.checksum);
155
+ }
156
+ }
157
+ // If locator is provided, store only the fingerprint
158
+ if (keyMetadata.locator) {
159
+ this.keyStore[keyMetadata.locator] = computedFingerprint;
160
+ }
161
+ return computedFingerprint;
162
+ }
163
+ /**
164
+ * Verifies key bytes against stored or provided metadata. Follows a priority verification scheme:
165
+ * 1. If KeyFingerprint is provided in the metadata, this method verifies against that first.
166
+ * 2. If a locator is provided, attempts to verify against stored fingerprint.
167
+ * 3. If neither is available, throws an error.
168
+ *
169
+ * @param {KeyMetadata} keyMetadata - Object containing the key bytes and optional verification metadata.
170
+ * @throws {Error} When neither fingerprint nor valid locator is provided for verification.
171
+ * @throws {KeyVerificationError} When size or checksum verification fails.
172
+ * @returns {Promise<void>} Promise that resolves when verification succeeds.
173
+ */
174
+ async verifyKeyBytes(keyMetadata) {
175
+ if (!keyMetadata.keyBytes) {
176
+ throw new Error("Key bytes must be provided for verification.");
177
+ }
178
+ // Compute the fingerprint for the provided bytes.
179
+ const computedFingerprint = await this.computeKeyMetadata({
180
+ keyBytes: keyMetadata.keyBytes
181
+ });
182
+ // Determine which fingerprint to verify against.
183
+ let fingerprintToVerify;
184
+ if (keyMetadata.fingerprint) {
185
+ // If a key fingerprint is provided, use it.
186
+ fingerprintToVerify = keyMetadata.fingerprint;
187
+ }
188
+ else if (keyMetadata.locator) {
189
+ // Otherwise try to get stored fingerprint by locator.
190
+ fingerprintToVerify = this.keyStore[keyMetadata.locator];
191
+ }
192
+ if (!fingerprintToVerify) {
193
+ throw new Error("Either fingerprint or a valid locator must be provided for verification.");
194
+ }
195
+ // Verify the key size.
196
+ if (fingerprintToVerify.size !== computedFingerprint.size) {
197
+ throw new KeyVerificationError(keyMetadata.locator || "", "size", String(fingerprintToVerify.size), String(computedFingerprint.size));
198
+ }
199
+ // Verify the key checksum.
200
+ if (fingerprintToVerify.checksum !== computedFingerprint.checksum) {
201
+ throw new KeyVerificationError(keyMetadata.locator || "", "checksum", fingerprintToVerify.checksum, computedFingerprint.checksum);
202
+ }
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Browser-compatible {@link KeyStore} backed by IndexedDB.
208
+ *
209
+ * This is the browser counterpart to {@link LocalFileKeyStore} (which requires Node.js `fs`).
210
+ * It persists proving and verifying keys across page reloads and browser sessions using the
211
+ * IndexedDB API available in all modern browsers and Web Workers.
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * import { IndexedDBKeyStore, ProgramManager } from "@provablehq/sdk";
216
+ *
217
+ * const keyStore = new IndexedDBKeyStore();
218
+ * const pm = new ProgramManager();
219
+ * pm.setKeyStore(keyStore);
220
+ * // Keys synthesized during execution are now cached in IndexedDB
221
+ * // and reloaded automatically on subsequent runs.
222
+ * ```
223
+ */
224
+ class IndexedDBKeyStore {
225
+ dbName;
226
+ storeName = "keys";
227
+ keyVerifier = new MemKeyVerifier();
228
+ dbPromise = null;
229
+ /**
230
+ * @param dbName IndexedDB database name. Defaults to `"aleo-keystore"`.
231
+ */
232
+ constructor(dbName = "aleo-keystore") {
233
+ this.dbName = dbName;
234
+ }
235
+ // -------------------------------------------------------
236
+ // IndexedDB helpers
237
+ // -------------------------------------------------------
238
+ /** Opens (or creates) the database, returning a cached promise. */
239
+ openDB() {
240
+ if (this.dbPromise)
241
+ return this.dbPromise;
242
+ this.dbPromise = new Promise((resolve, reject) => {
243
+ const request = indexedDB.open(this.dbName, 1);
244
+ request.onupgradeneeded = () => {
245
+ const db = request.result;
246
+ if (!db.objectStoreNames.contains(this.storeName)) {
247
+ db.createObjectStore(this.storeName, { keyPath: "locator" });
248
+ }
249
+ };
250
+ request.onsuccess = () => resolve(request.result);
251
+ request.onerror = () => {
252
+ this.dbPromise = null;
253
+ reject(request.error);
254
+ };
255
+ request.onblocked = () => {
256
+ this.dbPromise = null;
257
+ reject(new DOMException("Database open blocked", "AbortError"));
258
+ };
259
+ });
260
+ return this.dbPromise;
261
+ }
262
+ /** Runs a single read-write transaction and returns the request result. */
263
+ async tx(mode, fn) {
264
+ const db = await this.openDB();
265
+ return new Promise((resolve, reject) => {
266
+ const txn = db.transaction(this.storeName, mode);
267
+ const store = txn.objectStore(this.storeName);
268
+ const req = fn(store);
269
+ let result;
270
+ req.onsuccess = () => { result = req.result; };
271
+ txn.oncomplete = () => resolve(result);
272
+ txn.onerror = () => reject(txn.error);
273
+ txn.onabort = () => reject(txn.error ?? new DOMException("Transaction aborted", "AbortError"));
274
+ });
275
+ }
276
+ // -------------------------------------------------------
277
+ // Locator serialization (mirrors LocalFileKeyStore)
278
+ // -------------------------------------------------------
279
+ validateComponent(value, label) {
280
+ if (value === "" || value === ".") {
281
+ throw new InvalidLocatorError(`KeyLocator ${label} must not be empty or "." (got "${value}")`, value, "reserved_name");
282
+ }
283
+ if (value.includes("..")) {
284
+ throw new InvalidLocatorError(`KeyLocator ${label} must not contain ".." (got "${value}")`, value, "path_traversal");
285
+ }
286
+ if (value.includes("/") || value.includes("\\") || value.includes("\0")) {
287
+ throw new InvalidLocatorError(`KeyLocator ${label} must not contain path separators or null bytes (got "${value}")`, value, "path_separator");
288
+ }
289
+ }
290
+ validateNonNegative(value, label) {
291
+ if (!Number.isInteger(value) || value < 0) {
292
+ throw new InvalidLocatorError(`KeyLocator ${label} must be a non-negative integer (got ${value})`, String(value), "negative_value");
293
+ }
294
+ }
295
+ serializeLocator(locator) {
296
+ this.validateComponent(locator.program, "program");
297
+ this.validateComponent(locator.functionName, "functionName");
298
+ this.validateComponent(locator.network, "network");
299
+ this.validateNonNegative(locator.edition, "edition");
300
+ this.validateNonNegative(locator.amendment, "amendment");
301
+ const base = `${locator.program}.${locator.functionName}.e${locator.edition}.a${locator.amendment}.${locator.network}.${locator.keyType}`;
302
+ if (locator.keyType === "translation") {
303
+ this.validateComponent(locator.recordName, "recordName");
304
+ this.validateNonNegative(locator.recordInputPosition, "recordInputPosition");
305
+ return `${base}.${locator.recordName}.${locator.recordInputPosition}`;
306
+ }
307
+ return base;
308
+ }
309
+ checksumToFingerprint(checksum, keyBytes) {
310
+ if (!checksum)
311
+ return undefined;
312
+ return { checksum, size: keyBytes.length };
313
+ }
314
+ // -------------------------------------------------------
315
+ // KeyStore interface
316
+ // -------------------------------------------------------
317
+ async getKeyBytes(locator) {
318
+ const key = this.serializeLocator(locator);
319
+ const record = await this.tx("readonly", (store) => store.get(key));
320
+ if (!record)
321
+ return null;
322
+ const fingerprint = this.checksumToFingerprint(locator.checksum, record.bytes) ?? record.metadata;
323
+ if (fingerprint) {
324
+ await this.keyVerifier.verifyKeyBytes({
325
+ keyBytes: record.bytes,
326
+ locator: key,
327
+ fingerprint,
328
+ });
329
+ }
330
+ return record.bytes;
331
+ }
332
+ async getProvingKey(locator) {
333
+ const bytes = await this.getKeyBytes(locator);
334
+ if (!bytes)
335
+ return null;
336
+ return ProvingKey.fromBytes(bytes);
337
+ }
338
+ async getVerifyingKey(locator) {
339
+ const bytes = await this.getKeyBytes(locator);
340
+ if (!bytes)
341
+ return null;
342
+ return VerifyingKey.fromBytes(bytes);
343
+ }
344
+ async setKeys(proverLocator, verifierLocator, keys) {
345
+ const proverKey = this.serializeLocator(proverLocator);
346
+ const verifierKey = this.serializeLocator(verifierLocator);
347
+ const [provingKey, verifyingKey] = keys;
348
+ const [provingKeyBytes, verifyingKeyBytes] = [
349
+ provingKey.toBytes(),
350
+ verifyingKey.toBytes(),
351
+ ];
352
+ const [proverFingerprint, verifierFingerprint] = await Promise.all([
353
+ this.keyVerifier.computeKeyMetadata({
354
+ keyBytes: provingKeyBytes,
355
+ locator: proverKey,
356
+ fingerprint: this.checksumToFingerprint(proverLocator.checksum, provingKeyBytes),
357
+ }),
358
+ this.keyVerifier.computeKeyMetadata({
359
+ keyBytes: verifyingKeyBytes,
360
+ locator: verifierKey,
361
+ fingerprint: this.checksumToFingerprint(verifierLocator.checksum, verifyingKeyBytes),
362
+ }),
363
+ ]);
364
+ const proverRecord = { locator: proverKey, bytes: provingKeyBytes, metadata: proverFingerprint };
365
+ const verifierRecord = { locator: verifierKey, bytes: verifyingKeyBytes, metadata: verifierFingerprint };
366
+ // Write both in a single transaction for atomicity.
367
+ const db = await this.openDB();
368
+ await new Promise((resolve, reject) => {
369
+ const txn = db.transaction(this.storeName, "readwrite");
370
+ const store = txn.objectStore(this.storeName);
371
+ store.put(proverRecord);
372
+ store.put(verifierRecord);
373
+ txn.oncomplete = () => resolve();
374
+ txn.onerror = () => reject(txn.error);
375
+ txn.onabort = () => reject(txn.error ?? new DOMException("Transaction aborted", "AbortError"));
376
+ });
377
+ }
378
+ async setKeyBytes(keyBytes, locator) {
379
+ const key = this.serializeLocator(locator);
380
+ const computedMetadata = await this.keyVerifier.computeKeyMetadata({
381
+ keyBytes,
382
+ locator: key,
383
+ fingerprint: this.checksumToFingerprint(locator.checksum, keyBytes),
384
+ });
385
+ const record = { locator: key, bytes: keyBytes, metadata: computedMetadata };
386
+ await this.tx("readwrite", (store) => store.put(record));
387
+ }
388
+ async getKeyMetadata(locator) {
389
+ const key = this.serializeLocator(locator);
390
+ const record = await this.tx("readonly", (store) => store.get(key));
391
+ return record?.metadata ?? null;
392
+ }
393
+ async has(locator) {
394
+ const key = this.serializeLocator(locator);
395
+ const count = await this.tx("readonly", (store) => store.count(IDBKeyRange.only(key)));
396
+ return count > 0;
397
+ }
398
+ async delete(locator) {
399
+ const key = this.serializeLocator(locator);
400
+ await this.tx("readwrite", (store) => store.delete(key));
401
+ }
402
+ async clear() {
403
+ await this.tx("readwrite", (store) => store.clear());
404
+ }
405
+ }
406
+
7
407
  /**
8
408
  * Encrypt an authorization with a cryptobox X25519 public key (libsodium-compatible wire format).
9
409
  *
@@ -163,7 +563,7 @@ class Account {
163
563
  this._privateKey = this.privateKeyFromParams(params);
164
564
  }
165
565
  catch (e) {
166
- console.error("Wrong parameter", e);
566
+ logger.error("Wrong parameter", e);
167
567
  throw new Error("Wrong Parameter");
168
568
  }
169
569
  this._viewKey = ViewKey.from_private_key(this._privateKey);
@@ -634,7 +1034,7 @@ function environment() {
634
1034
  }
635
1035
  }
636
1036
  function logAndThrow(message) {
637
- console.error(message);
1037
+ logger.error(message);
638
1038
  throw new Error(message);
639
1039
  }
640
1040
  /** Default transport — wraps global fetch to avoid illegal-invocation errors in browsers. */
@@ -695,7 +1095,7 @@ async function retryWithBackoff(fn, { maxAttempts = 5, baseDelay = 100, jitter,
695
1095
  const jitterAmount = jitter ?? baseDelay;
696
1096
  const actualJitter = Math.floor(Math.random() * jitterAmount);
697
1097
  const delay = baseDelay * 2 ** (attempt - 1) + actualJitter;
698
- console.warn(`Retry ${attempt}/${maxAttempts} failed. Retrying in ${delay}ms...`);
1098
+ logger.warn(`Retry ${attempt}/${maxAttempts} failed. Retrying in ${delay}ms...`);
699
1099
  await new Promise((res) => setTimeout(res, delay));
700
1100
  }
701
1101
  }
@@ -864,7 +1264,7 @@ class AleoNetworkClient {
864
1264
  else {
865
1265
  this.headers = {
866
1266
  // This is replaced by the actual version by a Rollup plugin
867
- "X-Aleo-SDK-Version": "0.10.5",
1267
+ "X-Aleo-SDK-Version": "0.10.6-rc.1",
868
1268
  "X-Aleo-environment": environment(),
869
1269
  };
870
1270
  }
@@ -880,7 +1280,7 @@ class AleoNetworkClient {
880
1280
  else {
881
1281
  this.headers = {
882
1282
  // This is replaced by the actual version by a Rollup plugin
883
- "X-Aleo-SDK-Version": "0.10.5",
1283
+ "X-Aleo-SDK-Version": "0.10.6-rc.1",
884
1284
  "X-Aleo-environment": environment(),
885
1285
  };
886
1286
  }
@@ -1196,7 +1596,7 @@ class AleoNetworkClient {
1196
1596
  continue;
1197
1597
  }
1198
1598
  catch (error) {
1199
- console.log("Found unspent record!");
1599
+ logger.log("Found unspent record!");
1200
1600
  }
1201
1601
  }
1202
1602
  // Add the record to the list of records if the user did not specify amounts.
@@ -1256,14 +1656,14 @@ class AleoNetworkClient {
1256
1656
  }
1257
1657
  catch (error) {
1258
1658
  // If there is an error fetching blocks, log it and keep searching
1259
- console.warn("Error fetching blocks in range: " +
1659
+ logger.warn("Error fetching blocks in range: " +
1260
1660
  start.toString() +
1261
1661
  "-" +
1262
1662
  end.toString());
1263
- console.warn("Error: ", error);
1663
+ logger.warn("Error: ", error);
1264
1664
  failures += 1;
1265
1665
  if (failures > 10) {
1266
- console.warn("10 failures fetching records reached. Returning records fetched so far");
1666
+ logger.warn("10 failures fetching records reached. Returning records fetched so far");
1267
1667
  return records;
1268
1668
  }
1269
1669
  }
@@ -1713,6 +2113,30 @@ class AleoNetworkClient {
1713
2113
  this.ctx = {};
1714
2114
  }
1715
2115
  }
2116
+ /**
2117
+ * Returns the current edition and amendment count for a program.
2118
+ *
2119
+ * @param {string} programId - The program ID (e.g. "hello_hello.aleo")
2120
+ * @returns {{ program_id: string, edition: number, amendment_count: number }}
2121
+ *
2122
+ * @example
2123
+ * const networkClient = new AleoNetworkClient("https://api.provable.com/v2");
2124
+ * const info = await networkClient.getProgramAmendmentCount("hello_hello.aleo");
2125
+ * console.log(info.edition, info.amendment_count);
2126
+ */
2127
+ async getProgramAmendmentCount(programId) {
2128
+ try {
2129
+ this.ctx = { "X-ALEO-METHOD": "getProgramAmendmentCount" };
2130
+ const raw = await this.fetchRaw("/programs/" + programId + "/amendment_count");
2131
+ return JSON.parse(raw);
2132
+ }
2133
+ catch (error) {
2134
+ throw new Error(`Error fetching amendment count for ${programId}: ${error}`);
2135
+ }
2136
+ finally {
2137
+ this.ctx = {};
2138
+ }
2139
+ }
1716
2140
  /**
1717
2141
  * Returns a program object from a program ID or program source code.
1718
2142
  *
@@ -2380,7 +2804,7 @@ class AleoNetworkClient {
2380
2804
  options.jwtData = jwtData;
2381
2805
  }
2382
2806
  else {
2383
- console.warn('JWT or both apiKey and consumerId are required when using the Provable API');
2807
+ logger.warn('JWT or both apiKey and consumerId are required when using the Provable API');
2384
2808
  }
2385
2809
  }
2386
2810
  // Create the necessary headers to hit the provable api.
@@ -2510,10 +2934,10 @@ class AleoNetworkClient {
2510
2934
  let text = "";
2511
2935
  try {
2512
2936
  text = await res.text();
2513
- console.warn("Response text from server:", text);
2937
+ logger.warn("Response text from server:", text);
2514
2938
  }
2515
2939
  catch (err) {
2516
- console.warn("Failed to read response text:", err);
2940
+ logger.warn("Failed to read response text:", err);
2517
2941
  }
2518
2942
  // If the transaction ID is malformed (e.g. invalid checksum, wrong length),
2519
2943
  // the API returns a 4XX with "Invalid URL" — we treat this as a fatal error and stop polling.
@@ -2524,7 +2948,7 @@ class AleoNetworkClient {
2524
2948
  return reject(new Error(`Malformed transaction ID: ${text}`));
2525
2949
  }
2526
2950
  // Log and continue polling for 404s or 5XX errors in case a tx doesn't exist yet
2527
- console.warn("Non-OK response (retrying):", res.status, text);
2951
+ logger.warn("Non-OK response (retrying):", res.status, text);
2528
2952
  return;
2529
2953
  }
2530
2954
  const data = parseJSON(await res.text());
@@ -2538,7 +2962,7 @@ class AleoNetworkClient {
2538
2962
  }
2539
2963
  }
2540
2964
  catch (err) {
2541
- console.error("Polling error:", err);
2965
+ logger.error("Polling error:", err);
2542
2966
  }
2543
2967
  }, checkInterval);
2544
2968
  });
@@ -2546,7 +2970,7 @@ class AleoNetworkClient {
2546
2970
  }
2547
2971
 
2548
2972
  /**
2549
- * Error thrown when a record scanner request fails (e.g. /register, /register/encrypted).
2973
+ * Error thrown when a record scanner request fails (e.g. /register/encrypted).
2550
2974
  * Includes HTTP status so callers can handle 422 vs 500 etc.
2551
2975
  */
2552
2976
  class RecordScannerRequestError extends Error {
@@ -2706,7 +3130,7 @@ class AleoKeyProvider {
2706
3130
  * @returns {FunctionKeyPair} Proving and verifying keys for the specified program
2707
3131
  */
2708
3132
  getKeys(keyId) {
2709
- console.debug(`Checking if key exists in cache. KeyId: ${keyId}`);
3133
+ logger.debug(`Checking if key exists in cache. KeyId: ${keyId}`);
2710
3134
  if (this.cache.has(keyId)) {
2711
3135
  const [provingKeyBytes, verifyingKeyBytes] = (this.cache.get(keyId));
2712
3136
  return [
@@ -2807,9 +3231,9 @@ class AleoKeyProvider {
2807
3231
  ];
2808
3232
  }
2809
3233
  else {
2810
- console.debug("Fetching proving keys from url " + proverUrl);
3234
+ logger.debug("Fetching proving keys from url " + proverUrl);
2811
3235
  const provingKey = (ProvingKey.fromBytes(await this.fetchBytes(proverUrl)));
2812
- console.debug("Fetching verifying keys " + verifierUrl);
3236
+ logger.debug("Fetching verifying keys " + verifierUrl);
2813
3237
  const verifyingKey = (await this.getVerifyingKey(verifierUrl));
2814
3238
  this.cache.set(cacheKey, [
2815
3239
  provingKey.toBytes(),
@@ -2849,7 +3273,7 @@ class AleoKeyProvider {
2849
3273
  return ProvingKey.fromBytes(value[0]);
2850
3274
  }
2851
3275
  else {
2852
- console.debug("Fetching proving keys from url " + proverUrl);
3276
+ logger.debug("Fetching proving keys from url " + proverUrl);
2853
3277
  const provingKey = (ProvingKey.fromBytes(await this.fetchBytes(proverUrl)));
2854
3278
  return provingKey;
2855
3279
  }
@@ -3027,167 +3451,17 @@ class AleoKeyProvider {
3027
3451
  }
3028
3452
  catch (e) {
3029
3453
  /// If that fails, try to fetch the verifying key from the network as bytes
3030
- try {
3031
- return (VerifyingKey.fromBytes(await this.fetchBytes(verifierUri)));
3032
- }
3033
- catch (inner) {
3034
- throw new Error("Invalid verifying key. Error: " + inner.message);
3035
- }
3036
- }
3037
- }
3038
- }
3039
- unBondPublicKeys() {
3040
- return this.fetchCreditsKeys(CREDITS_PROGRAM_KEYS.unbond_public);
3041
- }
3042
- }
3043
-
3044
- /**
3045
- * Error thrown when there is a mismatch between expected and actual key metadata.
3046
- * This can occur during verification of either the checksum or size of a key.
3047
- *
3048
- * @extends Error
3049
- */
3050
- class KeyVerificationError extends Error {
3051
- locator;
3052
- field;
3053
- expected;
3054
- actual;
3055
- /**
3056
- * Creates a new KeyVerificationError instance (error.name is "ChecksumMismatchError").
3057
- *
3058
- * @param {string} locator - The key locator where the mismatch occurred.
3059
- * @param {"checksum" | "size"} field - The field that failed verification (either "checksum" or "size").
3060
- * @param {string} expected - The expected value of the field.
3061
- * @param {string} actual - The actual value encountered.
3062
- */
3063
- constructor(locator, field, expected, actual) {
3064
- super(`Key verification ${locator} ${field} mismatch: expected ${expected}, got ${actual}`);
3065
- this.locator = locator;
3066
- this.field = field;
3067
- this.expected = expected;
3068
- this.actual = actual;
3069
- this.name = "ChecksumMismatchError";
3070
- Object.setPrototypeOf(this, KeyVerificationError.prototype);
3071
- }
3072
- }
3073
- /**
3074
- * Computes the SHA-256 checksum of a given set of bytes.
3075
- *
3076
- * @param {Uint8Array} bytes - The bytes to compute the checksum of.
3077
- */
3078
- async function sha256Hex(bytes) {
3079
- const hash = await crypto.subtle.digest("SHA-256", bytes);
3080
- return Array.from(new Uint8Array(hash))
3081
- .map((b) => b.toString(16).padStart(2, "0"))
3082
- .join("");
3083
- }
3084
-
3085
- /**
3086
- * In-memory implementation of KeyVerifier that stores and verifies key fingerprints.
3087
- * Provides functionality to compute and verify cryptographic checksums of keys, storing them
3088
- * in memory for subsequent verification. This implementation is primarily used for testing
3089
- * and development purposes where persistence is not required.
3090
- *
3091
- * Key features:
3092
- * - Computes SHA-256 checksums and sizes for key bytes.
3093
- * - Stores key fingerprints in memory using string locators.
3094
- * - Verifies key bytes against stored or provided fingerprints.
3095
- *
3096
- * @implements {KeyVerifier}
3097
- */
3098
- class MemKeyVerifier {
3099
- keyStore = {};
3100
- /**
3101
- * Computes and optionally stores key metadata. If a keyFingerprint is provided, this function will verify the computed checksum against it before storing it.
3102
- *
3103
- * @param {KeyMetadata} keyMetadata - Object containing key bytes and optional verification data.
3104
- * @throws {KeyVerificationError} When provided keyFingerprint doesn't match computed values.
3105
- * @returns {Promise<KeyFingerprint>} Computed key metadata.
3106
- */
3107
- async computeKeyMetadata(keyMetadata) {
3108
- // Compute the metadata from the key bytes
3109
- const computedFingerprint = {
3110
- checksum: await sha256Hex(keyMetadata.keyBytes),
3111
- size: keyMetadata.keyBytes.length
3112
- };
3113
- // If a KeyFingerprint is provided, verify it matches computed values.
3114
- if (keyMetadata.fingerprint) {
3115
- if (keyMetadata.fingerprint.size !== computedFingerprint.size) {
3116
- throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "size", String(keyMetadata.fingerprint.size), String(computedFingerprint.size));
3117
- }
3118
- if (keyMetadata.fingerprint.checksum !== computedFingerprint.checksum) {
3119
- throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "checksum", keyMetadata.fingerprint.checksum, computedFingerprint.checksum);
3120
- }
3121
- }
3122
- // If locator is provided, store only the fingerprint
3123
- if (keyMetadata.locator) {
3124
- this.keyStore[keyMetadata.locator] = computedFingerprint;
3125
- }
3126
- return computedFingerprint;
3127
- }
3128
- /**
3129
- * Verifies key bytes against stored or provided metadata. Follows a priority verification scheme:
3130
- * 1. If KeyFingerprint is provided in the metadata, this method verifies against that first.
3131
- * 2. If a locator is provided, attempts to verify against stored fingerprint.
3132
- * 3. If neither is available, throws an error.
3133
- *
3134
- * @param {KeyMetadata} keyMetadata - Object containing the key bytes and optional verification metadata.
3135
- * @throws {Error} When neither fingerprint nor valid locator is provided for verification.
3136
- * @throws {KeyVerificationError} When size or checksum verification fails.
3137
- * @returns {Promise<void>} Promise that resolves when verification succeeds.
3138
- */
3139
- async verifyKeyBytes(keyMetadata) {
3140
- if (!keyMetadata.keyBytes) {
3141
- throw new Error("Key bytes must be provided for verification.");
3142
- }
3143
- // Compute the fingerprint for the provided bytes.
3144
- const computedFingerprint = await this.computeKeyMetadata({
3145
- keyBytes: keyMetadata.keyBytes
3146
- });
3147
- // Determine which fingerprint to verify against.
3148
- let fingerprintToVerify;
3149
- if (keyMetadata.fingerprint) {
3150
- // If a key fingerprint is provided, use it.
3151
- fingerprintToVerify = keyMetadata.fingerprint;
3152
- }
3153
- else if (keyMetadata.locator) {
3154
- // Otherwise try to get stored fingerprint by locator.
3155
- fingerprintToVerify = this.keyStore[keyMetadata.locator];
3156
- }
3157
- if (!fingerprintToVerify) {
3158
- throw new Error("Either fingerprint or a valid locator must be provided for verification.");
3159
- }
3160
- // Verify the key size.
3161
- if (fingerprintToVerify.size !== computedFingerprint.size) {
3162
- throw new KeyVerificationError(keyMetadata.locator || "", "size", String(fingerprintToVerify.size), String(computedFingerprint.size));
3163
- }
3164
- // Verify the key checksum.
3165
- if (fingerprintToVerify.checksum !== computedFingerprint.checksum) {
3166
- throw new KeyVerificationError(keyMetadata.locator || "", "checksum", fingerprintToVerify.checksum, computedFingerprint.checksum);
3454
+ try {
3455
+ return (VerifyingKey.fromBytes(await this.fetchBytes(verifierUri)));
3456
+ }
3457
+ catch (inner) {
3458
+ throw new Error("Invalid verifying key. Error: " + inner.message);
3459
+ }
3460
+ }
3167
3461
  }
3168
3462
  }
3169
- }
3170
-
3171
- /**
3172
- * Error thrown when a key locator is invalid for filesystem use.
3173
- * Used to prevent path traversal and other filesystem injection when deriving paths from locators.
3174
- *
3175
- * @extends Error
3176
- */
3177
- class InvalidLocatorError extends Error {
3178
- locator;
3179
- reason;
3180
- /**
3181
- * @param message - Human-readable description of the validation failure.
3182
- * @param locator - The invalid locator string that failed validation.
3183
- * @param reason - Machine-readable reason code for the failure.
3184
- */
3185
- constructor(message, locator, reason) {
3186
- super(message);
3187
- this.locator = locator;
3188
- this.reason = reason;
3189
- this.name = "InvalidLocatorError";
3190
- Object.setPrototypeOf(this, InvalidLocatorError.prototype);
3463
+ unBondPublicKeys() {
3464
+ return this.fetchCreditsKeys(CREDITS_PROGRAM_KEYS.unbond_public);
3191
3465
  }
3192
3466
  }
3193
3467
 
@@ -3944,12 +4218,12 @@ class NetworkRecordProvider {
3944
4218
  records = await this.findCreditsRecords([microcredits], searchParameters);
3945
4219
  }
3946
4220
  catch (e) {
3947
- console.log("No records found with error:", e);
4221
+ logger.log("No records found with error:", e);
3948
4222
  }
3949
4223
  if (records && records.length > 0) {
3950
4224
  return records[0];
3951
4225
  }
3952
- console.error("Record not found with error:", records);
4226
+ logger.error("Record not found with error:", records);
3953
4227
  throw new Error("Record not found");
3954
4228
  }
3955
4229
  /**
@@ -3961,12 +4235,12 @@ class NetworkRecordProvider {
3961
4235
  records = await this.findRecords(searchParameters);
3962
4236
  }
3963
4237
  catch (e) {
3964
- console.log("No records found with error:", e);
4238
+ logger.log("No records found with error:", e);
3965
4239
  }
3966
4240
  if (records && records.length > 0) {
3967
4241
  return records[0];
3968
4242
  }
3969
- console.error("Record not found with error:", records);
4243
+ logger.error("Record not found with error:", records);
3970
4244
  throw new Error("Record not found");
3971
4245
  }
3972
4246
  /**
@@ -4061,7 +4335,7 @@ class BlockHeightSearch {
4061
4335
  * const recordScanner = new RecordScanner({ url: "https://record-scanner.aleo.org" });
4062
4336
  * recordScanner.setAccount(account);
4063
4337
  * recordScanner.setApiKey("example-api-key");
4064
- * const result = await recordScanner.register(viewKey, 0);
4338
+ * const result = await recordScanner.registerEncrypted(viewKey, 0);
4065
4339
  * if (result.ok) { const uuid = result.data.uuid; }
4066
4340
  *
4067
4341
  * const filter = {
@@ -4316,40 +4590,6 @@ class RecordScanner {
4316
4590
  }
4317
4591
  throw err;
4318
4592
  }
4319
- /**
4320
- * Register the account with the record scanning service (unencrypted POST /register). Does not throw if a valid error response from the record scanner is received; returns a result object instead.
4321
- *
4322
- * @param {ViewKey} viewKey The view key to register.
4323
- * @param {number} startBlock The block height to start scanning from.
4324
- * @returns {Promise<RegisterResult>} `{ ok: true, data }` on success, or `{ ok: false, status, error }` on failure.
4325
- */
4326
- async register(viewKey, startBlock) {
4327
- try {
4328
- const request = {
4329
- view_key: viewKey.to_string(),
4330
- start: startBlock,
4331
- };
4332
- const response = await this.request(new Request(`${this.url}/register`, {
4333
- method: "POST",
4334
- headers: { "Content-Type": "application/json" },
4335
- body: JSON.stringify(request),
4336
- }));
4337
- const data = await response.json();
4338
- // If the uuid is not set, set it on the scanner.
4339
- if (!this.uuid) {
4340
- this.uuid = data.uuid;
4341
- }
4342
- // Add the view key to the local scanner's view keys if configured to do so.
4343
- if (this.cacheViewKeysOnRegister) {
4344
- this.addViewKey(viewKey);
4345
- }
4346
- return { ok: true, data };
4347
- }
4348
- catch (err) {
4349
- console.error(`Failed to register view key: ${err}`);
4350
- return this.handleRequestError(err);
4351
- }
4352
- }
4353
4593
  /**
4354
4594
  * Fetches an ephemeral public key from the record scanning service for use with registerEncrypted.
4355
4595
  * Follows the same pattern as the delegated proving service /pubkey endpoint.
@@ -4591,7 +4831,7 @@ class RecordScanner {
4591
4831
  throw new Error("Record not found");
4592
4832
  }
4593
4833
  catch (error) {
4594
- console.error(`Failed to find record: ${error}`);
4834
+ logger.error(`Failed to find record: ${error}`);
4595
4835
  throw error;
4596
4836
  }
4597
4837
  }
@@ -4752,7 +4992,7 @@ class RecordScanner {
4752
4992
  return record;
4753
4993
  }
4754
4994
  catch (error) {
4755
- console.error(`Failed to find credits record: ${error}`);
4995
+ logger.error(`Failed to find credits record: ${error}`);
4756
4996
  throw error;
4757
4997
  }
4758
4998
  }
@@ -4801,7 +5041,7 @@ class RecordScanner {
4801
5041
  });
4802
5042
  }
4803
5043
  catch (error) {
4804
- console.error(`Failed to find credits records: ${error}`);
5044
+ logger.error(`Failed to find credits records: ${error}`);
4805
5045
  throw error;
4806
5046
  }
4807
5047
  }
@@ -4839,7 +5079,7 @@ class RecordScanner {
4839
5079
  return response;
4840
5080
  }
4841
5081
  catch (error) {
4842
- console.error(`Failed to make request to ${req.url}: ${error}`);
5082
+ logger.error(`Failed to make request to ${req.url}: ${error}`);
4843
5083
  throw error;
4844
5084
  }
4845
5085
  }
@@ -5196,17 +5436,19 @@ class ProgramManager {
5196
5436
  networkClient;
5197
5437
  recordProvider;
5198
5438
  inclusionKeysLoaded = false;
5439
+ _keyStore;
5199
5440
  /** Create a new instance of the ProgramManager
5200
5441
  *
5201
5442
  * @param { string | undefined } host A host uri running the official Aleo API
5202
5443
  * @param { FunctionKeyProvider | undefined } keyProvider A key provider that implements {@link FunctionKeyProvider} interface
5203
5444
  * @param { RecordProvider | undefined } recordProvider A record provider that implements {@link RecordProvider} interface
5204
5445
  */
5205
- constructor(host, keyProvider, recordProvider, networkClientOptions) {
5446
+ constructor(host, keyProvider, recordProvider, networkClientOptions, keyStore) {
5206
5447
  this.host = host ? host : "https://api.provable.com/v2";
5207
5448
  this.networkClient = new AleoNetworkClient(this.host, networkClientOptions);
5208
5449
  this.keyProvider = keyProvider ? keyProvider : new AleoKeyProvider({ transport: networkClientOptions?.transport });
5209
5450
  this.recordProvider = recordProvider;
5451
+ this._keyStore = keyStore;
5210
5452
  }
5211
5453
  /**
5212
5454
  * Pre-load the inclusion prover for offline execution. Required when the
@@ -5284,6 +5526,330 @@ class ProgramManager {
5284
5526
  setRecordProvider(recordProvider) {
5285
5527
  this.recordProvider = recordProvider;
5286
5528
  }
5529
+ /**
5530
+ * Set the key store for automatic key caching across executions.
5531
+ *
5532
+ * @param {KeyStore} keyStore
5533
+ */
5534
+ setKeyStore(keyStore) {
5535
+ this._keyStore = keyStore;
5536
+ }
5537
+ /**
5538
+ * Build a ProgramImportsBuilder from a program and its imports.
5539
+ * Fetches imports from the network if not provided, resolves transitive
5540
+ * dependencies, and optionally pre-loads cached keys from the KeyStore.
5541
+ *
5542
+ * @param loadKeys When true (default), loads cached proving/verifying keys
5543
+ * from the KeyStore into the builder. Set to false for authorization and
5544
+ * proving request paths where keys are not synthesized.
5545
+ */
5546
+ async buildProgramImports(program, imports, loadKeys, entryFunction) {
5547
+ const builder = new ProgramImports();
5548
+ const programSource = typeof program === "string" ? program : program.toString();
5549
+ const programObj = Program.fromString(programSource);
5550
+ const importNames = programObj.getImports();
5551
+ if (importNames.length === 0 && (!imports || Object.keys(imports).length === 0)) {
5552
+ return { builder, importEditions: new Map() };
5553
+ }
5554
+ let resolvedImports = {};
5555
+ if (imports) {
5556
+ resolvedImports = { ...imports };
5557
+ }
5558
+ if (importNames.length > 0 && !imports) {
5559
+ try {
5560
+ resolvedImports = await this.networkClient.getProgramImports(programSource);
5561
+ }
5562
+ catch (e) {
5563
+ console.warn(`Failed to resolve program imports from network: ${e}.`);
5564
+ }
5565
+ }
5566
+ // Build a map of which functions each import actually calls,
5567
+ // so we only load keys for functions in the call chain.
5568
+ const calledFunctions = ProgramManager.callGraphToMap(programObj.getCallGraph(entryFunction));
5569
+ // Phase 1: Collect all programs via BFS, discovering transitive imports.
5570
+ // The initial getProgramImports call above already resolves the full
5571
+ // transitive closure via recursive DFS. The BFS loop here only needs
5572
+ // to handle user-provided imports whose transitive deps may not yet be
5573
+ // known. For each unknown import we issue a single getProgram() call
5574
+ // (not a full recursive getProgramImports) and fetch siblings in
5575
+ // parallel to minimize round-trips.
5576
+ const collected = new Map();
5577
+ const collectQueue = Object.entries(resolvedImports).map(([name, src]) => [name, typeof src === "string" ? src : src.toString()]);
5578
+ while (collectQueue.length > 0) {
5579
+ const [name, source] = collectQueue.shift();
5580
+ if (collected.has(name))
5581
+ continue;
5582
+ collected.set(name, source);
5583
+ // Discover transitive imports for collection (call-graph tracing
5584
+ // happens in a separate pass after topological sorting).
5585
+ try {
5586
+ const subImports = ProgramManager.getImportNames(source);
5587
+ if (subImports.length > 0) {
5588
+ // Fetch only unknown transitive imports — the source for
5589
+ // programs already in collected/resolvedImports is known, so
5590
+ // we skip them. Fetches within a BFS level run in parallel.
5591
+ const unknownImports = subImports.filter((id) => !collected.has(id) && !(id in resolvedImports));
5592
+ if (unknownImports.length > 0) {
5593
+ const fetched = await Promise.all(unknownImports.map(async (id) => {
5594
+ try {
5595
+ const src = await this.networkClient.getProgram(id);
5596
+ return [id, src];
5597
+ }
5598
+ catch {
5599
+ return null;
5600
+ }
5601
+ }));
5602
+ for (const entry of fetched) {
5603
+ if (entry && !collected.has(entry[0])) {
5604
+ collectQueue.push(entry);
5605
+ }
5606
+ }
5607
+ }
5608
+ }
5609
+ }
5610
+ catch (e) {
5611
+ console.warn(`Failed to resolve transitive imports for ${name}: ${e}`);
5612
+ }
5613
+ }
5614
+ // Phase 2: Add programs in topological order (leaves first).
5615
+ // A simple DFS post-order ensures dependencies are added before
5616
+ // dependents, regardless of the order collected entries arrived.
5617
+ const sorted = [];
5618
+ const visited = new Set();
5619
+ const visit = (name) => {
5620
+ if (visited.has(name))
5621
+ return;
5622
+ visited.add(name);
5623
+ const source = collected.get(name);
5624
+ if (!source)
5625
+ return;
5626
+ for (const dep of ProgramManager.getImportNames(source)) {
5627
+ if (collected.has(dep))
5628
+ visit(dep);
5629
+ }
5630
+ sorted.push([name, source]);
5631
+ };
5632
+ for (const [name] of collected)
5633
+ visit(name);
5634
+ // When scoped, trace call graphs in reverse topological order
5635
+ // (dependents before leaves) so parent calls propagate before
5636
+ // children are traced. sorted is leaves-first, so reverse it.
5637
+ if (entryFunction) {
5638
+ for (let i = sorted.length - 1; i >= 0; i--) {
5639
+ const [name, source] = sorted[i];
5640
+ const functionsNeeded = calledFunctions.get(name);
5641
+ if (!functionsNeeded)
5642
+ continue;
5643
+ try {
5644
+ const importProgram = Program.fromString(source);
5645
+ for (const fn of functionsNeeded) {
5646
+ const reachable = ProgramManager.callGraphToMap(importProgram.getCallGraph(fn));
5647
+ for (const [p, fns] of reachable) {
5648
+ if (!calledFunctions.has(p)) {
5649
+ calledFunctions.set(p, new Set(fns));
5650
+ }
5651
+ else {
5652
+ for (const f of fns)
5653
+ calledFunctions.get(p).add(f);
5654
+ }
5655
+ }
5656
+ }
5657
+ }
5658
+ catch (e) {
5659
+ console.warn(`Failed to trace call graph for ${name}: ${e}`);
5660
+ }
5661
+ }
5662
+ }
5663
+ // Resolve editions for all imports in parallel (each import may have
5664
+ // a different edition than the top-level program).
5665
+ const importEditions = new Map();
5666
+ await Promise.all(sorted.map(async ([name]) => {
5667
+ importEditions.set(name, await this.resolveEditionAndAmendment(name));
5668
+ }));
5669
+ for (const [name, source] of sorted) {
5670
+ if (builder.contains(name))
5671
+ continue;
5672
+ const { edition: importEdition, amendment: importAmendment } = importEditions.get(name);
5673
+ try {
5674
+ builder.addProgram(name, source, importEdition);
5675
+ }
5676
+ catch (e) {
5677
+ console.warn(`Failed to add import ${name} to builder: ${e}`);
5678
+ continue;
5679
+ }
5680
+ if (loadKeys) {
5681
+ const calledFns = calledFunctions.get(name);
5682
+ await this.loadKeysFromStore(builder, name, calledFns ? [...calledFns] : [], importEdition, importAmendment);
5683
+ }
5684
+ }
5685
+ return { builder, importEditions };
5686
+ }
5687
+ /**
5688
+ * Extract `import program_name.aleo;` names from program source via regex.
5689
+ * Avoids a WASM round-trip compared to Program.fromString + getImports.
5690
+ */
5691
+ static getImportNames(programSource) {
5692
+ const result = [];
5693
+ const importPattern = /^\s*import\s+([a-zA-Z_][a-zA-Z0-9_]*\.aleo)\s*;/gm;
5694
+ let match;
5695
+ while ((match = importPattern.exec(programSource)) !== null) {
5696
+ result.push(match[1]);
5697
+ }
5698
+ return result;
5699
+ }
5700
+ /**
5701
+ * Convert the JS object returned by Program.getCallGraph() into a
5702
+ * Map<string, Set<string>> for use in buildProgramImports.
5703
+ */
5704
+ static callGraphToMap(callGraph) {
5705
+ const result = new Map();
5706
+ for (const [programName, functions] of Object.entries(callGraph)) {
5707
+ result.set(programName, new Set(functions));
5708
+ }
5709
+ return result;
5710
+ }
5711
+ /**
5712
+ * Resolve the active KeyStore, preferring the directly-set _keyStore
5713
+ * over the KeyProvider's keyStore().
5714
+ */
5715
+ async resolveKeyStore() {
5716
+ if (this._keyStore)
5717
+ return this._keyStore;
5718
+ try {
5719
+ return await this.keyProvider?.keyStore?.();
5720
+ }
5721
+ catch {
5722
+ return undefined;
5723
+ }
5724
+ }
5725
+ /**
5726
+ * Resolve the edition and amendment count for a program from the network.
5727
+ * Returns `{ edition, amendment }` or falls back to `{ edition: 1, amendment: 0 }`.
5728
+ */
5729
+ async resolveEditionAndAmendment(programName, fallbackEdition) {
5730
+ try {
5731
+ const info = await this.networkClient.getProgramAmendmentCount(programName);
5732
+ return { edition: info.edition, amendment: info.amendment_count };
5733
+ }
5734
+ catch (e) {
5735
+ console.warn(`Error finding edition/amendment for ${programName}. Network response: '${e.message}'. Defaulting to edition ${fallbackEdition ?? 1}, amendment 0.`);
5736
+ return { edition: fallbackEdition ?? 1, amendment: 0 };
5737
+ }
5738
+ }
5739
+ /**
5740
+ * Load cached proving/verifying keys from the KeyStore into a ProgramImportsBuilder.
5741
+ * Only loads keys for the specified functions — returns immediately if
5742
+ * functionNames is empty or undefined.
5743
+ * Resolves edition and amendment from the network for accurate key locator
5744
+ * construction when not explicitly provided.
5745
+ */
5746
+ async loadKeysFromStore(builder, programName, functionNames, edition, amendment) {
5747
+ if (!functionNames || functionNames.length === 0)
5748
+ return;
5749
+ const keyStore = await this.resolveKeyStore();
5750
+ if (!keyStore)
5751
+ return;
5752
+ // Resolve edition and amendment from the network if not fully provided.
5753
+ if (edition === undefined || amendment === undefined) {
5754
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
5755
+ edition = edition ?? resolved.edition;
5756
+ amendment = amendment ?? resolved.amendment;
5757
+ }
5758
+ for (const fnName of functionNames) {
5759
+ const pkLocator = provingKeyLocator(programName, fnName, edition, amendment);
5760
+ const vkLocator = verifyingKeyLocator(programName, fnName, edition, amendment);
5761
+ try {
5762
+ const pk = await keyStore.getProvingKey(pkLocator);
5763
+ if (pk)
5764
+ builder.addProvingKey(programName, fnName, pk);
5765
+ const vk = await keyStore.getVerifyingKey(vkLocator);
5766
+ if (vk)
5767
+ builder.addVerifyingKey(programName, fnName, vk);
5768
+ }
5769
+ catch (e) {
5770
+ console.debug(`Failed to load keys for ${programName}/${fnName}: ${e}`);
5771
+ }
5772
+ }
5773
+ }
5774
+ /**
5775
+ * Persist newly synthesized keys from the returned ProgramImportsBuilder
5776
+ * into the KeyStore. Only writes keys that are not already in the store,
5777
+ * avoiding unnecessary writes of large proving keys.
5778
+ * Fetches each program's current edition and amendment count from the network
5779
+ * for accurate key locator construction.
5780
+ */
5781
+ async persistExtractedKeys(builder, importEditions) {
5782
+ const keyStore = await this.resolveKeyStore();
5783
+ if (!keyStore)
5784
+ return;
5785
+ const programNames = Array.from(builder.programNames());
5786
+ // Reuse editions resolved during buildProgramImports when available,
5787
+ // falling back to parallel network resolution for any missing programs.
5788
+ const editionMap = importEditions ?? new Map();
5789
+ const missing = programNames.filter((name) => !editionMap.has(name));
5790
+ if (missing.length > 0) {
5791
+ await Promise.all(missing.map(async (name) => {
5792
+ editionMap.set(name, await this.resolveEditionAndAmendment(name));
5793
+ }));
5794
+ }
5795
+ for (const programName of programNames) {
5796
+ const { edition, amendment } = editionMap.get(programName);
5797
+ let fns;
5798
+ try {
5799
+ fns = Array.from(builder.functionKeysAvailable(programName));
5800
+ }
5801
+ catch (e) {
5802
+ console.debug(`Failed to query keys for ${programName}: ${e}`);
5803
+ continue;
5804
+ }
5805
+ for (const fnName of fns) {
5806
+ try {
5807
+ const pkLocator = provingKeyLocator(programName, fnName, edition, amendment);
5808
+ const vkLocator = verifyingKeyLocator(programName, fnName, edition, amendment);
5809
+ // Skip keys already in the store.
5810
+ if (await keyStore.has(pkLocator) && await keyStore.has(vkLocator)) {
5811
+ continue;
5812
+ }
5813
+ const pk = builder.getProvingKey(programName, fnName);
5814
+ const vk = builder.getVerifyingKey(programName, fnName);
5815
+ if (pk && vk) {
5816
+ await keyStore.setKeys(pkLocator, vkLocator, [pk, vk]);
5817
+ }
5818
+ }
5819
+ catch (e) {
5820
+ console.debug(`Failed to persist key for ${programName}/${fnName}: ${e}`);
5821
+ }
5822
+ }
5823
+ }
5824
+ }
5825
+ /**
5826
+ * Resolve top-level function keys, checking the KeyStore first and
5827
+ * falling back to the KeyProvider.
5828
+ */
5829
+ async resolveTopLevelKeys(programName, functionName, keySearchParams, edition, amendment) {
5830
+ const keyStore = await this.resolveKeyStore();
5831
+ if (keyStore) {
5832
+ try {
5833
+ const pkLocator = provingKeyLocator(programName, functionName, edition, amendment);
5834
+ const vkLocator = verifyingKeyLocator(programName, functionName, edition, amendment);
5835
+ if (await keyStore.has(pkLocator) && await keyStore.has(vkLocator)) {
5836
+ const pk = await keyStore.getProvingKey(pkLocator);
5837
+ const vk = await keyStore.getVerifyingKey(vkLocator);
5838
+ if (pk && vk)
5839
+ return [pk, vk];
5840
+ }
5841
+ }
5842
+ catch {
5843
+ // Fall through to KeyProvider
5844
+ }
5845
+ }
5846
+ try {
5847
+ return (await this.keyProvider.functionKeys(keySearchParams));
5848
+ }
5849
+ catch {
5850
+ return undefined;
5851
+ }
5852
+ }
5287
5853
  /**
5288
5854
  * Set a header in the `AleoNetworkClient`s header map
5289
5855
  *
@@ -5337,7 +5903,7 @@ class ProgramManager {
5337
5903
  return;
5338
5904
  }
5339
5905
  catch {
5340
- console.log("Setting the inclusion prover requires either a key provider to be configured for the ProgramManager OR to pass the inclusion prover directly");
5906
+ logger.log("Setting the inclusion prover requires either a key provider to be configured for the ProgramManager OR to pass the inclusion prover directly");
5341
5907
  }
5342
5908
  }
5343
5909
  /**
@@ -5412,7 +5978,7 @@ class ProgramManager {
5412
5978
  }
5413
5979
  catch (e) {
5414
5980
  // Program does not exist on the network, deployment can proceed
5415
- console.log(`Program ${programObject.id()} does not exist on the network, deploying...`);
5981
+ logger.log(`Program ${programObject.id()} does not exist on the network, deploying...`);
5416
5982
  }
5417
5983
  if (typeof programSource === "string") {
5418
5984
  throw Error(`Program ${programObject.id()} already exists on the network, please rename your program`);
@@ -5436,7 +6002,7 @@ class ProgramManager {
5436
6002
  let fee = priorityFee;
5437
6003
  // If a private fee is specified, but no fee record is provided, estimate the fee and find a matching record.
5438
6004
  if (!feeRecord) {
5439
- console.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
6005
+ logger.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
5440
6006
  const programString = programObject.toString();
5441
6007
  const imports = await this.networkClient.getProgramImports(programString);
5442
6008
  const baseFee = Number(ProgramManager$1.estimateDeploymentFee(programString, imports));
@@ -5527,7 +6093,7 @@ class ProgramManager {
5527
6093
  }
5528
6094
  catch (e) {
5529
6095
  // Program does not exist on the network, deployment can proceed
5530
- console.log(`Program ${programObject.id()} does not exist on the network...`);
6096
+ logger.log(`Program ${programObject.id()} does not exist on the network...`);
5531
6097
  }
5532
6098
  }
5533
6099
  catch (e) {
@@ -5548,7 +6114,7 @@ class ProgramManager {
5548
6114
  let fee = priorityFee;
5549
6115
  // If a private fee is specified, but no fee record is provided, estimate the fee and find a matching record.
5550
6116
  if (!feeRecord) {
5551
- console.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
6117
+ logger.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
5552
6118
  const programString = programObject.toString();
5553
6119
  const imports = await this.networkClient.getProgramImports(programString);
5554
6120
  const baseFee = Number(ProgramManager$1.estimateDeploymentFee(programString, imports));
@@ -5718,15 +6284,9 @@ class ProgramManager {
5718
6284
  if (programName === undefined) {
5719
6285
  programName = programObject.id();
5720
6286
  }
5721
- if (edition == undefined) {
5722
- try {
5723
- edition = await this.networkClient.getLatestProgramEdition(programName);
5724
- }
5725
- catch (e) {
5726
- console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
5727
- edition = 0;
5728
- }
5729
- }
6287
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
6288
+ edition = edition ?? resolved.edition;
6289
+ const { amendment } = resolved;
5730
6290
  // Get the private key from the account if it is not provided in the parameters
5731
6291
  let executionPrivateKey = privateKey;
5732
6292
  if (typeof privateKey === "undefined" &&
@@ -5747,23 +6307,19 @@ class ProgramManager {
5747
6307
  logAndThrow(`Error finding fee keys. Key finder response: '${e.message}'. Please ensure your key provider is configured correctly.`);
5748
6308
  }
5749
6309
  const [feeProvingKey, feeVerifyingKey] = feeKeys;
5750
- // If the function proving and verifying keys are not provided, attempt to find them using the key provider
6310
+ // Build the ProgramImportsBuilder for import resolution and key caching.
6311
+ const { builder: programImportsBuilder, importEditions } = await this.buildProgramImports(program, imports, true, functionName);
6312
+ // Include the top-level program's already-resolved edition so
6313
+ // persistExtractedKeys doesn't make a redundant network call for it.
6314
+ importEditions.set(programName, { edition: edition, amendment });
6315
+ // If the function proving and verifying keys are not provided, attempt to find them
5751
6316
  if (!provingKey || !verifyingKey) {
5752
- try {
5753
- [provingKey, verifyingKey] = (await this.keyProvider.functionKeys(keySearchParams));
5754
- }
5755
- catch (e) {
5756
- console.log(`Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`);
5757
- }
5758
- }
5759
- // Resolve the program imports if they exist
5760
- const numberOfImports = programObject.getImports().length;
5761
- if (numberOfImports > 0 && !imports) {
5762
- try {
5763
- imports = (await this.networkClient.getProgramImports(programName));
6317
+ const keys = await this.resolveTopLevelKeys(programName, functionName, keySearchParams, edition, amendment);
6318
+ if (keys) {
6319
+ [provingKey, verifyingKey] = keys;
5764
6320
  }
5765
- catch (e) {
5766
- logAndThrow(`Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`);
6321
+ else {
6322
+ console.log("Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized");
5767
6323
  }
5768
6324
  }
5769
6325
  // Get the fee record from the account if it is not provided in the parameters
@@ -5788,17 +6344,23 @@ class ProgramManager {
5788
6344
  }
5789
6345
  // Auto-convert bare string inputs to field elements where the function expects field type.
5790
6346
  const preparedInputs = this.prepareInputs(program, functionName, inputs);
5791
- // Build the query: user-provided OfflineQuery for truly offline execution,
5792
- // CallbackQuery when a custom transport is configured, or undefined to
5793
- // let WASM use its internal SnapshotQuery with the default fetch.
5794
6347
  const query = offlineQuery
5795
6348
  ? QueryOption.offlineQuery(offlineQuery)
5796
6349
  : this.networkClient.hasCustomTransport
5797
6350
  ? QueryOption.callbackQuery(this.buildCallbackQuery())
5798
6351
  : undefined;
5799
6352
  await this.ensureInclusionKeys(!!offlineQuery);
5800
- // Build an execution transaction
5801
- return await ProgramManager$1.buildExecutionTransaction(executionPrivateKey, program, functionName, preparedInputs, priorityFee, feeRecord, this.host, imports, provingKey, verifyingKey, feeProvingKey, feeVerifyingKey, query, edition);
6353
+ const result = await ProgramManager$1.buildExecutionTransaction(executionPrivateKey, program, functionName, preparedInputs, priorityFee, feeRecord, this.host, undefined, // imports (legacy Object path — builder handles resolution)
6354
+ provingKey, verifyingKey, feeProvingKey, feeVerifyingKey, query, edition, programImportsBuilder?.clone());
6355
+ // The clone passed to WASM shares state with the original via
6356
+ // Rc<RefCell<>> — synthesized keys are already visible.
6357
+ try {
6358
+ await this.persistExtractedKeys(programImportsBuilder, importEditions);
6359
+ }
6360
+ catch (e) {
6361
+ console.debug(`Failed to persist extracted keys: ${e}`);
6362
+ }
6363
+ return result;
5802
6364
  }
5803
6365
  /**
5804
6366
  * Builds an execution transaction for submission to the Aleo network from an Authorization and Fee Authorization.
@@ -5876,6 +6438,10 @@ class ProgramManager {
5876
6438
  const feeAuthorization = options.feeAuthorization;
5877
6439
  const keySearchParams = options.keySearchParams;
5878
6440
  const offlineQuery = options.offlineQuery;
6441
+ let edition = options.edition;
6442
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
6443
+ edition = edition ?? resolved.edition;
6444
+ const { amendment } = resolved;
5879
6445
  let provingKey = options.provingKey;
5880
6446
  let verifyingKey = options.verifyingKey;
5881
6447
  let program = options.program;
@@ -5904,24 +6470,17 @@ class ProgramManager {
5904
6470
  logAndThrow(`Error finding fee keys. Key finder response: '${e.message}'. Please ensure your key provider is configured correctly.`);
5905
6471
  }
5906
6472
  const [feeProvingKey, feeVerifyingKey] = feeKeys;
5907
- // If the function proving and verifying keys are not provided, attempt to find them using the key provider.
6473
+ // Build the ProgramImportsBuilder for import resolution and key caching.
6474
+ const { builder: programImportsBuilder, importEditions } = await this.buildProgramImports(program, imports, true, authorization.functionName());
6475
+ importEditions.set(programName, { edition: edition, amendment });
6476
+ // If the function proving and verifying keys are not provided, attempt to find them.
5908
6477
  if (!provingKey || !verifyingKey) {
5909
- try {
5910
- [provingKey, verifyingKey] = (await this.keyProvider.functionKeys(keySearchParams));
5911
- }
5912
- catch (e) {
5913
- console.log(`Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`);
5914
- }
5915
- }
5916
- // Resolve the program imports if they exist.
5917
- console.log("Resolving program imports");
5918
- const numberOfImports = Program.fromString(program).getImports().length;
5919
- if (numberOfImports > 0 && !imports) {
5920
- try {
5921
- imports = (await this.networkClient.getProgramImports(programName));
6478
+ const keys = await this.resolveTopLevelKeys(programName, authorization.functionName(), keySearchParams, edition, amendment);
6479
+ if (keys) {
6480
+ [provingKey, verifyingKey] = keys;
5922
6481
  }
5923
- catch (e) {
5924
- logAndThrow(`Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`);
6482
+ else {
6483
+ console.log("Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized");
5925
6484
  }
5926
6485
  }
5927
6486
  const query = offlineQuery
@@ -5930,9 +6489,18 @@ class ProgramManager {
5930
6489
  ? QueryOption.callbackQuery(this.buildCallbackQuery())
5931
6490
  : undefined;
5932
6491
  await this.ensureInclusionKeys(!!offlineQuery);
5933
- // Build an execution transaction from the authorization.
5934
- console.log("Executing authorizations");
5935
- return await ProgramManager$1.executeAuthorization(authorization, feeAuthorization, program, provingKey, verifyingKey, feeProvingKey, feeVerifyingKey, imports, this.host, query);
6492
+ // Build an execution transaction from the authorization. The
6493
+ // ProgramImportsBuilder is consumed by value (ownership transfers to WASM)
6494
+ // and returned enriched with any keys synthesized during execution.
6495
+ const result = await ProgramManager$1.executeAuthorization(authorization, feeAuthorization, program, provingKey, verifyingKey, feeProvingKey, feeVerifyingKey, undefined, // imports (legacy Object path — builder handles resolution)
6496
+ this.host, query, programImportsBuilder?.clone(), edition);
6497
+ try {
6498
+ await this.persistExtractedKeys(programImportsBuilder, importEditions);
6499
+ }
6500
+ catch (e) {
6501
+ console.debug(`Failed to persist extracted keys: ${e}`);
6502
+ }
6503
+ return result;
5936
6504
  }
5937
6505
  /**
5938
6506
  * Builds a SnarkVM `Authorization` for a specific function.
@@ -5995,29 +6563,17 @@ class ProgramManager {
5995
6563
  if (typeof executionPrivateKey === "undefined") {
5996
6564
  throw "No private key provided and no private key set in the ProgramManager";
5997
6565
  }
5998
- if (edition == undefined) {
5999
- try {
6000
- edition = await this.networkClient.getLatestProgramEdition(programName);
6001
- }
6002
- catch (e) {
6003
- console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
6004
- edition = 0;
6005
- }
6006
- }
6007
- // Resolve the program imports if they exist.
6008
- const numberOfImports = Program.fromString(program).getImports().length;
6009
- if (numberOfImports > 0 && !imports) {
6010
- try {
6011
- imports = (await this.networkClient.getProgramImports(programName));
6012
- }
6013
- catch (e) {
6014
- logAndThrow(`Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`);
6015
- }
6016
- }
6566
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
6567
+ edition = edition ?? resolved.edition;
6017
6568
  // Auto-convert bare string inputs to field elements where the function expects field type.
6018
6569
  const preparedInputs = this.prepareInputs(program, functionName, inputs);
6570
+ // Build ProgramImportsBuilder for import resolution (no key loading —
6571
+ // authorizations don't synthesize keys).
6572
+ const { builder } = await this.buildProgramImports(program, imports, false, functionName);
6573
+ const hasImports = !builder.isEmpty();
6019
6574
  // Build and return an `Authorization` for the desired function.
6020
- return await ProgramManager$1.authorize(executionPrivateKey, program, functionName, preparedInputs, imports, edition);
6575
+ const authorization = await ProgramManager$1.authorize(executionPrivateKey, program, functionName, preparedInputs, hasImports ? undefined : imports, edition, hasImports ? builder?.clone() : undefined);
6576
+ return authorization;
6021
6577
  }
6022
6578
  /**
6023
6579
  * Builds a SnarkVM `Authorization` for a specific function without building a circuit first. This should be used when fast authorization generation is needed and the invoker is confident inputs are coorect.
@@ -6080,29 +6636,17 @@ class ProgramManager {
6080
6636
  if (typeof executionPrivateKey === "undefined") {
6081
6637
  throw "No private key provided and no private key set in the ProgramManager";
6082
6638
  }
6083
- // Resolve the program imports if they exist.
6084
- const numberOfImports = Program.fromString(program).getImports().length;
6085
- if (numberOfImports > 0 && !imports) {
6086
- try {
6087
- imports = (await this.networkClient.getProgramImports(programName));
6088
- }
6089
- catch (e) {
6090
- logAndThrow(`Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`);
6091
- }
6092
- }
6093
- if (edition == undefined) {
6094
- try {
6095
- edition = await this.networkClient.getLatestProgramEdition(programName);
6096
- }
6097
- catch (e) {
6098
- console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
6099
- edition = 0;
6100
- }
6101
- }
6639
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
6640
+ edition = edition ?? resolved.edition;
6102
6641
  // Auto-convert bare string inputs to field elements where the function expects field type.
6103
6642
  const preparedInputs = this.prepareInputs(program, functionName, inputs);
6643
+ // Build ProgramImportsBuilder for import resolution (no key loading —
6644
+ // authorizations don't synthesize keys).
6645
+ const { builder } = await this.buildProgramImports(program, imports, false, functionName);
6646
+ const hasImports = !builder.isEmpty();
6104
6647
  // Build and return an `Authorization` for the desired function.
6105
- return await ProgramManager$1.buildAuthorizationUnchecked(executionPrivateKey, program, functionName, preparedInputs, imports, edition);
6648
+ const authorization = await ProgramManager$1.buildAuthorizationUnchecked(executionPrivateKey, program, functionName, preparedInputs, hasImports ? undefined : imports, edition, hasImports ? builder?.clone() : undefined);
6649
+ return authorization;
6106
6650
  }
6107
6651
  /**
6108
6652
  * Builds a `ProvingRequest` for submission to a prover for execution. If building a proving request with an ExecutionRequest, a private key must be explicitly provided.
@@ -6165,15 +6709,8 @@ class ProgramManager {
6165
6709
  if (programName === undefined) {
6166
6710
  programName = Program.fromString(program).id();
6167
6711
  }
6168
- if (edition == undefined) {
6169
- try {
6170
- edition = await this.networkClient.getLatestProgramEdition(programName);
6171
- }
6172
- catch (e) {
6173
- console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
6174
- edition = 0;
6175
- }
6176
- }
6712
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
6713
+ edition = edition ?? resolved.edition;
6177
6714
  // Get the private key from the account if it is not provided in the parameters.
6178
6715
  let executionPrivateKey = privateKey;
6179
6716
  if (typeof privateKey === "undefined" &&
@@ -6211,8 +6748,30 @@ class ProgramManager {
6211
6748
  catch (e) {
6212
6749
  logAndThrow(`Error finding fee record. Record finder response: '${e.message}'. Please ensure you're connected to a valid Aleo network and a record with enough balance exists.`);
6213
6750
  }
6751
+ // Build ProgramImportsBuilder for import resolution (no key loading —
6752
+ // proving requests don't synthesize keys).
6753
+ // Note: imports is kept for the Rust-side fee estimation fallback
6754
+ // (estimate_fee_for_authorization uses the legacy imports Object to avoid
6755
+ // a RefCell double-borrow — see proving_request.rs).
6756
+ const { builder } = await this.buildProgramImports(program, imports, false, functionName);
6757
+ const hasImports = !builder.isEmpty();
6758
+ // Normalize imports to the full merged set from the builder so the
6759
+ // Rust fee estimator sees all transitive imports, not just the
6760
+ // caller's partial set. Use programNames + getProgram to avoid
6761
+ // serializing key bytes (toObject would serialize all proving/
6762
+ // verifying keys, which can be tens of MB).
6763
+ if (hasImports) {
6764
+ const merged = {};
6765
+ for (const name of Array.from(builder.programNames())) {
6766
+ const src = builder.getProgram(name);
6767
+ if (src)
6768
+ merged[name] = src;
6769
+ }
6770
+ imports = merged;
6771
+ }
6214
6772
  if (options.executionRequest instanceof ExecutionRequest) {
6215
- return await ProgramManager$1.buildProvingRequestFromExecutionRequest(options.executionRequest, program, unchecked, broadcast, edition, imports, executionPrivateKey);
6773
+ return await ProgramManager$1.buildProvingRequestFromExecutionRequest(options.executionRequest, program, unchecked, broadcast, edition, imports, // kept for fee estimation in Rust
6774
+ executionPrivateKey, hasImports ? builder?.clone() : undefined);
6216
6775
  }
6217
6776
  else {
6218
6777
  // Ensure the private key exists.
@@ -6226,7 +6785,8 @@ class ProgramManager {
6226
6785
  // Auto-convert bare string inputs to field elements where the function expects field type.
6227
6786
  const preparedInputs = this.prepareInputs(program, functionName, inputs);
6228
6787
  // Build and return the `ProvingRequest`.
6229
- return await ProgramManager$1.buildProvingRequest(executionPrivateKey, program, functionName, preparedInputs, baseFee, priorityFee, feeRecord, imports, broadcast, unchecked, edition, useFeeMaster);
6788
+ return await ProgramManager$1.buildProvingRequest(executionPrivateKey, program, functionName, preparedInputs, baseFee, priorityFee, feeRecord, imports, // kept for fee estimation in Rust
6789
+ broadcast, unchecked, edition, useFeeMaster, hasImports ? builder?.clone() : undefined);
6230
6790
  }
6231
6791
  }
6232
6792
  /**
@@ -6399,6 +6959,10 @@ class ProgramManager {
6399
6959
  * assert(result === ["10u32"]);
6400
6960
  */
6401
6961
  async run(program, function_name, inputs, proveExecution, imports, keySearchParams, provingKey, verifyingKey, privateKey, offlineQuery, edition) {
6962
+ const programName = Program.fromString(program).id();
6963
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
6964
+ edition = edition ?? resolved.edition;
6965
+ const { amendment } = resolved;
6402
6966
  // Get the private key from the account if it is not provided in the parameters
6403
6967
  let executionPrivateKey = privateKey;
6404
6968
  if (typeof privateKey === "undefined" &&
@@ -6408,13 +6972,16 @@ class ProgramManager {
6408
6972
  if (typeof executionPrivateKey === "undefined") {
6409
6973
  throw "No private key provided and no private key set in the ProgramManager";
6410
6974
  }
6411
- // If the function proving and verifying keys are not provided, attempt to find them using the key provider
6975
+ // Build the ProgramImportsBuilder for import resolution and key caching.
6976
+ const { builder, importEditions } = await this.buildProgramImports(program, imports, true, function_name);
6977
+ importEditions.set(programName, { edition: edition, amendment });
6412
6978
  if (!provingKey || !verifyingKey) {
6413
- try {
6414
- [provingKey, verifyingKey] = (await this.keyProvider.functionKeys(keySearchParams));
6979
+ const keys = await this.resolveTopLevelKeys(programName, function_name, keySearchParams, edition, amendment);
6980
+ if (keys) {
6981
+ [provingKey, verifyingKey] = keys;
6415
6982
  }
6416
- catch (e) {
6417
- console.log(`Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`);
6983
+ else {
6984
+ console.log("Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized");
6418
6985
  }
6419
6986
  }
6420
6987
  // Auto-convert bare string inputs to field elements where the function expects field type.
@@ -6425,11 +6992,17 @@ class ProgramManager {
6425
6992
  ? QueryOption.callbackQuery(this.buildCallbackQuery())
6426
6993
  : undefined;
6427
6994
  await this.ensureInclusionKeys(!!offlineQuery);
6428
- // Run the program offline and return the result
6429
- console.log("Running program offline");
6430
- console.log("Proving key: ", provingKey);
6431
- console.log("Verifying key: ", verifyingKey);
6432
- return ProgramManager$1.executeFunctionOffline(executionPrivateKey, program, function_name, preparedInputs, proveExecution, false, imports, provingKey, verifyingKey, this.host, query, edition);
6995
+ const result = await ProgramManager$1.executeFunctionOffline(executionPrivateKey, program, function_name, preparedInputs, proveExecution, false, undefined, // imports (legacy Object path — builder handles resolution)
6996
+ provingKey, verifyingKey, this.host, query, edition, builder?.clone());
6997
+ // The clone passed to WASM shares state with the original via
6998
+ // Rc<RefCell<>> synthesized keys are already visible.
6999
+ try {
7000
+ await this.persistExtractedKeys(builder, importEditions);
7001
+ }
7002
+ catch (e) {
7003
+ console.debug(`Failed to persist extracted keys: ${e}`);
7004
+ }
7005
+ return result;
6433
7006
  }
6434
7007
  /**
6435
7008
  * Join two credits records into a single credits record
@@ -7418,7 +7991,7 @@ class ProgramManager {
7418
7991
  return verifyFunctionExecution(execution, verifyingKey, program, function_id, imports, importedVerifyingKeys, blockHeight);
7419
7992
  }
7420
7993
  catch (e) {
7421
- console.warn(`The execution was not found in the response, cannot verify the execution: ${e}`);
7994
+ logger.warn(`The execution was not found in the response, cannot verify the execution: ${e}`);
7422
7995
  return false;
7423
7996
  }
7424
7997
  }
@@ -7511,12 +8084,8 @@ class ProgramManager {
7511
8084
  throw new Error("Authorization must be provided if estimating fee for Authorization.");
7512
8085
  }
7513
8086
  const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition);
7514
- const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource);
7515
- console.log(JSON.stringify(programImports));
7516
- if (Object.keys(programImports)) {
7517
- return ProgramManager$1.estimateFeeForAuthorization(authorization, programSource, programImports, edition);
7518
- }
7519
- return ProgramManager$1.estimateFeeForAuthorization(authorization, programSource, imports, edition);
8087
+ const programImports = imports ?? await this.networkClient.getProgramImports(programSource);
8088
+ return ProgramManager$1.estimateFeeForAuthorization(authorization, programSource, programImports, edition);
7520
8089
  }
7521
8090
  /**
7522
8091
  * Estimate the execution fee for an Aleo function.
@@ -7551,10 +8120,7 @@ class ProgramManager {
7551
8120
  }
7552
8121
  const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition);
7553
8122
  const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource);
7554
- if (Object.keys(programImports)) {
7555
- return ProgramManager$1.estimateExecutionFee(programSource, functionName, programImports, edition);
7556
- }
7557
- return ProgramManager$1.estimateExecutionFee(programSource, functionName, imports, edition);
8123
+ return ProgramManager$1.estimateExecutionFee(programSource, functionName, programImports, edition);
7558
8124
  }
7559
8125
  // Internal utility function for getting a credits.aleo record
7560
8126
  async getCreditsRecord(amount, nonces, record, params) {
@@ -7662,15 +8228,8 @@ class ProgramManager {
7662
8228
  if (programName === undefined) {
7663
8229
  programName = programObject.id();
7664
8230
  }
7665
- if (edition == undefined) {
7666
- try {
7667
- edition = await this.networkClient.getLatestProgramEdition(programName);
7668
- }
7669
- catch (e) {
7670
- console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
7671
- edition = 0;
7672
- }
7673
- }
8231
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
8232
+ edition = edition ?? resolved.edition;
7674
8233
  // Get the private key from the account if it is not provided in the parameters.
7675
8234
  let executionPrivateKey = privateKey;
7676
8235
  if (typeof privateKey === "undefined" &&
@@ -7686,7 +8245,7 @@ class ProgramManager {
7686
8245
  let fee = priorityFee;
7687
8246
  // If a private fee is specified, but no fee record is provided, estimate the fee and find a matching record.
7688
8247
  if (!feeRecord) {
7689
- console.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
8248
+ logger.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
7690
8249
  const programString = programObject.toString();
7691
8250
  const imports = await this.networkClient.getProgramImports(programString);
7692
8251
  const baseFee = Number(ProgramManager$1.estimateDeploymentFee(programString, imports));
@@ -7775,7 +8334,7 @@ class ProgramManager {
7775
8334
  }
7776
8335
  catch (e) {
7777
8336
  // Program does not exist on the network, deployment can proceed
7778
- console.log(`Program ${programObject.id()} does not exist on the network, deploying...`);
8337
+ logger.log(`Program ${programObject.id()} does not exist on the network, deploying...`);
7779
8338
  }
7780
8339
  if (typeof programSource === "string") {
7781
8340
  throw Error(`Program ${programObject.id()} already exists on the network, please rename your program`);
@@ -7799,7 +8358,7 @@ class ProgramManager {
7799
8358
  let fee = priorityFee;
7800
8359
  // If a private fee is specified, but no fee record is provided, estimate the fee and find a matching record.
7801
8360
  if (!feeRecord) {
7802
- console.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
8361
+ logger.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
7803
8362
  const programString = programObject.toString();
7804
8363
  const imports = await this.networkClient.getProgramImports(programString);
7805
8364
  const baseFee = Number(ProgramManager$1.estimateDeploymentFee(programString, imports));
@@ -7900,7 +8459,7 @@ class ProgramManager {
7900
8459
  let fee = priorityFee;
7901
8460
  // If a private fee is specified, but no fee record is provided, estimate the fee and find a matching record.
7902
8461
  if (!feeRecord) {
7903
- console.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
8462
+ logger.log("Private fee specified, but no private fee record provided, estimating fee and finding a matching fee record.");
7904
8463
  const programString = programObject.toString();
7905
8464
  const imports = await this.networkClient.getProgramImports(programString);
7906
8465
  const baseFee = Number(ProgramManager$1.estimateDeploymentFee(programString, imports));
@@ -8274,8 +8833,8 @@ function serializeRequestSignInputToBytes(input) {
8274
8833
 
8275
8834
  // @TODO: This function is no longer needed, remove it.
8276
8835
  async function initializeWasm() {
8277
- console.warn("initializeWasm is deprecated, you no longer need to use it");
8836
+ logger.warn("initializeWasm is deprecated, you no longer need to use it");
8278
8837
  }
8279
8838
 
8280
- export { Account, AleoKeyProvider, AleoKeyProviderParams, AleoNetworkClient, BlockHeightSearch, CREDITS_PROGRAM_KEYS, KeyVerificationError as ChecksumMismatchError, DecryptionNotEnabledError, InvalidLocatorError, KEY_STORE, KeyVerificationError, MemKeyVerifier, NetworkRecordProvider, OfflineKeyProvider, OfflineSearchParams, PRIVATE_TO_PUBLIC_TRANSFER, PRIVATE_TRANSFER, PRIVATE_TRANSFER_TYPES, PUBLIC_TO_PRIVATE_TRANSFER, PUBLIC_TRANSFER, PUBLIC_TRANSFER_AS_SIGNER, ProgramManager, RECORD_DOMAIN, RecordNotFoundError, RecordScanner, RecordScannerRequestError, SealanceMerkleTree, UUIDError, VALID_TRANSFER_TYPES, ViewKeyNotStoredError, buildExecutionRequestFromExternallySignedData, computeExternalSigningInputs, encryptAuthorization, encryptProvingRequest, encryptRegistrationRequest, encryptViewKey, initializeWasm, inputsToFields, isInputIdStrategy, isProveApiErrorBody, isProvingResponse, isRecordViewKeyStrategy, isViewKeyStrategy, logAndThrow, programChecksum, provingKeyLocator, sha256Hex, toAddress, toField, toGroup, toSignature, toViewKey, translationKeyLocator, verifyBatchProof, verifyProof, verifyingKeyLocator, zeroizeBytes };
8839
+ export { Account, AleoKeyProvider, AleoKeyProviderParams, AleoNetworkClient, BlockHeightSearch, CREDITS_PROGRAM_KEYS, KeyVerificationError as ChecksumMismatchError, DecryptionNotEnabledError, IndexedDBKeyStore, InvalidLocatorError, KEY_STORE, KeyVerificationError, MemKeyVerifier, NetworkRecordProvider, OfflineKeyProvider, OfflineSearchParams, PRIVATE_TO_PUBLIC_TRANSFER, PRIVATE_TRANSFER, PRIVATE_TRANSFER_TYPES, PUBLIC_TO_PRIVATE_TRANSFER, PUBLIC_TRANSFER, PUBLIC_TRANSFER_AS_SIGNER, ProgramManager, RECORD_DOMAIN, RecordNotFoundError, RecordScanner, RecordScannerRequestError, SealanceMerkleTree, UUIDError, VALID_TRANSFER_TYPES, ViewKeyNotStoredError, buildExecutionRequestFromExternallySignedData, computeExternalSigningInputs, encryptAuthorization, encryptProvingRequest, encryptRegistrationRequest, encryptViewKey, getLogLevel, initializeWasm, inputsToFields, isInputIdStrategy, isProveApiErrorBody, isProvingResponse, isRecordViewKeyStrategy, isViewKeyStrategy, logAndThrow, programChecksum, provingKeyLocator, setLogLevel, sha256Hex, toAddress, toField, toGroup, toSignature, toViewKey, translationKeyLocator, verifyBatchProof, verifyProof, verifyingKeyLocator, zeroizeBytes };
8281
8840
  //# sourceMappingURL=browser.js.map