zkverifyjs
Version:
Submit proofs to zkVerify and query proof state with ease using our npm package.
369 lines (368 loc) • 13.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeEmit = exports.extractErrorMessage = exports.getSelectedAccount = void 0;
exports.waitForNodeToSync = waitForNodeToSync;
exports.getProofProcessor = getProofProcessor;
exports.getProofPallet = getProofPallet;
exports.checkReadOnly = checkReadOnly;
exports.interpretDryRunResponse = interpretDryRunResponse;
exports.bindMethods = bindMethods;
exports.getKeyringAccountIfAvailable = getKeyringAccountIfAvailable;
exports.normalizeDeliveryFromOptions = normalizeDeliveryFromOptions;
exports.isGroth16Config = isGroth16Config;
exports.isPlonky2Config = isPlonky2Config;
exports.isRisc0Config = isRisc0Config;
exports.isUltraplonkConfig = isUltraplonkConfig;
exports.isUltrahonkConfig = isUltrahonkConfig;
exports.toSubmittableExtrinsic = toSubmittableExtrinsic;
const enums_1 = require("../../enums");
const config_1 = require("../../config");
const errors_1 = require("../transactions/errors");
__exportStar(require("./runtimeVersion"), exports);
/**
* Waits for the zkVerify node to sync.
* @param api - The ApiPromise instance.
* @returns A promise that resolves when the node is synced.
*/
async function waitForNodeToSync(api) {
let isSyncing = true;
while (isSyncing) {
const health = await api.rpc.system.health();
isSyncing = health.isSyncing.isTrue;
if (isSyncing) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
function getProofProcessor(proofType) {
const config = config_1.proofConfigurations[proofType];
if (!config) {
throw new Error(`No config found for Proof Processor: ${proofType}`);
}
return config.processor;
}
function getProofPallet(proofType) {
const config = config_1.proofConfigurations[proofType];
if (!config) {
throw new Error(`No config found for Proof Pallet: ${proofType}`);
}
return config.pallet;
}
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(config_1.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).
*/
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 } = (0, errors_1.decodeDispatchError)(api, dispatchErrorCodec);
const expectedPallet = proofType
? config_1.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,
};
}
/**
* Binds all methods from the source object to the target object,
* preserving the original `this` context.
*
* Throws an error if a method with the same name already exists on the target.
*
* @param target - The object to bind methods to.
* @param source - The object containing the methods to bind.
*
* @throws {Error} If a method with the same name already exists on the target.
*/
function bindMethods(target, source) {
const propertyNames = Object.getOwnPropertyNames(Object.getPrototypeOf(source));
for (const name of propertyNames) {
const method = source[name];
if (typeof method === 'function' && name !== 'constructor') {
if (name in target) {
throw new Error(`❌ Method collision detected: "${name}". Binding aborted.`);
}
target[name] = method.bind(source);
}
}
}
/**
* 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.
*/
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;
};
exports.getSelectedAccount = getSelectedAccount;
/**
* 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`.
*/
function getKeyringAccountIfAvailable(connection, accountAddress) {
return 'accounts' in connection
? (0, exports.getSelectedAccount)(connection, accountAddress)
: undefined;
}
/**
* Converts a `DeliveryInput` into a properly formatted `Delivery` object.
* Supports either a `None` variant or a `Hyperbridge` delivery configuration.
*
* @returns A `Delivery` object formatted for on-chain use.
* @throws {Error} If required fields for Hyperbridge delivery are missing or invalid.
* @param options
*/
function normalizeDeliveryFromOptions(options) {
if (options.destination === enums_1.Destination.None) {
return { None: null };
}
const { deliveryInput } = options;
return {
destination: {
Hyperbridge: {
destinationChain: deliveryInput.destinationChain,
destination_module: deliveryInput.destination_module,
timeout: deliveryInput.timeout,
},
},
price: deliveryInput.price,
};
}
/**
* 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.
*/
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;
}
return JSON.stringify(err);
}
return String(err);
};
exports.extractErrorMessage = extractErrorMessage;
/**
* Safe wrapper for emitting events without crashing.
*/
const safeEmit = (emitter, event, data) => {
try {
emitter.emit(event, data);
}
catch (error) {
console.debug(`Failed to emit event ${event}:`, error);
}
};
exports.safeEmit = safeEmit;
/**
* Type guard for Groth16Config
*/
function isGroth16Config(options) {
return (options.proofType === config_1.ProofType.groth16 &&
options.config !== undefined &&
options.config.library !== undefined &&
options.config.curve !== undefined);
}
/**
* Type guard for Plonky2Config
*/
function isPlonky2Config(options) {
return (options.proofType === config_1.ProofType.plonky2 &&
options.config !== undefined &&
options.config.hashFunction !== undefined);
}
/**
* Type guard for Risc0Config
*/
function isRisc0Config(options) {
return (options.proofType === config_1.ProofType.risc0 &&
options.config !== undefined &&
options.config.version !== undefined);
}
/**
* Type guard for Ultraplonk Config
*/
function isUltraplonkConfig(options) {
return (options.proofType === config_1.ProofType.ultraplonk &&
options.config !== undefined &&
options.config.numberOfPublicInputs !== undefined);
}
/**
* Type guard for Ultrahonk Config
*/
function isUltrahonkConfig(options) {
return (options.proofType === config_1.ProofType.ultrahonk &&
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.
*/
function toSubmittableExtrinsic(extrinsic, api) {
if (isSubmittableExtrinsic(extrinsic)) {
return extrinsic;
}
const call = api.createType('Call', extrinsic.method);
return api.tx(call);
}