From 55195b732cb2eaa0528f9f9a3047806c9b75fdc6 Mon Sep 17 00:00:00 2001 From: zeluisping Date: Tue, 21 Oct 2025 12:08:10 +0100 Subject: [PATCH 1/3] feat: events service private event support --- .../services/EventsService/versions/0.0.7.ts | 192 ++++++++---------- 1 file changed, 82 insertions(+), 110 deletions(-) diff --git a/packages/executor/src/services/EventsService/versions/0.0.7.ts b/packages/executor/src/services/EventsService/versions/0.0.7.ts index e65aea40..962a9be4 100644 --- a/packages/executor/src/services/EventsService/versions/0.0.7.ts +++ b/packages/executor/src/services/EventsService/versions/0.0.7.ts @@ -1,111 +1,21 @@ import { IDbController, Logger } from "@skandha/types/lib"; -import { - AccountDeployedEvent, - SignatureAggregatorChangedEvent, - UserOperationEventEvent, -} from "@skandha/types/lib/contracts/EPv7/core/EntryPoint"; import { MempoolEntryStatus } from "@skandha/types/lib/executor"; import { ReputationService } from "../../ReputationService"; import { MempoolService } from "../../MempoolService"; import { ExecutorEvent, ExecutorEventBus } from "../../SubscriptionService"; import { EntryPoint__factory } from "@skandha/types/lib/contracts/EPv7/factories/core"; -import { GetContractReturnType, Hex, PublicClient, Log, parseAbi } from "viem"; - -type UserOperationEventAbi = { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "userOpHash", - type: "bytes32", - }, - { - indexed: true, - internalType: "address", - name: "sender", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "paymaster", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "nonce", - type: "uint256", - }, - { - indexed: false, - internalType: "bool", - name: "success", - type: "bool", - }, - { - indexed: false, - internalType: "uint256", - name: "actualGasCost", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "actualGasUsed", - type: "uint256", - }, - ], - name: "UserOperationEvent", - type: "event", -}; - -type AccountDeployedEventAbi = { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "userOpHash", - type: "bytes32", - }, - { - indexed: true, - internalType: "address", - name: "sender", - type: "address", - }, - { - indexed: false, - internalType: "address", - name: "factory", - type: "address", - }, - { - indexed: false, - internalType: "address", - name: "paymaster", - type: "address", - }, - ], - name: "AccountDeployed", - type: "event", -}; +import { GetContractReturnType, Hex, PublicClient, Log, parseAbi, AbiEvent } from "viem"; +import { + ACCOUNT_DEPLOYED_EVENT_HASH, + AccountDeployedAbi, + PrivateEventAbi, + SignatureAggregatorChangedAbi, + USER_OPERATION_EVENT_HASH, + UserOperationEventAbi, +} from '../../../utils/abi-events' +import { isPrivateEventLogWithArgs, unwrapPrivateEvent } from '../../../utils/unwrapPrivateEvent' -type SignatureAggregatorChangedEventAbi = { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "aggregator", - type: "address", - }, - ], - name: "SignatureAggregatorChanged", - type: "event", -}; +type EventLog = Log export class EntryPointV7EventsService { private lastBlock: bigint = BigInt(0); @@ -153,7 +63,9 @@ export class EntryPointV7EventsService { events: parseAbi([ 'event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)', 'event AccountDeployed(bytes32 indexed userOpHash, address indexed sender, address factory, address paymaster)', - 'event SignatureAggregatorChanged(address indexed aggregator)' + 'event SignatureAggregatorChanged(address indexed aggregator)', + // PrivateEvent: UserOperationEvent, AccountDeployed + 'event PrivateEvent(address[] allowedViewers, bytes32 indexed eventType, bytes payload)', ]), address: this.entryPoint, fromBlock @@ -210,6 +122,23 @@ export class EntryPointV7EventsService { await this.handleAggregatorChangedEvent(ev); } }); + + this.publicClient.watchContractEvent({ + abi: EntryPoint__factory.abi, + address: this.entryPoint, + // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent + eventName: "PrivateEvent", + args: { + // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent + eventType: [ACCOUNT_DEPLOYED_EVENT_HASH, USER_OPERATION_EVENT_HASH] + }, + onLogs: async (args) => { + const ev = args[args.length - 1]; + + // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent + await this.handleEvent(ev); + } + }); } setInterval(() => { @@ -218,10 +147,17 @@ export class EntryPointV7EventsService { } async handleEvent( - ev: Log | - Log | - Log + originalEvent: EventLog | + EventLog ): Promise { + const ev = originalEvent.eventName === 'PrivateEvent' + ? this.handlePrivateEventUnwrap(originalEvent) + : originalEvent + + if (ev === null) { + return; + } + switch (ev.eventName) { case "UserOperationEvent": await this.handleUserOperationEvent(ev); @@ -235,8 +171,44 @@ export class EntryPointV7EventsService { } } + handlePrivateEventUnwrap( + ev: EventLog + ): ( + | EventLog + | EventLog + ) | null { + if (!isPrivateEventLogWithArgs(ev)) { + this.logger.error("Invalid PrivateEvent: missing required fields"); + return null + } + + const wrappedEventHashToAbi = { + [USER_OPERATION_EVENT_HASH]: UserOperationEventAbi, + [ACCOUNT_DEPLOYED_EVENT_HASH]: AccountDeployedAbi, + } + const abi = wrappedEventHashToAbi[ev.args.eventType] + + // if abi is not on the list, then we do not care about this event + if (!abi) { + return null + } + + try { + // type cast to unwrap EventLog + return unwrapPrivateEvent(abi, ev) as ( + | EventLog + | EventLog + ) + } catch (error) { + this.logger.error( + `Failed to unwrap PrivateEvent for ${abi.name}: ${error instanceof Error ? error.message : JSON.stringify(error)}` + ); + } + return null + } + async handleAggregatorChangedEvent( - ev: Log + ev: EventLog ): Promise { this.eventAggregator = ev.args.aggregator ?? null; this.eventAggregatorTxHash = ev.transactionHash; @@ -248,8 +220,8 @@ export class EntryPointV7EventsService { // aggregator event is sent once per events bundle for all UserOperationEvents in this bundle. // it is not sent at all if the transaction is handleOps getEventAggregator( - ev: Log | - Log + ev: EventLog | + EventLog ): string | null { if (ev.transactionHash !== this.eventAggregatorTxHash) { this.eventAggregator = null; @@ -260,13 +232,13 @@ export class EntryPointV7EventsService { // AccountDeployed event is sent before each UserOperationEvent that deploys a contract. async handleAccountDeployedEvent( - ev: Log + ev: EventLog ): Promise { await this.includedAddress(ev.args.factory ?? null); } async handleUserOperationEvent( - ev: Log + ev: EventLog ): Promise { const entry = await this.mempoolService.getEntryByHash(ev.args.userOpHash!); if (entry) { From d775fc692001c9289e3ca21436cc4c145b63bf75 Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 22 Oct 2025 16:08:12 +0100 Subject: [PATCH 2/3] chore: don't poll nor watch for public event of privatised event --- .../services/EventsService/versions/0.0.7.ts | 47 ++++++------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/packages/executor/src/services/EventsService/versions/0.0.7.ts b/packages/executor/src/services/EventsService/versions/0.0.7.ts index 962a9be4..e4362fb5 100644 --- a/packages/executor/src/services/EventsService/versions/0.0.7.ts +++ b/packages/executor/src/services/EventsService/versions/0.0.7.ts @@ -61,11 +61,9 @@ export class EntryPointV7EventsService { } const logs = await publicClient.getLogs({ events: parseAbi([ - 'event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)', - 'event AccountDeployed(bytes32 indexed userOpHash, address indexed sender, address factory, address paymaster)', - 'event SignatureAggregatorChanged(address indexed aggregator)', // PrivateEvent: UserOperationEvent, AccountDeployed 'event PrivateEvent(address[] allowedViewers, bytes32 indexed eventType, bytes payload)', + 'event SignatureAggregatorChanged(address indexed aggregator)' ]), address: this.entryPoint, fromBlock @@ -93,23 +91,21 @@ export class EntryPointV7EventsService { initEventListener(): void { if(!this.disableWatchContractevent) { + // Watching PrivateEvent for: UserOperationEvent and AccountDeployed this.publicClient.watchContractEvent({ abi: EntryPoint__factory.abi, - eventName: "UserOperationEvent", address: this.entryPoint, + // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent + eventName: "PrivateEvent", + args: { + // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent + eventType: [ACCOUNT_DEPLOYED_EVENT_HASH, USER_OPERATION_EVENT_HASH] + }, onLogs: async (args) => { const ev = args[args.length - 1]; - await this.handleUserOperationEvent(ev); - } - }); - this.publicClient.watchContractEvent({ - abi: EntryPoint__factory.abi, - address: this.entryPoint, - eventName: "AccountDeployed", - onLogs: async (args) => { - const ev = args[args.length - 1]; - await this.handleAccountDeployedEvent(ev); + // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent + await this.handleEvent(ev); } }); @@ -122,23 +118,6 @@ export class EntryPointV7EventsService { await this.handleAggregatorChangedEvent(ev); } }); - - this.publicClient.watchContractEvent({ - abi: EntryPoint__factory.abi, - address: this.entryPoint, - // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent - eventName: "PrivateEvent", - args: { - // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent - eventType: [ACCOUNT_DEPLOYED_EVENT_HASH, USER_OPERATION_EVENT_HASH] - }, - onLogs: async (args) => { - const ev = args[args.length - 1]; - - // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent - await this.handleEvent(ev); - } - }); } setInterval(() => { @@ -147,8 +126,10 @@ export class EntryPointV7EventsService { } async handleEvent( - originalEvent: EventLog | - EventLog + originalEvent: ( + | EventLog + | EventLog + ) ): Promise { const ev = originalEvent.eventName === 'PrivateEvent' ? this.handlePrivateEventUnwrap(originalEvent) From 9b030731337065d028b88e01f6a918f462751b4e Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 22 Oct 2025 16:11:06 +0100 Subject: [PATCH 3/3] chore: construct PrivateEntryPointAbi for better type-safety --- .../executor/src/services/EventsService/versions/0.0.7.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/executor/src/services/EventsService/versions/0.0.7.ts b/packages/executor/src/services/EventsService/versions/0.0.7.ts index e4362fb5..6c865fd1 100644 --- a/packages/executor/src/services/EventsService/versions/0.0.7.ts +++ b/packages/executor/src/services/EventsService/versions/0.0.7.ts @@ -16,6 +16,7 @@ import { import { isPrivateEventLogWithArgs, unwrapPrivateEvent } from '../../../utils/unwrapPrivateEvent' type EventLog = Log +const PrivateEntryPointAbi = [ PrivateEventAbi, ...EntryPoint__factory.abi ] export class EntryPointV7EventsService { private lastBlock: bigint = BigInt(0); @@ -93,18 +94,14 @@ export class EntryPointV7EventsService { if(!this.disableWatchContractevent) { // Watching PrivateEvent for: UserOperationEvent and AccountDeployed this.publicClient.watchContractEvent({ - abi: EntryPoint__factory.abi, + abi: PrivateEntryPointAbi, address: this.entryPoint, - // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent eventName: "PrivateEvent", args: { - // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent eventType: [ACCOUNT_DEPLOYED_EVENT_HASH, USER_OPERATION_EVENT_HASH] }, onLogs: async (args) => { const ev = args[args.length - 1]; - - // @ts-ignore EntryPoint__factory.abi does not have PrivateEvent await this.handleEvent(ev); } });