zkverifyjs
Version:
Submit proofs to zkVerify and query proof state with ease using our npm package.
149 lines • 5.55 kB
JavaScript
import { subscribeToNewAggregationReceipts, unsubscribe } from "../../../api/aggregation/index.js";
import { EventEmitter } from 'events';
import { PUBLIC_ZK_VERIFY_EVENTS, ZkVerifyEvents } from "../../../enums.js";
export class EventManager {
constructor(connectionManager) {
this.unsubscribeFunctions = [];
this.connectionManager = connectionManager;
this.emitter = new EventEmitter();
}
/**
* Subscribes to specified ZkVerifyEvents.
* For `NewAggregationReceipt`, `options` can include `domainId` and `aggregationId`.
* For runtime events (e.g., ProofVerified), options are ignored.
*
* @param subscriptions - List of events to subscribe to with optional callback and filtering options.
* @returns EventEmitter to allow listening to additional internal events (e.g., `Unsubscribe`).
*/
subscribe(subscriptions) {
const {
api
} = this.connectionManager;
const eventsToSubscribe = subscriptions?.length ? subscriptions : PUBLIC_ZK_VERIFY_EVENTS.map(event => ({
event,
callback: undefined,
options: event === ZkVerifyEvents.NewAggregationReceipt ? undefined : undefined
}));
eventsToSubscribe.forEach(({
event,
callback,
options
}) => {
switch (event) {
case ZkVerifyEvents.NewAggregationReceipt:
subscribeToNewAggregationReceipts(api, data => {
this.emitter.emit(event, data);
if (callback) callback(data);
}, options, this.emitter);
break;
case ZkVerifyEvents.ProofVerified:
case ZkVerifyEvents.NewProof:
case ZkVerifyEvents.VkRegistered:
case ZkVerifyEvents.NewDomain:
case ZkVerifyEvents.DomainStateChanged:
case ZkVerifyEvents.AggregationComplete:
this._subscribeToRuntimeEvent(api, event, callback);
break;
default:
throw new Error(`Unsupported event type for subscription: ${event}`);
}
});
return this.emitter;
}
/**
* Subscribes to on-chain runtime events using api.query.system.events
*/
_subscribeToRuntimeEvent(api, eventType, callback) {
const unsubscribeFn = api.query.system.events(records => {
for (const {
event,
phase
} of records) {
const key = `${event.section}::${event.method}`;
const matchMap = {
[ZkVerifyEvents.ProofVerified]: /::ProofVerified/,
[ZkVerifyEvents.CannotAggregate]: 'aggregate::CannotAggregate',
[ZkVerifyEvents.NewProof]: 'aggregate::NewProof',
[ZkVerifyEvents.VkRegistered]: /::VkRegistered/,
[ZkVerifyEvents.AggregationComplete]: 'aggregate::AggregationComplete',
[ZkVerifyEvents.NewDomain]: 'aggregate::NewDomain',
[ZkVerifyEvents.DomainStateChanged]: 'aggregate::DomainStateChanged'
};
const expected = matchMap[eventType];
if (expected && (typeof expected === 'string' && key === expected || expected instanceof RegExp && expected.test(key))) {
const parsedPhase = phase.toJSON ? phase.toJSON() : phase.toString();
const eventPayload = {
event: eventType,
data: event.data.toHuman?.() ?? event.data.toString(),
phase: parsedPhase
};
this.emitter.emit(eventType, eventPayload);
if (callback) {
callback(eventPayload);
}
}
}
});
if (unsubscribeFn) {
if (typeof unsubscribeFn === 'function') {
this.unsubscribeFunctions.push(unsubscribeFn);
} else if (unsubscribeFn && typeof unsubscribeFn.then === 'function') {
unsubscribeFn.then(fn => {
if (typeof fn === 'function') {
this.unsubscribeFunctions.push(fn);
}
}).catch(error => {
this.emitter.emit(ZkVerifyEvents.ErrorEvent, error);
});
}
}
}
/**
* Waits for a specific `NewAggregationReceipt` event and returns the result as a NewAggregationReceipt object.
*
* @param domainId - The domain ID to listen for.
* @param aggregationId - The aggregation ID to listen for.
* @param timeout - Optional timeout value in milliseconds.
* @returns {Promise<NewAggregationReceipt>} Resolves with the event data when found, or rejects on timeout/error.
*/
async waitForAggregationReceipt(domainId, aggregationId, timeout) {
const {
api
} = this.connectionManager;
const options = {
domainId,
aggregationId,
timeout
};
return new Promise((resolve, reject) => {
subscribeToNewAggregationReceipts(api, eventObject => {
const event = eventObject;
if (event && event.data && event.data.domainId && event.data.aggregationId && event.data.receipt) {
const result = {
blockHash: event.blockHash ?? null,
domainId: Number(event.data.domainId),
aggregationId: Number(event.data.aggregationId),
receipt: String(event.data.receipt)
};
resolve(result);
} else {
reject(new Error('Invalid event data structure'));
}
}, options, this.emitter).catch(reject);
});
}
/**
* Unsubscribes from all active subscriptions.
*/
unsubscribe() {
this.unsubscribeFunctions.forEach(unsubscribeFn => {
try {
unsubscribeFn();
} catch (error) {
console.debug('Error during subscription cleanup:', error);
}
});
this.unsubscribeFunctions.length = 0;
unsubscribe(this.emitter);
}
}