zkverifyjs
Version:
Submit proofs to zkVerify and query proof state with ease using our npm package.
131 lines (130 loc) • 5.8 kB
JavaScript
import { subscribeToNewAggregationReceipts, unsubscribe, } from '../../../api/aggregation';
import { EventEmitter } from 'events';
import { PUBLIC_ZK_VERIFY_EVENTS, ZkVerifyEvents } from '../../../enums';
export class EventManager {
constructor(connectionManager) {
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) {
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);
}
}
}
})
.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() {
unsubscribe(this.emitter);
}
}