diff --git a/packages/executor/src/utils/abi-events/AccountDeployed.ts b/packages/executor/src/utils/abi-events/AccountDeployed.ts new file mode 100644 index 00000000..01c2e7dd --- /dev/null +++ b/packages/executor/src/utils/abi-events/AccountDeployed.ts @@ -0,0 +1,36 @@ +import { AbiEvent, toEventHash } from 'viem' + +export const AccountDeployedAbi = { + 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", +} satisfies AbiEvent +export type AccountDeployedAbi = typeof AccountDeployedAbi + +export const ACCOUNT_DEPLOYED_EVENT_HASH = toEventHash(AccountDeployedAbi) diff --git a/packages/executor/src/utils/abi-events/PrivateEvent.ts b/packages/executor/src/utils/abi-events/PrivateEvent.ts new file mode 100644 index 00000000..43bac731 --- /dev/null +++ b/packages/executor/src/utils/abi-events/PrivateEvent.ts @@ -0,0 +1,28 @@ +import { AbiEvent } from 'viem' + +export const PrivateEventAbi = { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address[]", + name: "allowedViewers", + type: "address[]", + }, + { + indexed: true, + internalType: "bytes32", + name: "eventType", + type: "bytes32", + }, + { + indexed: false, + internalType: "bytes", + name: "payload", + type: "bytes", + }, + ], + name: "PrivateEvent", + type: "event", +} satisfies AbiEvent +export type PrivateEventAbi = typeof PrivateEventAbi diff --git a/packages/executor/src/utils/abi-events/SignatureAggregatorChanged.ts b/packages/executor/src/utils/abi-events/SignatureAggregatorChanged.ts new file mode 100644 index 00000000..da166d62 --- /dev/null +++ b/packages/executor/src/utils/abi-events/SignatureAggregatorChanged.ts @@ -0,0 +1,16 @@ +import { AbiEvent } from 'viem' + +export const SignatureAggregatorChangedAbi = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "aggregator", + type: "address", + }, + ], + name: "SignatureAggregatorChanged", + type: "event", +} satisfies AbiEvent +export type SignatureAggregatorChangedAbi = typeof SignatureAggregatorChangedAbi diff --git a/packages/executor/src/utils/abi-events/UserOperationEvent.ts b/packages/executor/src/utils/abi-events/UserOperationEvent.ts new file mode 100644 index 00000000..f5245431 --- /dev/null +++ b/packages/executor/src/utils/abi-events/UserOperationEvent.ts @@ -0,0 +1,54 @@ +import { AbiEvent, toEventHash } from 'viem' + +export const 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", +} satisfies AbiEvent +export type UserOperationEventAbi = typeof UserOperationEventAbi + +export const USER_OPERATION_EVENT_HASH = toEventHash(UserOperationEventAbi) diff --git a/packages/executor/src/utils/abi-events/index.ts b/packages/executor/src/utils/abi-events/index.ts new file mode 100644 index 00000000..7ec98dbe --- /dev/null +++ b/packages/executor/src/utils/abi-events/index.ts @@ -0,0 +1,4 @@ +export * from './AccountDeployed' +export * from './PrivateEvent' +export * from './SignatureAggregatorChanged' +export * from './UserOperationEvent' diff --git a/packages/executor/src/utils/extractEventAbiTopics.ts b/packages/executor/src/utils/extractEventAbiTopics.ts new file mode 100644 index 00000000..a998558d --- /dev/null +++ b/packages/executor/src/utils/extractEventAbiTopics.ts @@ -0,0 +1,96 @@ +import { AbiParameter } from "abitype" +import { AbiEvent, decodeAbiParameters, encodeEventTopics, Hex, Log } from "viem" + + +export class AbiEventTopicsCountMismatchError extends Error { + indexedParams: readonly AbiParameter[] + expectedCount: number + actualCount: number + + constructor({ + indexedParams, + expectedCount, + actualCount, + }: { + indexedParams: readonly AbiParameter[] + expectedCount: number + actualCount: number + }) { + const indexedParamsStr = indexedParams + .map(p => `${p.type}${p.name ? ` ${p.name}` : ''}`) + .join(', ') + + const message = [ + `Topics count mismatch: expected ${expectedCount}, got ${actualCount}`, + `Indexed params: (${indexedParamsStr})`, + ].join('\n') + + super(message) + + // Restore prototype chain for instanceof checks + Object.setPrototypeOf(this, AbiEventTopicsCountMismatchError.prototype) + this.name = 'AbiEventTopicsCountMismatchError' + this.indexedParams = indexedParams + this.expectedCount = expectedCount + this.actualCount = actualCount + } +} + +/** + * Extracts indexed parameters into a fixed length array of hex + * + * @param Inputs - the inputs of the event (the indexed parameters) + * @param Acc - the accumulator (the array of hex) + * @returns the indexed parameters as a fixed length array of hex + */ +type ExtractIndexedAsHex< + Inputs extends readonly any[], + Acc extends Hex[] = [] +> = Inputs extends readonly [infer First, ...infer Rest] + ? First extends { indexed: true } + ? ExtractIndexedAsHex + : ExtractIndexedAsHex + : Acc; + +/** + * Builds the topics fixed-length array type + * + * @param ABI - the abi of the event to extract the topics from + * @returns the topics fixed-length array type + */ +type AbiEventTopicsArray = + ABI['inputs'] extends readonly [...infer Inputs] + ? [Hex, ...ExtractIndexedAsHex] + : [Hex]; + +/** + * Extracts the topics from an event + * + * @param abi - the abi of the event to extract the topics from + * @param payload - the payload of the event + * @returns fixed-length array with the encoded event topics + * @throws an {@link AbiEventTopicsCountMismatchError} if the topics count mismatch + */ +export function extractEventAbiTopics< + ABI extends AbiEvent +>( + abi: ABI, + payload: Hex, +): AbiEventTopicsArray { + const allParams = decodeAbiParameters(abi.inputs, payload) + + const indexedValues: unknown[] = [] + abi.inputs.forEach((input, index) => { + if (input.indexed) { + indexedValues.push(allParams[index]) + } + }) + + // @ts-expect-error - dynamically extracted indexedValues cannot be proven to match generic tuple type + const topics = encodeEventTopics({ + abi: [abi], + args: indexedValues + }) + + return topics as AbiEventTopicsArray +} diff --git a/packages/executor/src/utils/unwrapPrivateEvent.ts b/packages/executor/src/utils/unwrapPrivateEvent.ts new file mode 100644 index 00000000..50e3ed48 --- /dev/null +++ b/packages/executor/src/utils/unwrapPrivateEvent.ts @@ -0,0 +1,51 @@ +import { AbiEvent, decodeEventLog, Log } from 'viem' +import { PrivateEventAbi } from './abi-events' +import { extractEventAbiTopics } from './extractEventAbiTopics' + +type PrivateEventLog = Log +type PrivateEventLogWithArgs = PrivateEventLog & { + args: Required +} + +/** + * Checks if a private event log has all the args defined. + * + * @param x - the private event log to check if it has args + * @returns true if the private event log has args, false otherwise + */ +export const isPrivateEventLogWithArgs = ( + x: PrivateEventLog, +): x is PrivateEventLogWithArgs => ( + x.args.allowedViewers !== undefined && + x.args.eventType !== undefined && + x.args.payload !== undefined +) + +/** + * Unwraps a private event log into a regular event log. + * + * @param abi - the abi of the event to unwrap + * @param ev - the private event log to unwrap + * @returns the unwrapped event log + * @throws if there is an error decoding the event log or extracting the topics + */ +export function unwrapPrivateEvent( + abi: ABI, + ev: PrivateEventLogWithArgs +): Log { + const decoded = decodeEventLog({ + abi: [abi], + data: ev.args.payload, + topics: [], // empty so viem decodes all params from the payload + }); + const topics = extractEventAbiTopics(abi, ev.args.payload) + + // Note: decoded.args has type `readonly unknown[] | ...` because viem can't + // narrow the type when topics is empty, but at runtime it is correctly decoded. + return { + ...ev, + eventName: abi.name, + args: decoded.args, + topics, + } as unknown as Log +}