sindri
Version:
The Sindri Labs JavaScript SDK and CLI tool.
1 lines • 130 kB
Source Map (JSON)
{"version":3,"sources":["../../src/lib/client.ts","../../src/lib/api/core/BaseHttpRequest.ts","../../src/lib/api/core/request.ts","../../src/lib/isomorphic.ts","../../src/lib/api/core/ApiError.ts","../../src/lib/api/core/CancelablePromise.ts","../../src/lib/api/core/AxiosHttpRequest.ts","../../src/lib/api/services/AuthorizationService.ts","../../src/lib/api/services/CircuitsService.ts","../../src/lib/api/services/InternalService.ts","../../src/lib/api/services/ProofsService.ts","../../src/lib/api/services/TokenService.ts","../../src/lib/api/ApiClient.ts","../../src/lib/config.ts","../../src/lib/logging.ts","../../src/lib/utils.ts","../../src/lib/index.ts"],"sourcesContent":["import { readFile, stat } from \"fs/promises\";\nimport path from \"path\";\nimport type { Readable } from \"stream\";\n\nimport gzip from \"gzip-js\";\nimport walk from \"ignore-walk\";\nimport type { WrapOptions as RetryOptions } from \"retry\";\nimport tar from \"tar\";\nimport Tar from \"tar-js\";\n\nimport { ApiClient, CircuitType, JobStatus, OpenAPIConfig } from \"lib/api\";\nimport type {\n BoojumCircuitInfoResponse,\n CircomCircuitInfoResponse,\n CircuitStatusResponse,\n GnarkCircuitInfoResponse,\n HermezCircuitInfoResponse,\n Halo2CircuitInfoResponse,\n JoltCircuitInfoResponse,\n NoirCircuitInfoResponse,\n OpenvmCircuitInfoResponse,\n Plonky2CircuitInfoResponse,\n ProofInfoResponse,\n ProofStatusResponse,\n SnarkvmCircuitInfoResponse,\n Sp1CircuitInfoResponse,\n} from \"lib/api\";\nimport { Config } from \"lib/config\";\nimport { createLogger, type Logger, type LogLevel } from \"lib/logging\";\nimport { File, FormData } from \"lib/isomorphic\";\nimport type {\n BrowserFile,\n BrowserFormData,\n NodeFile,\n NodeFormData,\n} from \"lib/isomorphic\";\nimport { type Meta, validateMetaAndMergeWithDefaults } from \"lib/utils\";\n\n// Re-export types from the API.\nexport type {\n BoojumCircuitInfoResponse,\n CircomCircuitInfoResponse,\n CircuitType,\n GnarkCircuitInfoResponse,\n Halo2CircuitInfoResponse,\n HermezCircuitInfoResponse,\n JoltCircuitInfoResponse,\n JobStatus,\n NoirCircuitInfoResponse,\n OpenvmCircuitInfoResponse,\n Plonky2CircuitInfoResponse,\n ProofInfoResponse,\n SnarkvmCircuitInfoResponse,\n Sp1CircuitInfoResponse,\n};\nexport type CircuitInfoResponse =\n | BoojumCircuitInfoResponse\n | CircomCircuitInfoResponse\n | Halo2CircuitInfoResponse\n | HermezCircuitInfoResponse\n | JoltCircuitInfoResponse\n | GnarkCircuitInfoResponse\n | NoirCircuitInfoResponse\n | OpenvmCircuitInfoResponse\n | Plonky2CircuitInfoResponse\n | SnarkvmCircuitInfoResponse\n | Sp1CircuitInfoResponse;\n\n// Re-export other internal types.\nexport type { Logger, LogLevel, Meta, RetryOptions };\n\n/**\n * The options for authenticating with the API.\n */\nexport interface AuthOptions {\n /**\n * The API key to use for authentication.\n */\n apiKey?: string;\n /**\n * The base URL for the API.\n */\n baseUrl?: string;\n}\n\n/**\n * Represents the primary client for interacting with the Sindri ZKP service API. This class serves\n * as the central entry point for the SDK, facilitating various operations such as compiling ZKP\n * circuits and generating proofs.\n *\n * The {@link SindriClient} class encapsulates all the necessary methods and properties required to\n * communicate effectively with the Sindri ZKP service, handling tasks like authentication, request\n * management, and response processing.\n *\n * Usage of this class typically involves instantiating it with appropriate authentication options\n * and then utilizing its methods to interact with the service.\n *\n * @example\n * // Create an instance of the `SindriClient` class.\n * const client = new SindriClient({ apiKey: 'your-api-key' });\n *\n * // Use the client to interact with the Sindri ZKP service...\n */\nexport class SindriClient {\n /** @hidden */\n readonly _client: ApiClient;\n /** @hidden */\n readonly _clientConfig: OpenAPIConfig;\n /** @hidden */\n readonly _config: Config | undefined;\n\n readonly logger: Logger;\n\n /**\n * Represents the polling interval in milliseconds used for querying the status of an endpoint.\n * This value determines the frequency at which the SDK polls an endpoint to check for any changes\n * in status.\n *\n * The choice of polling interval is critical for balancing responsiveness against resource\n * consumption. A shorter interval leads to more frequent updates, beneficial for\n * rapidly-changing statuses, but at the expense of higher network and computational load. In\n * contrast, a longer interval reduces resource usage but may delay the detection of status\n * changes.\n *\n * For more complex ZKP circuits, which may take longer to compile, considering a larger polling\n * interval could be advantageous. This approach minimizes unnecessary network traffic and\n * computational effort while awaiting the completion of these time-intensive operations.\n *\n * The default value is set to 1000 milliseconds (1 second), offering a general balance. However,\n * it can and should be adjusted based on the expected complexity and compilation time of the\n * circuits being processed.\n */\n public pollingInterval: number = 1000;\n\n /**\n * Represents the options for retrying requests to the Sindri ZKP service.\n *\n * See the [`retry` package](https://www.npmjs.com/package/retry#retrytimeoutsoptions)\n * documentation for more information on the available options. The values here are the defaults,\n * but they can be replaced with custom values in the constructor.\n */\n public retryOptions: RetryOptions = {\n minTimeout: 1000,\n retries: 6,\n };\n\n /**\n * Constructs a new instance of the {@link SindriClient} class for interacting with the Sindri ZKP\n * service. This constructor initializes the client with the necessary authentication options.\n *\n * The provided `authOptions` parameter allows for specifying authentication credentials and\n * configurations required for the client to communicate securely with the service. See\n * {@link SindriClient.authorize} for more details about how authentication credentials are sourced.\n *\n * @param authOptions - The authentication options for the client, including\n * credentials like API keys or tokens. Defaults to an empty object if not provided.\n *\n * @example\n * // Instantiating the SindriClient with authentication options\n * const client = new SindriClient({ apiKey: 'sindri-...-fskd' });\n *\n * @see {@link SindriClient.authorize} for information on retrieving this value.\n */\n constructor(\n authOptions: AuthOptions = {},\n { retryOptions }: { retryOptions?: RetryOptions } = {},\n ) {\n // Initialize the client and store a reference to its config.\n this._client = new ApiClient();\n this._clientConfig = this._client.request.config;\n\n // Set the `Sindri-Client` header.\n const versionTag = process.env.VERSION\n ? `v${process.env.VERSION}`\n : \"unknown\";\n this._clientConfig.HEADERS = {\n ...this._clientConfig.HEADERS,\n \"Sindri-Client\": `sindri-js-sdk/${versionTag}`,\n };\n\n // Create a local logger instance.\n this.logger = createLogger();\n if (!process.env.BROWSER_BUILD) {\n this._config = new Config(this.logger);\n }\n this._clientConfig.sindri = this;\n\n // Authorize the client.\n this.authorize(authOptions);\n\n // Store the retry options.\n if (retryOptions) {\n this.retryOptions = structuredClone(retryOptions);\n }\n }\n\n /**\n * Retrieves the current value of the client's API key used for authenticating with the Sindri ZKP\n * service. This property is crucial for ensuring secure communication with the API and is\n * typically set during client initialization.\n *\n * If the API key is not set or is in an invalid format (not a string), this getter returns\n * `null`. Proper management of the API key is essential for the security and proper functioning\n * of the SDK.\n *\n * @returns The current API key if set and valid, otherwise `null`.\n *\n * @example\n * const currentApiKey = client.apiKey;\n * if (currentApiKey) {\n * console.log('API Key is set.');\n * } else {\n * console.log('API Key is not set or is invalid.');\n * }\n */\n get apiKey(): string | null {\n if (\n this._clientConfig.TOKEN &&\n typeof this._clientConfig.TOKEN !== \"string\"\n ) {\n return null;\n }\n return this._clientConfig.TOKEN || null;\n }\n\n /**\n * Retrieves the current base URL of the Sindri ZKP service that the client is configured to\n * interact with. This URL forms the foundation of all API requests made by the client and is\n * typically set during client initialization. Anyone other than employees at Sindri can typically\n * ignore this and use the default value of `https://sindri.app`.\n *\n * @returns The current base URL of the Sindri ZKP service.\n *\n * @example\n * console.log(`Current base URL: ${client.baseUrl}`);\n */\n get baseUrl(): string {\n return this._clientConfig.BASE;\n }\n\n /** Retrieves the current log level of the client. The log level determines the verbosity of logs\n * produced by the client which can be crucial for debugging and monitoring the client's\n * interactions with the Sindri ZKP service.\n *\n * @returns The current log level of the client.\n *\n * @example\n * console.log(`Current log level: ${client.logLevel}`);\n */\n get logLevel(): LogLevel {\n // We don't specify any custom log levels, so we can narrow the type to exclude strings.\n return this.logger.level as LogLevel;\n }\n\n /**\n * Sets the client's log level. This level determines the verbosity of logs produced by the\n * client, allowing for flexible control over the amount of information logged during operation.\n *\n * @param level - The new log level to set for the client.\n *\n * @example\n * // Set log level to debug.\n * client.logLevel = \"debug\";\n */\n set logLevel(level: LogLevel) {\n this.logger.level = level;\n this.logger.debug(`Set log level to \"${this.logger.level}\".`);\n }\n\n /**\n * Authorizes the client with the Sindri ZKP service using the provided authentication options.\n * This method is called automatically after initializing a client, but you may call it again if\n * you would like to change the credentials. The logic around how credentials is as follows:\n *\n * 1. Any explicitly specified options in `authOptions` are always used if provided.\n * 2. The `SINDRI_API_KEY` and `SINDRI_BASE_URL` environment variables are checked next.\n * 3. The settings in `sindri.conf.json` (produced by running `sindri login` on the command-line) will be checked after that.\n * 4. Finally, the default value of `https://sindri.app` will be used for the base URL (this is\n * typically what you want unless you're an employee at Sindri). The API key will remain unset and\n * you will only be able to make requests that allow anonymous access.\n *\n *\n * @param authOptions - The authentication details required to authorize the client.\n * @returns True if authorization is successful, false otherwise.\n *\n * @example\n * const authOptions = { apiKey: 'sindri-...-jskd' };\n * const isAuthorized = client.authorize(authOptions);\n * if (isAuthorized) {\n * console.log('Client is fully authorized.');\n * } else {\n * console.log('Client is not authorized.');\n * }\n */\n authorize(authOptions: AuthOptions): boolean {\n if (process.env.BROWSER_BUILD) {\n this._clientConfig.BASE = authOptions.baseUrl || \"https://sindri.app\";\n this._clientConfig.TOKEN = authOptions.apiKey;\n } else {\n this._config!.reload();\n this._clientConfig.BASE =\n authOptions.baseUrl ||\n process.env.SINDRI_BASE_URL ||\n this._config!.auth?.baseUrl ||\n this._clientConfig.BASE ||\n \"https://sindri.app\";\n this._clientConfig.TOKEN =\n authOptions.apiKey ||\n process.env.SINDRI_API_KEY ||\n this._config!.auth?.apiKey;\n }\n return !!(this._clientConfig.BASE && this._clientConfig.TOKEN);\n }\n\n /**\n * Creates a new {@link SindriClient} client instance. The class itself is not exported, so use\n * this method on the exported (or any other) client instance to create a new instance. The new\n * instance can be configured and used completely independently from any other instances. For\n * example it can use different credentials or a different log level.\n *\n * @param authOptions - The authentication options for the client, including\n * credentials like API keys or tokens. Defaults to an empty object if not provided.\n * @param options - Additional options for configuring the client.\n * @param options.retryOptions - The options related to retrying a request.\n *\n * @example\n * import sindri from 'sindri';\n *\n * // Equivalent to: const myClient = new SindriClient({ ... });\n * const myClient = sindri.create({ apiKey: 'sindri-mykey-1234'});\n *\n * @returns The new client instance.\n */\n create(\n authOptions: AuthOptions | undefined,\n options:\n | {\n retryOptions?: RetryOptions;\n }\n | undefined,\n ): SindriClient {\n return new SindriClient(authOptions, options);\n }\n\n /**\n * Asynchronously creates and deploys a new circuit, initiating its compilation process. This\n * method is essential for submitting new versions of circuits to the Sindri ZKP service for\n * compilation. Upon deployment, it continuously polls the service to track the compilation status\n * until the process either completes successfully or fails.\n *\n * The method accepts two parameters: `project` and `tags`. The `project` parameter can be either\n * a string representing the path to the project or an array of files (browser or Node.js file\n * objects) constituting the circuit. The `tags` parameter is used to assign tags to the deployed\n * circuit, facilitating versioning and identification. By default, the circuit is tagged as\n * \"latest\".\n *\n * After successful deployment and compilation, the method returns a `CircuitInfoResponse` object,\n * which includes details about the compiled circuit, such as its identifier and status.\n *\n * @param project - In Node.js, this can either be a path to the root\n * directory of a Sindri project, the path to a gzipped tarball containing the project, or an\n * array of `buffer.File` objects. In a web browser, it can only be an array of `File` objects.\n * @param tags - The list of tags, or singular tag if a string is passed, that\n * should be associated with the deployed circuit. Defaults to `[\"latest\"]`. Specify an empty\n * array to indicate that you don't care about the compilation outputs and just want to see if it\n * the circuit will compile.\n * @param meta - An object containing metadata to associate with the circuit build. This will be\n * merged into any metadata specified in the `SINDRI_META` environment variable. This variable can\n * be a JSON object (*e.g.* `{\"key\": \"value\"}`) or a colon-delimited set of assignments (*e.g.*\n * `key1=value1:key2=value2`).\n * @returns A promise which resolves to the details of the deployed circuit.\n *\n * @example\n * // Deploy a circuit with a project identifier and default `latest` tag.\n * const circuit = await client.createCircuit(\"/path/to/circuit-directory/\");\n * console.log(\"Did circuit compilation succeed?\", circuit.status);\n *\n * @example\n * // Deploy a circuit with files and custom tags.\n * await client.createCircuit([file1, file2], ['v1.0', 'experimental']);\n */\n async createCircuit(\n project: string | Array<BrowserFile | NodeFile>,\n tags: string | string[] | null = [\"latest\"],\n meta: Meta = {},\n ): Promise<CircuitInfoResponse> {\n const formData = new FormData();\n\n // First, validate the tags and them to the form data.\n tags = typeof tags === \"string\" ? [tags] : tags ?? [];\n for (const tag of tags) {\n if (!/^[-a-zA-Z0-9_.]+$/.test(tag)) {\n throw new Error(\n `\"${tag}\" is not a valid tag. Tags may only contain alphanumeric characters, ` +\n \"underscores, hyphens, and periods.\",\n );\n }\n formData.append(\"tags\", tag);\n }\n if (tags.length === 0) {\n formData.append(\"tags\", \"\");\n }\n\n // Validate and add the metadata.\n formData.append(\n \"meta\",\n JSON.stringify(validateMetaAndMergeWithDefaults(meta)),\n );\n\n // Handle `project` being a file or directory path.\n if (typeof project === \"string\") {\n if (process.env.BROWSER_BUILD) {\n throw new Error(\n \"Specifying `project` as a path is not allowed in the browser build.\",\n );\n }\n\n let projectStats;\n try {\n projectStats = await stat(project);\n } catch {\n throw new Error(\n `The \"${project}\" path does not exist or you do not have permission to access it.`,\n );\n }\n\n // If `project` is a path, then it's a prepackaged tarball.\n if (projectStats.isFile()) {\n if (!/\\.(zip|tar|tar\\.gz|tgz)$/i.test(project)) {\n throw new Error(\"Only gzipped tarballs or zip files are supported.\");\n }\n const tarballFilename = path.basename(project);\n const tarballContent = await readFile(project);\n (formData as NodeFormData).append(\n \"files\",\n new File([tarballContent], tarballFilename),\n );\n\n // If `project` is a directory, then we need to bundle it.\n } else if (projectStats.isDirectory()) {\n const sindriJsonPath = path.join(project, \"sindri.json\");\n let sindriJsonContent;\n try {\n sindriJsonContent = await readFile(sindriJsonPath, {\n encoding: \"utf-8\",\n });\n } catch {\n throw new Error(\n `Expected Sindri manifest file at \"${sindriJsonPath}\" does not exist.`,\n );\n }\n let sindriJson;\n try {\n sindriJson = JSON.parse(sindriJsonContent) as { name: string };\n } catch {\n throw new Error(\n `Could not parse \"${sindriJsonPath}\", is it valid JSON?`,\n );\n }\n const circuitName = sindriJson?.name;\n if (!circuitName) {\n throw new Error(\n `No circuit \"name\" field was found in \"${sindriJsonPath}\", the manifest is invalid.`,\n );\n }\n\n // Create a tarball with all the files that should be included from the project.\n const files = walk\n .sync({\n follow: true,\n ignoreFiles: [\".sindriignore\"],\n path: project,\n })\n .filter(\n (file) =>\n // Always exclude `.git` subdirectories.\n !/(^|\\/)\\.git(\\/|$)/.test(file),\n );\n // Always include the `sindri.json` file.\n const sindriJsonFilename = path.basename(sindriJsonPath);\n if (!files.includes(sindriJsonFilename)) {\n files.push(sindriJsonFilename);\n }\n const tarballFilename = `${circuitName}.tar.gz`;\n files.sort((a, b) => a.localeCompare(b)); // Deterministic for tests.\n const tarStream = tar.c(\n {\n cwd: project,\n gzip: true,\n onwarn: (code: string, message: string) => {\n this.logger.warn(`While creating tarball: ${code} - ${message}`);\n },\n prefix: `${circuitName}/`,\n sync: true,\n },\n files,\n // This works around a bug in the typing of `tar` when using `sync`.\n ) as unknown as Readable;\n\n // Add the tarball to the form data.\n (formData as NodeFormData).append(\n \"files\",\n new File([tarStream.read()], tarballFilename),\n );\n } else {\n throw new Error(`The \"${project}\" path is not a file or directory.`);\n }\n\n // Handle an array of files.\n } else if (Array.isArray(project)) {\n // Validate the file array.\n if (!project.every((file) => file instanceof File)) {\n throw new Error(\"All entries in `project` must be `File` instances.\");\n }\n const sindriJsonFile = project.find(\n (file) => file.name === \"sindri.json\",\n );\n if (!sindriJsonFile) {\n throw new Error(\n \"The `project` array must include a `sindri.json` file.\",\n );\n }\n let sindriJson;\n try {\n sindriJson = JSON.parse(await sindriJsonFile.text()) as {\n name: string;\n };\n } catch {\n throw new Error(`Could not parse \"sindri.json\", is it valid JSON?`);\n }\n const circuitName = sindriJson?.name;\n if (!circuitName) {\n throw new Error(\n `No circuit \"name\" field was found in \"sindri.json\", the manifest is invalid.`,\n );\n }\n\n // Create the gzipped tarball.\n const tarball = new Tar();\n project.sort((a, b) => a.name.localeCompare(b.name)); // Deterministic for tests.\n for (const file of project) {\n const content = new Uint8Array(await file.arrayBuffer());\n await new Promise((resolve) =>\n tarball.append(`${circuitName}/${file.name}`, content, resolve),\n );\n }\n const gzippedTarball = new Uint8Array(gzip.zip(tarball.out));\n const tarFile = new File([gzippedTarball], `${circuitName}.tar.gz`);\n\n // Append the tarball to the form data.\n // These lines are functionally identical, but we want to typecheck node and browser.\n if (process.env.BROWSER_BUILD) {\n (formData as BrowserFormData).append(\"files\", tarFile as BrowserFile);\n } else {\n (formData as NodeFormData).append(\"files\", tarFile as NodeFile);\n }\n }\n\n const createResponse = await this._client.circuits.circuitCreate(\n formData as NodeFormData,\n );\n const circuitId = createResponse.circuit_id;\n\n while (true) {\n const response: CircuitStatusResponse =\n await this._client.internal.circuitStatus(circuitId);\n if (response.status === \"Ready\" || response.status === \"Failed\") {\n break;\n }\n\n await new Promise((resolve) => setTimeout(resolve, this.pollingInterval));\n }\n return this._client.circuits.circuitDetail(circuitId, false);\n }\n\n /**\n * Retrieves all proofs associated with a specified circuit. This method is essential for\n * obtaining a comprehensive list of proofs generated for a given circuit, identified by its\n * unique circuit ID. It returns an array of `ProofInfoResponse` objects, each representing a\n * proof associated with the circuit.\n *\n * The method is particularly useful in scenarios where tracking or auditing all proofs of a\n * circuit is necessary. This could include verifying the integrity of proofs, understanding their\n * usage, or simply enumerating them for record-keeping.\n *\n * The `circuitId` parameter is a string that uniquely identifies the circuit in question. It's\n * crucial to provide the correct circuit ID to retrieve the corresponding proofs accurately.\n *\n * @param circuitId - The unique identifier of the circuit for which proofs are to be retrieved.\n * @returns A promise that resolves to an array of details for each associated proof.\n *\n * @example\n * const proofs = await client.getAllCircuitProofs(circuitId);\n * console.log(\"Proofs:', proofs);\n */\n async getAllCircuitProofs(circuitId: string): Promise<ProofInfoResponse[]> {\n return await this._client.circuits.circuitProofs(circuitId);\n }\n\n /**\n * Retrieves all circuits associated with the team. This method fetches a list of all circuits\n * that have been created or accessed by the currently authenticated team. It's a key method for\n * managing and monitoring circuit usage within a team, offering insights into the variety and\n * scope of circuits in use.\n *\n * @returns A promise that resolves to an array of circuit information responses.\n *\n * @example\n * const circuits = await = client.getAllCircuits();\n * console.log(\"Circuits:\", circuits);\n */\n async getAllCircuits(): Promise<CircuitInfoResponse[]> {\n return await this._client.circuits.circuitList();\n }\n\n /**\n * Retrieves a specific circuit using its unique circuit ID. This method is crucial for obtaining\n * detailed information about a particular circuit, identified by the provided `circuitId`. It's\n * especially useful when detailed insights or operations on a single circuit are required, rather\n * than handling multiple circuits.\n *\n * *Note:* In case the provided `circuitId` is invalid or does not correspond to an existing circuit,\n * the promise may reject, indicating an error. Proper error handling is therefore essential when using this method.\n *\n * @param circuitId - The unique identifier of the circuit to retrieve.\n * @returns A promise that resolves to the information about the specified circuit.\n *\n * @example\n * const circuit = await client.getCircuit(circuitId);\n * console.log('Circuit details:', circuit);\n */\n async getCircuit(circuitId: string): Promise<CircuitInfoResponse> {\n return await this._client.circuits.circuitDetail(circuitId);\n }\n\n /**\n * Retrieves detailed information about a specific proof, identified by its unique proof ID. This\n * method is vital for obtaining individual proof details, facilitating in-depth analysis or\n * verification of a particular proof within the system.\n *\n * The `proofId` parameter is the key identifier for the proof, and it should be provided to fetch\n * the corresponding information. The method returns a promise that resolves to a\n * {@link ProofInfoResponse}, containing all relevant details of the proof.\n *\n * @param proofId - The unique identifier of the proof to retrieve.\n * @returns A promise that resolves to the data about the specified proof.\n *\n * @example\n * const proof = await client.getProof(proofId);\n * console.log(\"Proof details:\", proof);\n */\n async getProof(proofId: string): Promise<ProofInfoResponse> {\n return await this._client.proofs.proofDetail(proofId);\n }\n\n /**\n * Generates a proof for a specified circuit. This method is critical for creating a new proof\n * based on a given circuit, identified by `circuitId`, and the provided `proofInput`. It's\n * primarily used to validate or verify certain conditions or properties of the circuit without\n * revealing underlying data or specifics. The method continuously polls the service to track the\n * compilation status until the process either completes successfully or fails.\n *\n * The `circuitId` parameter specifies the unique identifier of the circuit for which the proof is\n * to be generated. The `proofInput` is a string that represents the necessary input data or\n * parameters required for generating the proof.\n *\n * @param circuitId - The unique identifier of the circuit for which the proof is being generated.\n * @param proofInput - The input data required for generating the proof. This should be a string\n * containing either JSON data or TOML data (in the case of Noir).\n * @param verify - A boolean indicating whether to perform a verification check of the generated\n * proof.\n * @param includeSmartContractCalldata - A boolean indicating whether to include calldata for the\n * proof that can be passed into a smart contract for verification. Note that not all frameworks\n * support this.\n * @param meta - An object containing metadata to associate with the proof. This will be merged\n * into any metadata specified in the `SINDRI_META` environment variable. This variable can be a\n * JSON object (*e.g.* `{\"key\": \"value\"}`) or a colon-delimited set of assignments (*e.g.*\n * `key1=value1:key2=value2`).\n * @returns A promise that resolves to the information of the generated proof.\n *\n * @example\n * const proof = await client.proveCircuit(circuitId, '{\"X\": 23, \"Y\": 52}');\n * console.log(\"Generated proof:\", proof);\n */\n async proveCircuit(\n circuitId: string,\n proofInput: string,\n verify: boolean = false,\n includeSmartContractCalldata: boolean = false,\n meta: Meta = {},\n ): Promise<ProofInfoResponse> {\n const createResponse = await this._client.circuits.proofCreate(circuitId, {\n meta: validateMetaAndMergeWithDefaults(meta), // This will raise an error if it's invalid.\n perform_verify: verify,\n proof_input: proofInput,\n });\n const proofId: string = createResponse.proof_id;\n while (true) {\n const response: ProofStatusResponse =\n await this._client.internal.proofStatus(proofId);\n if (response.status === \"Ready\" || response.status === \"Failed\") {\n break;\n }\n\n await new Promise((resolve) => setTimeout(resolve, this.pollingInterval));\n }\n return this._client.proofs.proofDetail(\n proofId,\n true, // includeProof\n true, // includePublic\n includeSmartContractCalldata, // includeSmartContractCalldata\n true, // includeVerificationKey\n );\n }\n}\n","/* generated using openapi-typescript-codegen -- do no edit */\n/* istanbul ignore file */\n/* tslint:disable */\n/* eslint-disable */\nimport type { ApiRequestOptions } from \"./ApiRequestOptions\";\nimport type { CancelablePromise } from \"./CancelablePromise\";\nimport type { OpenAPIConfig } from \"./OpenAPI\";\n\nexport abstract class BaseHttpRequest {\n constructor(public readonly config: OpenAPIConfig) {}\n\n public abstract request<T>(options: ApiRequestOptions): CancelablePromise<T>;\n}\n","/* This file was originally generated by `openapi-typescript-codegen`, but we've customized it and\n * it is no longer regenerated. The key changes:\n *\n * * Support pre-constructed `FormData` instances, and make `FormData` isomorphic.\n * * Add request/response logging for all API requests in `request()`.\n */\n\n/* istanbul ignore file */\n/* tslint:disable */\n/* eslint-disable */\nimport axios from \"axios\";\nimport type { AxiosRequestConfig, AxiosResponse, AxiosInstance } from \"axios\";\nimport { AxiosError } from \"axios\";\nimport pRetry from \"@fullstax/p-retry\";\n\n// Manual edit to use our isomorphic `FormData`.\nimport { FormData } from \"lib/isomorphic\";\n\nimport { ApiError } from \"./ApiError\";\nimport type { ApiRequestOptions } from \"./ApiRequestOptions\";\nimport type { ApiResult } from \"./ApiResult\";\nimport { CancelablePromise } from \"./CancelablePromise\";\nimport type { OnCancel } from \"./CancelablePromise\";\nimport type { OpenAPIConfig } from \"./OpenAPI\";\n\nexport const isDefined = <T>(\n value: T | null | undefined,\n): value is Exclude<T, null | undefined> => {\n return value !== undefined && value !== null;\n};\n\nexport const isString = (value: any): value is string => {\n return typeof value === \"string\";\n};\n\nexport const isStringWithValue = (value: any): value is string => {\n return isString(value) && value !== \"\";\n};\n\nexport const isBlob = (value: any): value is Blob => {\n return (\n typeof value === \"object\" &&\n typeof value.type === \"string\" &&\n typeof value.stream === \"function\" &&\n typeof value.arrayBuffer === \"function\" &&\n typeof value.constructor === \"function\" &&\n typeof value.constructor.name === \"string\" &&\n /^(Blob|File)$/.test(value.constructor.name) &&\n /^(Blob|File)$/.test(value[Symbol.toStringTag])\n );\n};\n\nexport const isFormData = (value: any): value is FormData => {\n return value instanceof FormData;\n};\n\nexport const isSuccess = (status: number): boolean => {\n return status >= 200 && status < 300;\n};\n\nexport const base64 = (str: string): string => {\n try {\n return btoa(str);\n } catch (err) {\n // @ts-ignore\n return Buffer.from(str).toString(\"base64\");\n }\n};\n\nexport const getQueryString = (params: Record<string, any>): string => {\n const qs: string[] = [];\n\n const append = (key: string, value: any) => {\n qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);\n };\n\n const process = (key: string, value: any) => {\n if (isDefined(value)) {\n if (Array.isArray(value)) {\n value.forEach((v) => {\n process(key, v);\n });\n } else if (typeof value === \"object\") {\n Object.entries(value).forEach(([k, v]) => {\n process(`${key}[${k}]`, v);\n });\n } else {\n append(key, value);\n }\n }\n };\n\n Object.entries(params).forEach(([key, value]) => {\n process(key, value);\n });\n\n if (qs.length > 0) {\n return `?${qs.join(\"&\")}`;\n }\n\n return \"\";\n};\n\nconst getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {\n const encoder = config.ENCODE_PATH || encodeURI;\n\n const path = options.url\n .replace(\"{api-version}\", config.VERSION)\n .replace(/{(.*?)}/g, (substring: string, group: string) => {\n if (options.path?.hasOwnProperty(group)) {\n return encoder(String(options.path[group]));\n }\n return substring;\n });\n\n const url = `${config.BASE}${path}`;\n if (options.query) {\n return `${url}${getQueryString(options.query)}`;\n }\n return url;\n};\n\nexport const getFormData = (\n options: ApiRequestOptions,\n): FormData | undefined => {\n if (options.formData) {\n // This is a manual edit to allow `FormData` to be passed in directly.\n if (options.formData instanceof FormData) {\n return options.formData;\n }\n\n const formData = new FormData();\n\n const process = (key: string, value: any) => {\n if (isString(value) || isBlob(value)) {\n formData.append(key, value);\n } else {\n formData.append(key, JSON.stringify(value));\n }\n };\n\n Object.entries(options.formData)\n .filter(([_, value]) => isDefined(value))\n .forEach(([key, value]) => {\n if (Array.isArray(value)) {\n value.forEach((v) => process(key, v));\n } else {\n process(key, value);\n }\n });\n\n return formData;\n }\n return undefined;\n};\n\ntype Resolver<T> = (options: ApiRequestOptions) => Promise<T>;\n\nexport const resolve = async <T>(\n options: ApiRequestOptions,\n resolver?: T | Resolver<T>,\n): Promise<T | undefined> => {\n if (typeof resolver === \"function\") {\n return (resolver as Resolver<T>)(options);\n }\n return resolver;\n};\n\nexport const getHeaders = async (\n config: OpenAPIConfig,\n options: ApiRequestOptions,\n formData?: FormData,\n): Promise<Record<string, string>> => {\n const token = await resolve(options, config.TOKEN);\n const username = await resolve(options, config.USERNAME);\n const password = await resolve(options, config.PASSWORD);\n const additionalHeaders = await resolve(options, config.HEADERS);\n // Manual edit to support `FormData` implementations that don't include `getHeaders()`.\n const formHeaders =\n (formData &&\n \"getHeaders\" in formData &&\n typeof formData?.getHeaders === \"function\" &&\n formData?.getHeaders()) ||\n {};\n\n const headers = Object.entries({\n Accept: \"application/json\",\n ...additionalHeaders,\n ...options.headers,\n ...formHeaders,\n })\n .filter(([_, value]) => isDefined(value))\n .reduce(\n (headers, [key, value]) => ({\n ...headers,\n [key]: String(value),\n }),\n {} as Record<string, string>,\n );\n\n if (isStringWithValue(token)) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n\n if (isStringWithValue(username) && isStringWithValue(password)) {\n const credentials = base64(`${username}:${password}`);\n headers[\"Authorization\"] = `Basic ${credentials}`;\n }\n\n if (options.body) {\n if (options.mediaType) {\n headers[\"Content-Type\"] = options.mediaType;\n } else if (isBlob(options.body)) {\n headers[\"Content-Type\"] = options.body.type || \"application/octet-stream\";\n } else if (isString(options.body)) {\n headers[\"Content-Type\"] = \"text/plain\";\n } else if (!isFormData(options.body)) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n }\n\n return headers;\n};\n\nexport const getRequestBody = (options: ApiRequestOptions): any => {\n if (options.body) {\n return options.body;\n }\n return undefined;\n};\n\nconst shouldRetry = (error: Error): boolean =>\n !!(\n axios.isAxiosError(error) &&\n ((error.response?.status &&\n [502, 503, 504].includes(error.response.status)) ||\n (error.code &&\n [\n // Some of these codes aren't on `AxiosError` for some reason, see:\n // * https://github.com/axios/axios/issues/4894#issuecomment-2062064639\n \"ECONNREFUSED\",\n \"ECONNRESET\",\n \"ENOTFOUND\",\n \"ENETUNREACH\",\n AxiosError.ECONNABORTED,\n AxiosError.ERR_NETWORK,\n ].includes(error.code)))\n );\n\nexport const sendRequest = async <T>(\n config: OpenAPIConfig,\n options: ApiRequestOptions,\n url: string,\n body: any,\n formData: FormData | undefined,\n headers: Record<string, string>,\n onCancel: OnCancel,\n axiosClient: AxiosInstance,\n): Promise<AxiosResponse<T>> => {\n const source = axios.CancelToken.source();\n\n const requestConfig: AxiosRequestConfig = {\n url,\n headers,\n data: body ?? formData,\n method: options.method,\n withCredentials: config.WITH_CREDENTIALS,\n cancelToken: source.token,\n responseType: options.responseType,\n };\n\n onCancel(() => source.cancel(\"The user aborted a request.\"));\n\n try {\n if (!config.sindri) {\n return await axiosClient.request(requestConfig);\n }\n return await pRetry(() => axiosClient.request(requestConfig), {\n ...config.sindri.retryOptions,\n onFailedAttempt: (error) => {\n // Don't log anything if we're not going to retry the request, the error will get logged\n // further up the stack in that case.\n if (!shouldRetry(error)) return;\n\n config.sindri!.logger.debug(\n {\n attemptNumber: error.attemptNumber,\n error: error.message,\n retriesLeft: error.retriesLeft,\n },\n `${options.method} ${url} - Request failed, ` +\n `${error.retriesLeft > 0 ? \"retrying\" : \"aborting...\"}...`,\n );\n },\n shouldRetry,\n });\n } catch (error) {\n const axiosError = error as AxiosError<T>;\n if (axiosError.response) {\n return axiosError.response;\n }\n throw error;\n }\n};\n\nexport const getResponseHeader = (\n response: AxiosResponse<any>,\n responseHeader?: string,\n): string | undefined => {\n if (responseHeader) {\n const content = response.headers[responseHeader];\n if (isString(content)) {\n return content;\n }\n }\n return undefined;\n};\n\nexport const getResponseBody = (response: AxiosResponse<any>): any => {\n if (response.status !== 204) {\n return response.data;\n }\n return undefined;\n};\n\nexport const catchErrorCodes = (\n options: ApiRequestOptions,\n result: ApiResult,\n): void => {\n const errors: Record<number, string> = {\n 400: \"Bad Request\",\n 401: \"Unauthorized\",\n 403: \"Forbidden\",\n 404: \"Not Found\",\n 500: \"Internal Server Error\",\n 502: \"Bad Gateway\",\n 503: \"Service Unavailable\",\n ...options.errors,\n };\n\n const error = errors[result.status];\n if (error) {\n throw new ApiError(options, result, error);\n }\n\n if (!result.ok) {\n const errorStatus = result.status ?? \"unknown\";\n const errorStatusText = result.statusText ?? \"unknown\";\n const errorBody = (() => {\n try {\n return JSON.stringify(result.body, null, 2);\n } catch (e) {\n return undefined;\n }\n })();\n\n throw new ApiError(\n options,\n result,\n `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`,\n );\n }\n};\n\n/**\n * Request method\n * @param config The OpenAPI configuration object\n * @param options The request options from the service\n * @param axiosClient The axios client instance to use\n * @returns CancelablePromise<T>\n * @throws ApiError\n */\nexport const request = <T>(\n config: OpenAPIConfig,\n options: ApiRequestOptions,\n axiosClient: AxiosInstance = axios,\n): CancelablePromise<T> => {\n return new CancelablePromise(async (resolve, reject, onCancel) => {\n // Get a nicely formatted timedelta to display after requests.\n const startTime = Date.now();\n const getElapsedTime = (): string => {\n const ellapsedMilliseconds = Date.now() - startTime;\n if (ellapsedMilliseconds < 1000) {\n return `${ellapsedMilliseconds} ms`;\n }\n const ellapsedSeconds = ellapsedMilliseconds / 1000;\n if (ellapsedSeconds < 60) {\n return `${ellapsedSeconds.toFixed(2)} s`;\n }\n const ellapsedMinutes = ellapsedSeconds / 60;\n if (ellapsedMinutes < 60) {\n return `${ellapsedMinutes.toFixed(2)} m`;\n }\n const ellapsedHours = ellapsedMinutes / 60;\n return `${ellapsedHours.toFixed(2)} h`;\n };\n\n const url = getUrl(config, options);\n const logPrefix = `${options.method} ${url}`;\n try {\n const formData = getFormData(options);\n const body = getRequestBody(options);\n const headers = await getHeaders(config, options, formData);\n\n if (!onCancel.isCancelled) {\n config.sindri?.logger.debug(`${logPrefix} requested`);\n const response = await sendRequest<T>(\n config,\n options,\n url,\n body,\n formData,\n headers,\n onCancel,\n axiosClient,\n );\n const responseBody = getResponseBody(response);\n const responseHeader = getResponseHeader(\n response,\n options.responseHeader,\n );\n\n const result: ApiResult = {\n url,\n ok: isSuccess(response.status),\n status: response.status,\n statusText: response.statusText,\n body: responseHeader ?? responseBody,\n };\n const responseMessage = `${logPrefix} ${response.status} ${\n response.statusText\n } (${getElapsedTime()})`;\n if (!result.body || typeof result.body === \"string\") {\n config.sindri?.logger.debug(\n `${responseMessage} - ${result.body || \"<empty-body>\"}`,\n );\n } else if (options.responseType === \"stream\") {\n config.sindri?.logger.debug(\n `${responseMessage} - <streaming-response>`,\n );\n } else if (options.responseType === \"blob\") {\n config.sindri?.logger.debug(`${responseMessage} - <blob-response>`);\n } else {\n config.sindri?.logger.debug(result.body, responseMessage);\n }\n\n catchErrorCodes(options, result);\n\n resolve(result.body);\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n config.sindri?.logger.debug(\n `${logPrefix} ERROR (${getElapsedTime()}) - ${errorMessage}`,\n );\n reject(error);\n }\n });\n};\n","import { File as NodeFile } from \"buffer\";\n\nimport { FormData as NodeFormData } from \"formdata-node\";\n\nexport function assertType<T>(value: unknown) {\n function isType<T>(value: unknown): value is T {\n return true || value;\n }\n if (!isType<T>(value)) throw new Error(\"Impossible.\");\n}\n\nexport type { NodeFile, NodeFormData };\nexport type BrowserFile = File;\nexport type BrowserFormData = FormData;\n\nexport const File = process.env.BROWSER_BUILD ? window.File : NodeFile;\nexport const FormData = process.env.BROWSER_BUILD\n ? window.FormData\n : NodeFormData;\n","/* generated using openapi-typescript-codegen -- do no edit */\n/* istanbul ignore file */\n/* tslint:disable */\n/* eslint-disable */\nimport type { ApiRequestOptions } from \"./ApiRequestOptions\";\nimport type { ApiResult } from \"./ApiResult\";\n\nexport class ApiError extends Error {\n public readonly url: string;\n public readonly status: number;\n public readonly statusText: string;\n public readonly body: any;\n public readonly request: ApiRequestOptions;\n\n constructor(\n request: ApiRequestOptions,\n response: ApiResult,\n message: string,\n ) {\n super(message);\n\n this.name = \"ApiError\";\n this.url = response.url;\n this.status = response.status;\n this.statusText = response.statusText;\n this.body = response.body;\n this.request = request;\n }\n}\n","/* generated using openapi-typescript-codegen -- do no edit */\n/* istanbul ignore file */\n/* tslint:disable */\n/* eslint-disable */\nexport class CancelError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CancelError\";\n }\n\n public get isCancelled(): boolean {\n return true;\n }\n}\n\nexport interface OnCancel {\n readonly isResolved: boolean;\n readonly isRejected: boolean;\n readonly isCancelled: boolean;\n\n (cancelHandler: () => void): void;\n}\n\nexport class CancelablePromise<T> implements Promise<T> {\n #isResolved: boolean;\n #isRejected: boolean;\n #isCancelled: boolean;\n readonly #cancelHandlers: (() => void)[];\n readonly #promise: Promise<T>;\n #resolve?: (value: T | PromiseLike<T>) => void;\n #reject?: (reason?: any) => void;\n\n constructor(\n executor: (\n resolve: (value: T | PromiseLike<T>) => void,\n reject: (reason?: any) => void,\n onCancel: OnCancel,\n ) => void,\n ) {\n this.#isResolved = false;\n this.#isRejected = false;\n this.#isCancelled = false;\n this.#cancelHandlers = [];\n this.#promise = new Promise<T>((resolve, reject) => {\n this.#resolve = resolve;\n this.#reject = reject;\n\n const onResolve = (value: T | PromiseLike<T>): void => {\n if (this.#isResolved || this.#isRejected || this.#isCancelled) {\n return;\n }\n this.#isResolved = true;\n this.#resolve?.(value);\n };\n\n const onReject = (reason?: any): void => {\n if (this.#isResolved || this.#isRejected || this.#isCancelled) {\n return;\n }\n this.#isRejected = true;\n this.#reject?.(reason);\n };\n\n const onCancel = (cancelHandler: () => void): void => {\n if (this.#isResolved || this.#isRejected || this.#isCancelled) {\n return;\n }\n this.#cancelHandlers.push(cancelHandler);\n };\n\n Object.defineProperty(onCancel, \"isResolved\", {\n get: (): boolean => this.#isResolved,\n });\n\n Object.defineProperty(onCancel, \"isRejected\", {\n get: (): boolean => this.#isRejected,\n });\n\n Object.defineProperty(onCancel, \"isCancelled\", {\n get: (): boolean => this.#isCancelled,\n });\n\n return executor(onResolve, onReject, onCancel as OnCancel);\n });\n }\n\n get [Symbol.toStringTag]() {\n return \"Cancellable Promise\";\n }\n\n public then<TResult1 = T, TResult2 = never>(\n onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,\n onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.#promise.then(onFulfilled, onRejected);\n }\n\n public catch<TResult = never>(\n onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,\n ): Promise<T | TResult> {\n return this.#promise.catch(onRejected);\n }\n\n public finally(onFinally?: (() => void) | null): Promise<T> {\n return this.#promise.finally(onFinally);\n }\n\n public cancel(): void {\n if (this.#isResolved || this.#isRejected || this.#isCancelled) {\n return;\n }\n this.#isCancelled = true;\n if (this.#cancelHandlers.length) {\n try {\n for (const cancelHandler of this.#cancelHandlers) {\n cancelHandler();\n }\n } catch (error) {\n console.warn(\"Cancellation threw an error\", error);\n return;\n }\n }\n this.#cancelHandlers.length = 0;\n this.#reject?.(new CancelError(\"Request aborted\"));\n }\n\n public get isCancelled(): boolean {\n return this.#isCancelled;\n }\n}\n","/* generated using openapi-typescript-codegen -- do no edit */\n/* istanbul ignore file */\n/* tslint:disable */\n/* eslint-disable */\nimport type { ApiRequestOptions } from \"./ApiRequestOptions\";\nimport { BaseHttpRequest } from \"./BaseHttpRequest\";\nimport type { CancelablePromise } from \"./CancelablePromise\";\nimport type { OpenAPIConfig } from \"./OpenAPI\";\nimport { request as __request } from \"./request\";\n\nexport class AxiosHttpRequest extends BaseHttpRequest {\n constructor(config: OpenAPIConfig) {\n super(config);\n }\n\n /**\n * Request method\n * @param options The request options from the service\n * @returns CancelablePromise<T>\n * @throws ApiError\n */\n public override request<T>(options: ApiRequestOptions): CancelablePromise<T> {\n return __request(this.config,