zkverifyjs
Version:
Submit proofs to zkVerify and query proof state with ease using our npm package.
348 lines • 12.5 kB
JavaScript
import { proofConfigurations, ProofType, } from '../../config/index.js';
import { decodeDispatchError } from '../transactions/errors/index.js';
export * from './runtimeVersion/index.js';
/**
* Waits for the zkVerify node to finish syncing. Polls `system.health` at a
* fixed interval up to a bounded deadline; throws on timeout or abort.
*
* @param api - The ApiPromise instance.
* @param options - Optional timeout, poll interval, and abort signal.
* @returns A promise that resolves when the node is synced.
* @throws {Error} If the deadline is reached before the node finishes syncing.
* @throws {DOMException} `AbortError` if the supplied signal is aborted.
*/
export async function waitForNodeToSync(api, options = {}) {
const { timeoutMs = 300000, pollIntervalMs = 1000, signal } = options;
const deadline = Date.now() + timeoutMs;
while (true) {
if (signal?.aborted) {
throw new DOMException('waitForNodeToSync aborted', 'AbortError');
}
if (Date.now() > deadline) {
throw new Error(`waitForNodeToSync timed out after ${timeoutMs}ms; node still reports isSyncing.`);
}
const health = await api.rpc.system.health();
if (!health.isSyncing.isTrue)
return;
await sleep(pollIntervalMs, signal);
}
}
function sleep(ms, signal) {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(new DOMException('sleep aborted', 'AbortError'));
return;
}
const onAbort = () => {
clearTimeout(timer);
reject(new DOMException('sleep aborted', 'AbortError'));
};
const timer = setTimeout(() => {
signal?.removeEventListener('abort', onAbort);
resolve();
}, ms);
signal?.addEventListener('abort', onAbort, { once: true });
});
}
export function getProofProcessor(proofType) {
const config = proofConfigurations[proofType];
if (!config) {
throw new Error(`No config found for Proof Processor: ${proofType}`);
}
return config.processor;
}
export function getProofPallet(proofType) {
const config = proofConfigurations[proofType];
if (!config) {
throw new Error(`No config found for Proof Pallet: ${proofType}`);
}
return config.pallet;
}
export function checkReadOnly(connection) {
if ((!('accounts' in connection) ||
('accounts' in connection && connection.accounts.size === 0)) &&
!('injector' in connection)) {
throw new Error('This action requires an active account. The session is currently in read-only mode because no account is associated with it. Please provide an account at session start, or add one to the current session using `addAccount`.');
}
}
let cachedVerifierPallets = null;
function getVerifierPallets() {
if (cachedVerifierPallets)
return cachedVerifierPallets;
const entries = Object.values(proofConfigurations ?? {});
cachedVerifierPallets = new Set(entries.map((cfg) => cfg.pallet).filter(Boolean));
return cachedVerifierPallets;
}
/**
* Interprets a dry run response and returns whether it was successful and any error message.
* @param api - The Polkadot.js API instance.
* @param resultHex - The hex-encoded response from a dry run.
* @param proofType
* @returns An object containing `success` (boolean) and `message` (string).
*/
export async function interpretDryRunResponse(api, resultHex, proofType) {
const responseBytes = Uint8Array.from(Buffer.from(resultHex.replace(/^0x/, ''), 'hex'));
if (responseBytes.length === 0) {
return {
success: false,
type: 'unknown_error',
message: 'Empty dryRun response',
verificationError: false,
};
}
if (responseBytes[0] === 0x01) {
let validityError;
try {
validityError = api.registry.createType('TransactionValidityError', responseBytes.slice(1));
}
catch {
try {
validityError = api.registry.createType('SpRuntimeTransactionValidityError', responseBytes.slice(1));
}
catch {
validityError = null;
}
}
if (validityError?.isInvalid) {
const code = `InvalidTransaction.${validityError?.asInvalid.type}`;
return {
success: false,
type: 'validity_error',
code,
message: code,
verificationError: false,
};
}
if (validityError?.isUnknown) {
const code = `UnknownTransaction.${validityError?.asUnknown.type}`;
return {
success: false,
type: 'validity_error',
code,
message: code,
verificationError: false,
};
}
return {
success: false,
type: 'validity_error',
message: validityError?.toString() ?? 'TransactionValidityError',
verificationError: false,
};
}
if (responseBytes[0] === 0x00 && responseBytes.length >= 2) {
if (responseBytes[1] === 0x00) {
return {
success: true,
type: 'ok',
message: 'Optimistic Verification Successful!',
};
}
if (responseBytes[1] === 0x01) {
try {
const dispatchErrorCodec = api.registry.createType('DispatchError', responseBytes.slice(2));
const { code, message, section } = decodeDispatchError(api, dispatchErrorCodec);
const expectedPallet = proofType
? proofConfigurations?.[proofType]?.pallet
: undefined;
const isVerifier = !!section &&
(section === expectedPallet || getVerifierPallets().has(section));
return {
success: false,
type: 'dispatch_error',
code,
message,
verificationError: isVerifier,
};
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
return {
success: false,
type: 'exception',
message,
verificationError: false,
};
}
}
}
return {
success: false,
type: 'unknown_error',
message: `Unexpected response format: ${resultHex}`,
verificationError: false,
};
}
/**
* Retrieves the selected account from the connection based on the provided account address.
* If no account address is provided, it defaults to the first available account.
*
* @param {AccountConnection} connection - The connection containing account information.
* @param {string | undefined} accountAddress - The optional account address to retrieve.
* @returns {KeyringPair} - The selected account.
* @throws {Error} If the account is not found.
*/
export const getSelectedAccount = (connection, accountAddress) => {
let selectedAccount;
if (accountAddress) {
selectedAccount = connection.accounts.get(accountAddress);
}
else {
selectedAccount = Array.from(connection.accounts.values())[0];
}
if (!selectedAccount) {
throw new Error(`Account ${accountAddress ?? ''} not found in session.`);
}
return selectedAccount;
};
/**
* Retrieves the selected `KeyringPair` from the given connection if it is an `AccountConnection`.
*
* - If the connection has local `accounts` (i.e., it's an `AccountConnection`), it uses the provided `accountAddress`
* to select the appropriate account via `getSelectedAccount`.
* - If the connection is a `WalletConnection`, returns `undefined`.
*
* @param {AccountConnection | WalletConnection} connection - The connection object which may contain accounts.
* @param {string} [accountAddress] - Optional address of the account to select.
* @returns {KeyringPair | undefined} - The selected `KeyringPair` if available, otherwise `undefined`.
*/
export function getKeyringAccountIfAvailable(connection, accountAddress) {
return 'accounts' in connection
? getSelectedAccount(connection, accountAddress)
: undefined;
}
/**
* Extracts a human-readable error message from various error types.
*
* @param err - The error object to extract a message from.
* @returns A string message describing the error.
*/
export const extractErrorMessage = (err) => {
if (err instanceof Error) {
return err.message;
}
if (typeof err === 'object' && err !== null) {
const maybeError = err;
if (typeof maybeError.error === 'string') {
return maybeError.error;
}
try {
return JSON.stringify(err);
}
catch {
return String(err);
}
}
return String(err);
};
/**
* Safe wrapper for emitting events without crashing.
*/
export const safeEmit = (emitter, event, data) => {
try {
emitter.emit(event, data);
}
catch (error) {
console.debug(`Failed to emit event ${event}:`, error);
}
};
/**
* Type guard for Groth16Config
*/
export function isGroth16Config(options) {
return (options.proofType === ProofType.groth16 &&
options.config !== undefined &&
options.config.library !== undefined &&
options.config.curve !== undefined);
}
/**
* Type guard for Plonky2Config
*/
export function isPlonky2Config(options) {
return (options.proofType === ProofType.plonky2 &&
options.config !== undefined &&
options.config.hashFunction !== undefined);
}
/**
* Type guard for Risc0Config
*/
export function isRisc0Config(options) {
return (options.proofType === ProofType.risc0 &&
options.config !== undefined &&
options.config.version !== undefined);
}
/**
* Type guard for Ultraplonk Config
*/
export function isUltraplonkConfig(options) {
return (options.proofType === ProofType.ultraplonk &&
options.config !== undefined &&
options.config.numberOfPublicInputs !== undefined);
}
/**
* Type guard for Ultrahonk Config
*/
export function isUltrahonkConfig(options) {
return (options.proofType === ProofType.ultrahonk &&
options.config !== undefined &&
options.config.variant !== undefined);
}
/**
* Type guard for versioned Ultrahonk Config
*/
export function isVersionedUltrahonkConfig(options) {
return (isUltrahonkConfig(options) &&
options.config.version !== undefined);
}
/**
* Type guard for TEE Config
*/
export function isTeeConfig(options) {
return (options.proofType === ProofType.tee &&
options.config !== undefined &&
options.config.variant !== undefined);
}
// ADD_NEW_PROOF_TYPE if it has a config options object
/**
* Type guard to check if an object is a SubmittableExtrinsic<'promise'>.
*
* A SubmittableExtrinsic is identified by the presence of a `signAsync` method.
*
* @param obj - The object to evaluate.
* @returns True if the object is a SubmittableExtrinsic, otherwise false.
*/
function isSubmittableExtrinsic(obj) {
return (typeof obj === 'object' &&
obj !== null &&
'signAsync' in obj &&
typeof obj.signAsync === 'function');
}
/**
* Ensures the provided extrinsic is a SubmittableExtrinsic.
* Converts a raw Extrinsic to Submittable if needed using the given API instance.
*
* @param extrinsic - A SubmittableExtrinsic or raw Extrinsic.
* @param api - An instance of ApiPromise used to convert the extrinsic.
* @returns A SubmittableExtrinsic<'promise'> ready to be signed and submitted.
*/
export function toSubmittableExtrinsic(extrinsic, api) {
if (isSubmittableExtrinsic(extrinsic)) {
return extrinsic;
}
const call = api.createType('Call', extrinsic.method);
return api.tx(call);
}
/**
* Asserts that the given input is a 0x-prefixed hex string.
*
* @param input - The string to validate.
* @returns The input unchanged, for ergonomic inline use.
* @throws {Error} If the input does not start with `0x`.
*/
export function validateHexString(input) {
if (!input.startsWith('0x')) {
throw new Error('Invalid format: string input must be 0x-prefixed.');
}
return input;
}
//# sourceMappingURL=index.js.map