zkverifyjs
Version:
Submit proofs to zkVerify and query proof state with ease using our npm package.
181 lines (180 loc) • 8.07 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.subscribeToNewAggregationReceipts = subscribeToNewAggregationReceipts;
exports.unsubscribe = unsubscribe;
const enums_1 = require("../../enums");
/**
* Subscribes to `aggregation.NewAggregationReceipt` events and triggers the provided callback.
*
* - If both `domainId` and `aggregationId` are provided, the listener stops after the matching receipt is found.
* - If only `domainId` is provided, listens indefinitely for all receipts within that domain.
* - If neither is provided, listens to all receipts across all domains.
* - Throws if `aggregationId` is provided without a `domainId`.
*
* @param {ApiPromise} api - The Polkadot.js API instance.
* @param callback
* @param options - NewAggregationEventSubscriptionOptions containing domainId, aggregationId and optional timeout.
* @param emitter - EventEmitter
* @returns {EventEmitter} EventEmitter for listening to emitted events and unsubscribing.
*/
async function subscribeToNewAggregationReceipts(api, callback, options = undefined, emitter) {
return new Promise((resolve, reject) => {
const DEFAULT_MATCH_TIMEOUT = 180000;
let domainId = undefined;
let aggregationId = undefined;
let timeoutId;
let unsubscribeFinalizedHeads;
let isResolved = false;
let isRejected = false;
const cleanup = () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = undefined;
}
if (unsubscribeFinalizedHeads) {
try {
unsubscribeFinalizedHeads();
}
catch (err) {
console.debug('Error during finalized heads cleanup:', err);
}
unsubscribeFinalizedHeads = undefined;
}
};
const safeResolve = (value) => {
if (!isResolved && !isRejected) {
isResolved = true;
cleanup();
resolve(value);
}
};
const safeReject = (error) => {
if (!isResolved && !isRejected) {
isRejected = true;
cleanup();
reject(error);
}
};
emitter._cleanup = cleanup;
emitter.once(enums_1.ZkVerifyEvents.Unsubscribe, cleanup);
if (options) {
domainId = options.domainId?.toString().trim();
if ('aggregationId' in options) {
aggregationId = options.aggregationId?.toString().trim();
if (!domainId) {
safeReject(new Error('Cannot filter by aggregationId without also providing domainId.'));
return;
}
}
}
if (aggregationId && domainId) {
const timeoutValue = options && 'timeout' in options && typeof options.timeout === 'number'
? options.timeout
: DEFAULT_MATCH_TIMEOUT;
timeoutId = setTimeout(() => {
unsubscribe(emitter);
safeReject(new Error(`Timeout exceeded: No event received within ${timeoutValue} ms`));
}, timeoutValue);
}
try {
const subscriptionResult = api.rpc.chain.subscribeFinalizedHeads(async (header) => {
const blockHash = header.hash.toHex();
const apiAt = await api.at(blockHash);
const events = (await apiAt.query.system.events());
events.forEach((record) => {
const { event, phase } = record;
if (event.section === 'aggregate' &&
event.method === 'NewAggregationReceipt') {
let currentDomainId;
let currentAggregationId;
const eventData = event.data.toHuman
? event.data.toHuman()
: Array.from(event.data, (item) => item.toString());
const eventObject = {
event: enums_1.ZkVerifyEvents.NewAggregationReceipt,
blockHash,
data: eventData,
phase: phase && typeof phase.toJSON === 'function'
? phase.toJSON()
: phase?.toString() || '',
};
try {
currentDomainId = event.data[0]?.toString();
currentAggregationId = event.data[1]?.toString();
if (!currentDomainId || !currentAggregationId) {
emitter.emit(enums_1.ZkVerifyEvents.ErrorEvent, new Error('Event data is missing required fields: domainId or aggregationId.'));
safeReject(new Error('Event data is missing required fields: domainId or aggregationId.'));
return;
}
}
catch (error) {
emitter.emit(enums_1.ZkVerifyEvents.ErrorEvent, error);
safeReject(error);
return;
}
if (!options || (!aggregationId && !domainId)) {
emitter.emit(enums_1.ZkVerifyEvents.NewAggregationReceipt, eventObject);
callback(eventObject);
}
else if (domainId &&
!aggregationId &&
domainId === currentDomainId) {
emitter.emit(enums_1.ZkVerifyEvents.NewAggregationReceipt, eventObject);
callback(eventObject);
}
else if (domainId === currentDomainId &&
currentAggregationId === aggregationId) {
emitter.emit(enums_1.ZkVerifyEvents.NewAggregationReceipt, eventObject);
callback(eventObject);
cleanup();
safeResolve(emitter);
return;
}
}
});
});
if (typeof subscriptionResult === 'function') {
unsubscribeFinalizedHeads = subscriptionResult;
}
else if (subscriptionResult &&
typeof subscriptionResult.then === 'function') {
subscriptionResult
.then((unsubscribeFn) => {
if (!isResolved &&
!isRejected &&
typeof unsubscribeFn === 'function') {
unsubscribeFinalizedHeads = unsubscribeFn;
}
})
.catch((error) => {
if (!isResolved && !isRejected) {
emitter.emit(enums_1.ZkVerifyEvents.ErrorEvent, error);
safeReject(error);
}
});
}
}
catch (error) {
emitter.emit(enums_1.ZkVerifyEvents.ErrorEvent, error);
safeReject(error);
}
return emitter;
});
}
/**
* Unsubscribes from all event tracking.
*
* - Emits a `ZkVerifyEvents.Unsubscribe` event before removing all listeners.
* - Use this to manually stop listening when not auto-unsubscribing on matched receipts.
*
* @param {EventEmitter} emitter - The EventEmitter instance returned by the subscription.
*/
function unsubscribe(emitter) {
const emitterWithCleanup = emitter;
if (emitterWithCleanup._cleanup) {
emitterWithCleanup._cleanup();
delete emitterWithCleanup._cleanup;
}
emitter.emit(enums_1.ZkVerifyEvents.Unsubscribe);
emitter.removeAllListeners();
}