@provablehq/sdk 0.11.5 → 0.11.6

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.
@@ -1603,7 +1603,7 @@ class AleoNetworkClient {
1603
1603
  else {
1604
1604
  this.headers = {
1605
1605
  // This is replaced by the actual version by a Rollup plugin
1606
- "X-Aleo-SDK-Version": "0.11.5",
1606
+ "X-Aleo-SDK-Version": "0.11.6",
1607
1607
  "X-Aleo-environment": environment(),
1608
1608
  };
1609
1609
  }
@@ -1619,7 +1619,7 @@ class AleoNetworkClient {
1619
1619
  else {
1620
1620
  this.headers = {
1621
1621
  // This is replaced by the actual version by a Rollup plugin
1622
- "X-Aleo-SDK-Version": "0.11.5",
1622
+ "X-Aleo-SDK-Version": "0.11.6",
1623
1623
  "X-Aleo-environment": environment(),
1624
1624
  };
1625
1625
  }
@@ -5799,6 +5799,56 @@ function generateHookData(recipientAddress, secretNonce) {
5799
5799
  return hookData;
5800
5800
  }
5801
5801
 
5802
+ const preparedProgramBuilders = new WeakMap();
5803
+ function clonePreparedProgramBuilder(preparedProgram) {
5804
+ const builder = preparedProgramBuilders.get(preparedProgram);
5805
+ if (!builder) {
5806
+ throw new Error("Prepared program has already been freed");
5807
+ }
5808
+ // The WASM clone is a cheap shared Rc handle, not a deep copy. Mutations
5809
+ // made while authorizing intentionally remain cached in the prepared context.
5810
+ return builder.clone();
5811
+ }
5812
+ function programImportsFromBuilder(builder, entryProgramName) {
5813
+ const imports = {};
5814
+ for (const name of Array.from(builder.programNames())) {
5815
+ if (name === entryProgramName)
5816
+ continue;
5817
+ const source = builder.getProgram(name);
5818
+ if (source)
5819
+ imports[name] = source;
5820
+ }
5821
+ return imports;
5822
+ }
5823
+ /**
5824
+ * A reusable program context created by {@link ProgramManager.prepareProgram}.
5825
+ *
5826
+ * The context is scoped to one program function. Calls using it must be
5827
+ * sequential. The caller owns the context and should call {@link free} when it
5828
+ * is no longer needed.
5829
+ */
5830
+ class PreparedProgram {
5831
+ programName;
5832
+ functionName;
5833
+ programSource;
5834
+ edition;
5835
+ /**
5836
+ * Instances are normally created through {@link ProgramManager.prepareProgram}.
5837
+ */
5838
+ constructor(programName, functionName, programSource, edition) {
5839
+ this.programName = programName;
5840
+ this.functionName = functionName;
5841
+ this.programSource = programSource;
5842
+ this.edition = edition;
5843
+ }
5844
+ /**
5845
+ * Release the prepared snarkVM process. This method is idempotent.
5846
+ */
5847
+ free() {
5848
+ preparedProgramBuilders.get(this)?.free();
5849
+ preparedProgramBuilders.delete(this);
5850
+ }
5851
+ }
5802
5852
  /**
5803
5853
  * The ProgramManager class is used to execute and deploy programs on the Aleo network and create value transfers.
5804
5854
  */
@@ -5907,6 +5957,93 @@ class ProgramManager {
5907
5957
  setKeyStore(keyStore) {
5908
5958
  this._keyStore = keyStore;
5909
5959
  }
5960
+ /**
5961
+ * Prepare a program for later authorization or proving-request generation.
5962
+ *
5963
+ * This resolves the source, edition, and transitive imports and initializes
5964
+ * the underlying snarkVM process without accessing a private key. Reuse the
5965
+ * returned context through the `preparedProgram` option, then call `free()`
5966
+ * when it is no longer needed.
5967
+ *
5968
+ * @param options Program and function to prepare.
5969
+ * @returns A reusable, caller-owned context.
5970
+ *
5971
+ * @example
5972
+ * const prepared = await programManager.prepareProgram({
5973
+ * programName: "hello_hello.aleo",
5974
+ * functionName: "hello",
5975
+ * });
5976
+ * try {
5977
+ * const authorization = await programManager.buildAuthorization({
5978
+ * programName: "hello_hello.aleo",
5979
+ * functionName: "hello",
5980
+ * inputs: ["5u32", "5u32"],
5981
+ * preparedProgram: prepared,
5982
+ * });
5983
+ * } finally {
5984
+ * prepared.free();
5985
+ * }
5986
+ */
5987
+ async prepareProgram(options) {
5988
+ let program = options.programSource;
5989
+ if (program === undefined) {
5990
+ try {
5991
+ program = await this.networkClient.getProgram(options.programName);
5992
+ }
5993
+ catch (e) {
5994
+ logAndThrow(`Error finding ${options.programName}. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network the program is deployed to the network.`);
5995
+ }
5996
+ }
5997
+ else if (program instanceof testnet_js.Program) {
5998
+ program = program.toString();
5999
+ }
6000
+ const parsedProgram = testnet_js.Program.fromString(program);
6001
+ if (parsedProgram.id() !== options.programName) {
6002
+ throw new Error(`Program source ID '${parsedProgram.id()}' does not match requested program '${options.programName}'`);
6003
+ }
6004
+ if (!parsedProgram.getFunctions().includes(options.functionName)) {
6005
+ throw new Error(`Function '${options.functionName}' does not exist in program '${options.programName}'`);
6006
+ }
6007
+ const edition = options.edition
6008
+ ?? (await this.resolveEditionAndAmendment(options.programName)).edition;
6009
+ const { builder } = await this.buildProgramImports(program, options.programImports, false, options.functionName);
6010
+ // Add the entry program as well as its imports so the snarkVM process is
6011
+ // fully initialized before an authorization is requested.
6012
+ builder.addProgram(options.programName, program, edition);
6013
+ const preparedProgram = new PreparedProgram(options.programName, options.functionName, program, edition);
6014
+ preparedProgramBuilders.set(preparedProgram, builder);
6015
+ return preparedProgram;
6016
+ }
6017
+ validatePreparedProgram(preparedProgram, options) {
6018
+ if (preparedProgram.programName !== options.programName
6019
+ || preparedProgram.functionName !== options.functionName) {
6020
+ throw new Error(`Prepared program is for ${preparedProgram.programName}/${preparedProgram.functionName}, not ${options.programName}/${options.functionName}`);
6021
+ }
6022
+ const providedSource = options.programSource instanceof testnet_js.Program
6023
+ ? options.programSource.toString()
6024
+ : options.programSource;
6025
+ if (providedSource !== undefined
6026
+ && providedSource !== preparedProgram.programSource) {
6027
+ throw new Error("Prepared program source does not match the supplied program source");
6028
+ }
6029
+ if (options.edition !== undefined
6030
+ && options.edition !== preparedProgram.edition) {
6031
+ throw new Error("Prepared program edition does not match the supplied edition");
6032
+ }
6033
+ if (options.programImports) {
6034
+ const builder = clonePreparedProgramBuilder(preparedProgram);
6035
+ try {
6036
+ for (const [name, source] of Object.entries(options.programImports)) {
6037
+ if (builder.getProgram(name) !== source.toString()) {
6038
+ throw new Error(`Prepared program import '${name}' does not match the supplied import`);
6039
+ }
6040
+ }
6041
+ }
6042
+ finally {
6043
+ builder.free();
6044
+ }
6045
+ }
6046
+ }
5910
6047
  /**
5911
6048
  * Collect static imports declared by the entry program together with any
5912
6049
  * caller-provided imports, then resolve missing transitive dependencies.
@@ -6907,11 +7044,15 @@ class ProgramManager {
6907
7044
  async buildAuthorization(options) {
6908
7045
  // Destructure the options object to access the parameters.
6909
7046
  const { functionName, inputs, } = options;
7047
+ const preparedProgram = options.preparedProgram;
7048
+ if (preparedProgram) {
7049
+ this.validatePreparedProgram(preparedProgram, options);
7050
+ }
6910
7051
  const privateKey = options.privateKey;
6911
- let program = options.programSource;
6912
- let programName = options.programName;
6913
- let imports = options.programImports;
6914
- let edition = options.edition;
7052
+ let program = preparedProgram?.programSource ?? options.programSource;
7053
+ let programName = preparedProgram?.programName ?? options.programName;
7054
+ const imports = options.programImports;
7055
+ let edition = preparedProgram?.edition ?? options.edition;
6915
7056
  // Ensure the function exists on the network.
6916
7057
  if (program === undefined) {
6917
7058
  try {
@@ -6937,16 +7078,21 @@ class ProgramManager {
6937
7078
  if (typeof executionPrivateKey === "undefined") {
6938
7079
  throw "No private key provided and no private key set in the ProgramManager";
6939
7080
  }
6940
- const resolved = await this.resolveEditionAndAmendment(programName, edition);
6941
- edition = edition ?? resolved.edition;
7081
+ if (!preparedProgram) {
7082
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
7083
+ edition = edition ?? resolved.edition;
7084
+ }
6942
7085
  // Auto-convert bare string inputs to field elements where the function expects field type.
6943
7086
  const preparedInputs = this.prepareInputs(program, functionName, inputs);
6944
7087
  // Build ProgramImportsBuilder for import resolution (no key loading —
6945
7088
  // authorizations don't synthesize keys).
6946
- const { builder } = await this.buildProgramImports(program, imports, false, functionName);
7089
+ const builder = preparedProgram
7090
+ ? clonePreparedProgramBuilder(preparedProgram)
7091
+ : (await this.buildProgramImports(program, imports, false, functionName)).builder;
7092
+ const hasPreparedProcess = preparedProgram !== undefined;
6947
7093
  const hasImports = !builder.isEmpty();
6948
7094
  // Build and return an `Authorization` for the desired function.
6949
- const authorization = await testnet_js.ProgramManager.authorize(executionPrivateKey, program, functionName, preparedInputs, hasImports ? undefined : imports, edition, hasImports ? builder?.clone() : undefined);
7095
+ const authorization = await testnet_js.ProgramManager.authorize(executionPrivateKey, program, functionName, preparedInputs, hasPreparedProcess || hasImports ? undefined : imports, edition, hasPreparedProcess ? builder : hasImports ? builder.clone() : undefined);
6950
7096
  return authorization;
6951
7097
  }
6952
7098
  /**
@@ -6980,11 +7126,15 @@ class ProgramManager {
6980
7126
  async buildAuthorizationUnchecked(options) {
6981
7127
  // Destructure the options object to access the parameters.
6982
7128
  const { functionName, inputs, } = options;
7129
+ const preparedProgram = options.preparedProgram;
7130
+ if (preparedProgram) {
7131
+ this.validatePreparedProgram(preparedProgram, options);
7132
+ }
6983
7133
  const privateKey = options.privateKey;
6984
- let program = options.programSource;
6985
- let programName = options.programName;
6986
- let imports = options.programImports;
6987
- let edition = options.edition;
7134
+ let program = preparedProgram?.programSource ?? options.programSource;
7135
+ let programName = preparedProgram?.programName ?? options.programName;
7136
+ const imports = options.programImports;
7137
+ let edition = preparedProgram?.edition ?? options.edition;
6988
7138
  // Ensure the function exists on the network.
6989
7139
  if (program === undefined) {
6990
7140
  try {
@@ -7010,16 +7160,21 @@ class ProgramManager {
7010
7160
  if (typeof executionPrivateKey === "undefined") {
7011
7161
  throw "No private key provided and no private key set in the ProgramManager";
7012
7162
  }
7013
- const resolved = await this.resolveEditionAndAmendment(programName, edition);
7014
- edition = edition ?? resolved.edition;
7163
+ if (!preparedProgram) {
7164
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
7165
+ edition = edition ?? resolved.edition;
7166
+ }
7015
7167
  // Auto-convert bare string inputs to field elements where the function expects field type.
7016
7168
  const preparedInputs = this.prepareInputs(program, functionName, inputs);
7017
7169
  // Build ProgramImportsBuilder for import resolution (no key loading —
7018
7170
  // authorizations don't synthesize keys).
7019
- const { builder } = await this.buildProgramImports(program, imports, false, functionName);
7171
+ const builder = preparedProgram
7172
+ ? clonePreparedProgramBuilder(preparedProgram)
7173
+ : (await this.buildProgramImports(program, imports, false, functionName)).builder;
7174
+ const hasPreparedProcess = preparedProgram !== undefined;
7020
7175
  const hasImports = !builder.isEmpty();
7021
7176
  // Build and return an `Authorization` for the desired function.
7022
- const authorization = await testnet_js.ProgramManager.buildAuthorizationUnchecked(executionPrivateKey, program, functionName, preparedInputs, hasImports ? undefined : imports, edition, hasImports ? builder?.clone() : undefined);
7177
+ const authorization = await testnet_js.ProgramManager.buildAuthorizationUnchecked(executionPrivateKey, program, functionName, preparedInputs, hasPreparedProcess || hasImports ? undefined : imports, edition, hasPreparedProcess ? builder : hasImports ? builder.clone() : undefined);
7023
7178
  return authorization;
7024
7179
  }
7025
7180
  /**
@@ -7056,14 +7211,18 @@ class ProgramManager {
7056
7211
  async provingRequest(options) {
7057
7212
  // Destructure the options object to access the parameters.
7058
7213
  const { functionName, priorityFee, privateFee, inputs, recordSearchParams, broadcast = false, unchecked = false, } = options;
7214
+ const preparedProgram = options.preparedProgram;
7215
+ if (preparedProgram) {
7216
+ this.validatePreparedProgram(preparedProgram, options);
7217
+ }
7059
7218
  const baseFee = options.baseFee ? options.baseFee : 0;
7060
7219
  const privateKey = options.privateKey;
7061
7220
  const useFeeMaster = options.useFeeMaster ? options.useFeeMaster : false;
7062
- let program = options.programSource;
7063
- let programName = options.programName;
7221
+ let program = preparedProgram?.programSource ?? options.programSource;
7222
+ let programName = preparedProgram?.programName ?? options.programName;
7064
7223
  let feeRecord = options.feeRecord;
7065
7224
  let imports = options.programImports;
7066
- let edition = options.edition;
7225
+ let edition = preparedProgram?.edition ?? options.edition;
7067
7226
  if (!inputs && !options.executionRequest) {
7068
7227
  throw new Error("Either function inputs or an execution request must be provided to form a proving request");
7069
7228
  }
@@ -7083,8 +7242,10 @@ class ProgramManager {
7083
7242
  if (programName === undefined) {
7084
7243
  programName = testnet_js.Program.fromString(program).id();
7085
7244
  }
7086
- const resolved = await this.resolveEditionAndAmendment(programName, edition);
7087
- edition = edition ?? resolved.edition;
7245
+ if (!preparedProgram) {
7246
+ const resolved = await this.resolveEditionAndAmendment(programName, edition);
7247
+ edition = edition ?? resolved.edition;
7248
+ }
7088
7249
  // Get the private key from the account if it is not provided in the parameters.
7089
7250
  let executionPrivateKey = privateKey;
7090
7251
  if (typeof privateKey === "undefined" &&
@@ -7094,7 +7255,7 @@ class ProgramManager {
7094
7255
  }
7095
7256
  // Resolve the program imports if they exist.
7096
7257
  const numberOfImports = testnet_js.Program.fromString(program).getImports().length;
7097
- if (numberOfImports > 0 && !imports) {
7258
+ if (!preparedProgram && numberOfImports > 0 && !imports) {
7098
7259
  try {
7099
7260
  imports = (await this.networkClient.getProgramImports(programName));
7100
7261
  }
@@ -7102,6 +7263,28 @@ class ProgramManager {
7102
7263
  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.`);
7103
7264
  }
7104
7265
  }
7266
+ // Build ProgramImportsBuilder for import resolution (no key loading —
7267
+ // proving requests don't synthesize keys).
7268
+ const builder = preparedProgram
7269
+ ? clonePreparedProgramBuilder(preparedProgram)
7270
+ : (await this.buildProgramImports(program, imports, false, functionName)).builder;
7271
+ const hasPreparedProcess = preparedProgram !== undefined;
7272
+ const hasImports = !builder.isEmpty();
7273
+ // Normalize imports once to the full merged set from the builder so
7274
+ // both the private-fee estimate below and the Rust-side fee estimator
7275
+ // (estimate_fee_for_authorization uses the legacy imports Object to
7276
+ // avoid a RefCell double-borrow — see proving_request.rs) see all
7277
+ // transitive imports, not just the caller's partial set. Use
7278
+ // programNames + getProgram to avoid serializing key bytes (toObject
7279
+ // would serialize all proving/verifying keys, which can be tens of
7280
+ // MB). The prepared executionRequest path skips the copy: it passes
7281
+ // the process builder directly and never estimates fees, so the
7282
+ // imports Object goes unused there.
7283
+ const importsObjectUnused = hasPreparedProcess
7284
+ && options.executionRequest instanceof testnet_js.ExecutionRequest;
7285
+ if ((hasPreparedProcess || hasImports) && !importsObjectUnused) {
7286
+ imports = programImportsFromBuilder(builder, programName);
7287
+ }
7105
7288
  // Get the fee record from the account if it is not provided in the parameters
7106
7289
  try {
7107
7290
  if (privateFee && !useFeeMaster && !options.executionRequest) {
@@ -7122,30 +7305,11 @@ class ProgramManager {
7122
7305
  catch (e) {
7123
7306
  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.`);
7124
7307
  }
7125
- // Build ProgramImportsBuilder for import resolution (no key loading —
7126
- // proving requests don't synthesize keys).
7127
- // Note: imports is kept for the Rust-side fee estimation fallback
7128
- // (estimate_fee_for_authorization uses the legacy imports Object to avoid
7129
- // a RefCell double-borrow — see proving_request.rs).
7130
- const { builder } = await this.buildProgramImports(program, imports, false, functionName);
7131
- const hasImports = !builder.isEmpty();
7132
- // Normalize imports to the full merged set from the builder so the
7133
- // Rust fee estimator sees all transitive imports, not just the
7134
- // caller's partial set. Use programNames + getProgram to avoid
7135
- // serializing key bytes (toObject would serialize all proving/
7136
- // verifying keys, which can be tens of MB).
7137
- if (hasImports) {
7138
- const merged = {};
7139
- for (const name of Array.from(builder.programNames())) {
7140
- const src = builder.getProgram(name);
7141
- if (src)
7142
- merged[name] = src;
7143
- }
7144
- imports = merged;
7145
- }
7146
7308
  if (options.executionRequest instanceof testnet_js.ExecutionRequest) {
7147
- return await testnet_js.ProgramManager.buildProvingRequestFromExecutionRequest(options.executionRequest, program, unchecked, broadcast, edition, imports, // kept for fee estimation in Rust
7148
- executionPrivateKey, hasImports ? builder?.clone() : undefined);
7309
+ return await testnet_js.ProgramManager.buildProvingRequestFromExecutionRequest(options.executionRequest, program, unchecked, broadcast, edition,
7310
+ // The prepared process supersedes the legacy imports Object;
7311
+ // without one, imports is kept for fee estimation in Rust.
7312
+ hasPreparedProcess ? undefined : imports, executionPrivateKey, hasPreparedProcess ? builder : hasImports ? builder.clone() : undefined);
7149
7313
  }
7150
7314
  else {
7151
7315
  // Ensure the private key exists.
@@ -7160,7 +7324,7 @@ class ProgramManager {
7160
7324
  const preparedInputs = this.prepareInputs(program, functionName, inputs);
7161
7325
  // Build and return the `ProvingRequest`.
7162
7326
  return await testnet_js.ProgramManager.buildProvingRequest(executionPrivateKey, program, functionName, preparedInputs, baseFee, priorityFee, feeRecord, imports, // kept for fee estimation in Rust
7163
- broadcast, unchecked, edition, useFeeMaster, hasImports ? builder?.clone() : undefined);
7327
+ broadcast, unchecked, edition, useFeeMaster, hasPreparedProcess ? builder : hasImports ? builder.clone() : undefined);
7164
7328
  }
7165
7329
  }
7166
7330
  /**
@@ -8537,7 +8701,7 @@ class ProgramManager {
8537
8701
  * import { AleoKeyProvider, getOrInitConsensusVersionTestHeights, ProgramManager, NetworkRecordProvider } from "@provablehq/sdk/mainnet.js";
8538
8702
  *
8539
8703
  * // Initialize the development consensus heights in order to work with devnode.
8540
- * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16");
8704
+ * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17");
8541
8705
  *
8542
8706
  * // Create a new NetworkClient and RecordProvider.
8543
8707
  * const recordProvider = new NetworkRecordProvider(account, networkClient);
@@ -8664,7 +8828,7 @@ class ProgramManager {
8664
8828
  * import { ProgramManager, NetworkRecordProvider, getOrInitConsensusVersionTestHeights } from "@provablehq/sdk/mainnet.js";
8665
8829
  *
8666
8830
  * // Initialize the development consensus heights in order to work with a local devnode.
8667
- * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16");
8831
+ * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17");
8668
8832
  *
8669
8833
  * // Create a new NetworkClient, and RecordProvider
8670
8834
  * const recordProvider = new NetworkRecordProvider(account, networkClient);
@@ -9472,6 +9636,7 @@ exports.PRIVATE_TRANSFER_TYPES = PRIVATE_TRANSFER_TYPES;
9472
9636
  exports.PUBLIC_TO_PRIVATE_TRANSFER = PUBLIC_TO_PRIVATE_TRANSFER;
9473
9637
  exports.PUBLIC_TRANSFER = PUBLIC_TRANSFER;
9474
9638
  exports.PUBLIC_TRANSFER_AS_SIGNER = PUBLIC_TRANSFER_AS_SIGNER;
9639
+ exports.PreparedProgram = PreparedProgram;
9475
9640
  exports.ProgramManager = ProgramManager;
9476
9641
  exports.RECORD_DOMAIN = RECORD_DOMAIN;
9477
9642
  exports.RecordNotFoundError = RecordNotFoundError;