From b7a6274033379558fa202aa2f7137f0556de6208 Mon Sep 17 00:00:00 2001 From: zeluisping Date: Tue, 21 Oct 2025 12:09:43 +0100 Subject: [PATCH 1/7] feat: entry point service private event support --- .../EntryPointService/versions/0.0.7.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts index 1e58c28a..fc649f60 100644 --- a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts +++ b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts @@ -57,6 +57,8 @@ import { GetContractReturnType, toHex, } from "viem"; +import { USER_OPERATION_EVENT_HASH, UserOperationEventAbi } from '../../../utils/abi-events' +import { isPrivateEventLogWithArgs, unwrapPrivateEvent } from '../../../utils/unwrapPrivateEvent' export class EntryPointV7Service implements IEntryPointService { @@ -267,6 +269,34 @@ export class EntryPointV7Service implements IEntryPointService { if(logs[0]) { return logs[0]; } + const privateLogs = await this.publicClient.getLogs({ + address: this.address, + event: parseAbiItem([ + 'event PrivateEvent(address[] allowedViewers, bytes32 indexed eventType, bytes payload)', + ]), + fromBlock, + args: { + eventType: USER_OPERATION_EVENT_HASH, + } + }); + + for (const log of privateLogs) { + if (!isPrivateEventLogWithArgs(log)) { + continue; + } + + try { + const unwrapped = unwrapPrivateEvent(UserOperationEventAbi, log); + + if (unwrapped.args.userOpHash === userOpHash) { + return unwrapped; + } + } catch (error) { + this.logger.error( + `Failed to unwrap PrivateEvent for ${UserOperationEventAbi.name}: ${error instanceof Error ? error.message : JSON.stringify(error)}` + ); + } + } } catch (err) { this.logger.error(err); throw new RpcError( From 241321535f1aba0d9ca2372475a1e1e1b64460be Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 22 Oct 2025 15:42:38 +0100 Subject: [PATCH 2/7] chore: remove public event support, private only --- .../services/EntryPointService/versions/0.0.7.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts index fc649f60..6e79e0a3 100644 --- a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts +++ b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts @@ -256,19 +256,7 @@ export class EntryPointV7Service implements IEntryPointService { if (fromBlock < 0) { fromBlock = BigInt(0); } - const logs = await this.publicClient.getLogs({ - address: this.address, - event: parseAbiItem([ - 'event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)' - ]), - fromBlock, - args: { - userOpHash - } - }); - if(logs[0]) { - return logs[0]; - } + const privateLogs = await this.publicClient.getLogs({ address: this.address, event: parseAbiItem([ From 52ef3fc0f40b36c762303b3fded7e574d55b0723 Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 29 Oct 2025 08:01:48 +0000 Subject: [PATCH 3/7] chore: add silent data auth headers types --- packages/types/src/api/interfaces.ts | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/types/src/api/interfaces.ts b/packages/types/src/api/interfaces.ts index a341e765..371d7f22 100644 --- a/packages/types/src/api/interfaces.ts +++ b/packages/types/src/api/interfaces.ts @@ -125,3 +125,32 @@ export type UserOperationStatus = { transaction?: string; reason?: string; }; + +const SDAuthHeaders = [ + 'x-timestamp', + 'x-signature', + 'x-from-block', // consumed by the bundler + 'x-signer-swc', + 'x-delegate', + 'x-eip712-signature', + 'x-delegate-signature', + 'x-eip712-delegate-signature', +] +export type SDAuthHeaders = Record< + typeof SDAuthHeaders[number], + string | undefined +> + +export function pickSDAuthHeaders( + requestHeaders: Record, +): SDAuthHeaders { + return SDAuthHeaders.reduce( + (sdHeaders, header) => { + if (header in requestHeaders) { + sdHeaders[header] = requestHeaders[header] + } + return sdHeaders + }, + {} as SDAuthHeaders, + ) +} From c3220294336cb55a0a47ebd33dda6dea0d0b3146 Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 29 Oct 2025 08:01:57 +0000 Subject: [PATCH 4/7] feat: add silent data headers support to api (single rpc call only) --- packages/api/src/app.ts | 27 +++++++++++++++++++++++---- packages/api/src/modules/eth.ts | 11 +++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/api/src/app.ts b/packages/api/src/app.ts index 71020920..e3cdf4c0 100644 --- a/packages/api/src/app.ts +++ b/packages/api/src/app.ts @@ -20,6 +20,7 @@ import { import { SkandhaAPI } from "./modules/skandha"; import { JsonRpcRequest, JsonRpcResponse } from "./interface"; import { Server } from "./server"; +import { pickSDAuthHeaders, SDAuthHeaders } from '@skandha/types/lib/api/interfaces' export interface RpcHandlerOptions { config: Config; @@ -94,14 +95,18 @@ export class ApiApp { request, req.ip, req.headers.authorization + // Silent Data auth headers not supported for websocket requests ) ); } } else { + const sdAuthHeaders = pickSDAuthHeaders(req.headers); + response = await this.handleRpcRequest( body as JsonRpcRequest, req.ip, - req.headers.authorization + req.headers.authorization, + sdAuthHeaders, ); } return res.status(HttpStatus.OK).send(response); @@ -124,6 +129,7 @@ export class ApiApp { ); if (!wsRpc) { try { + // Silent Data auth headers not supported for websocket requests response = await this.handleRpcRequest(request, ""); } catch (err) { const { jsonrpc, id } = request; @@ -190,7 +196,8 @@ export class ApiApp { private async handleRpcRequest( request: JsonRpcRequest, ip: string, - authKey?: string + authKey?: string, + sdAuthHeaders?: SDAuthHeaders, ): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any let result: any; @@ -290,10 +297,22 @@ export class ApiApp { break; } case BundlerRPCMethods.eth_getUserOperationReceipt: - result = await this.ethApi.getUserOperationReceipt(params[0]); + if (!sdAuthHeaders) { + throw new RpcError( + "Missing Silent Data auth headers", + RpcErrorCodes.METHOD_NOT_FOUND + ); + } + result = await this.ethApi.getUserOperationReceipt(params[0], sdAuthHeaders); break; case BundlerRPCMethods.eth_getUserOperationByHash: - result = await this.ethApi.getUserOperationByHash(params[0]); + if (!sdAuthHeaders) { + throw new RpcError( + "Missing Silent Data auth headers", + RpcErrorCodes.METHOD_NOT_FOUND + ); + } + result = await this.ethApi.getUserOperationByHash(params[0], sdAuthHeaders); break; case BundlerRPCMethods.web3_clientVersion: result = this.web3Api.clientVersion(); diff --git a/packages/api/src/modules/eth.ts b/packages/api/src/modules/eth.ts index af5d7df6..5cd8c327 100644 --- a/packages/api/src/modules/eth.ts +++ b/packages/api/src/modules/eth.ts @@ -1,6 +1,7 @@ import { Eth } from "@skandha/executor/lib/modules/eth"; import { EstimatedUserOperationGas, + SDAuthHeaders, UserOperationByHashResponse, UserOperationReceipt, } from "@skandha/types/lib/api/interfaces"; @@ -53,9 +54,10 @@ export class EthAPI { * with the addition of entryPoint, blockNumber, blockHash and transactionHash */ async getUserOperationByHash( - hash: string + hash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise { - return await this.ethModule.getUserOperationByHash(hash); + return await this.ethModule.getUserOperationByHash(hash, sdAuthHeaders); } /** @@ -64,9 +66,10 @@ export class EthAPI { * @returns a UserOperation receipt */ async getUserOperationReceipt( - hash: string + hash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise { - return await this.ethModule.getUserOperationReceipt(hash); + return await this.ethModule.getUserOperationReceipt(hash, sdAuthHeaders); } /** From 59ad0f00178465cf2c0b3b18cde1c3f9c670c7af Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 29 Oct 2025 08:06:39 +0000 Subject: [PATCH 5/7] chore: pass silent data auth headers down to executor --- packages/executor/src/modules/eth.ts | 31 ++++++------------- .../src/services/EntryPointService/service.ts | 11 ++++--- .../EntryPointService/versions/0.0.7.ts | 14 ++++++--- .../EntryPointService/versions/base.ts | 12 +++++-- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/executor/src/modules/eth.ts b/packages/executor/src/modules/eth.ts index d8b0bd04..eaef9df3 100644 --- a/packages/executor/src/modules/eth.ts +++ b/packages/executor/src/modules/eth.ts @@ -2,6 +2,7 @@ import RpcError from "@skandha/types/lib/api/errors/rpc-error"; import * as RpcErrorCodes from "@skandha/types/lib/api/errors/rpc-error-codes"; import { EstimatedUserOperationGas, + SDAuthHeaders, UserOperationByHashResponse, UserOperationReceipt, } from "@skandha/types/lib/api/interfaces"; @@ -478,27 +479,12 @@ export class Eth { * with the addition of entryPoint, blockNumber, blockHash and transactionHash */ async getUserOperationByHash( - hash: string + hash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise { - const entry = await this.mempoolService.getEntryByHash(hash); - if (entry) { - if (entry.status < MempoolEntryStatus.Submitted || entry.transaction) { - let transaction: GetTransactionReturnType | undefined = undefined; - if (entry.transaction) { - transaction = await this.publicClient.getTransaction({ - hash: entry.transaction as Hex - }); - } - return { - userOperation: hexlifyUserOp(entry.userOp), - entryPoint: entry.entryPoint, - transactionHash: transaction?.hash, - blockHash: transaction?.blockHash, - blockNumber: transaction?.blockNumber, - }; - } - } - const rpcUserOp = await this.entryPointService.getUserOperationByHash(hash); + const rpcUserOp = await this.entryPointService.getUserOperationByHash( + hash, sdAuthHeaders, + ); if (!rpcUserOp && this.blockscoutApi) { return await this.blockscoutApi.getUserOperationByHash(hash); } @@ -511,10 +497,11 @@ export class Eth { * @returns a UserOperation receipt */ async getUserOperationReceipt( - hash: string + hash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise { const rpcUserOp = await this.entryPointService.getUserOperationReceipt( - hash + hash, sdAuthHeaders, ); if (!rpcUserOp && this.blockscoutApi) { return await this.blockscoutApi.getUserOperationReceipt(hash); diff --git a/packages/executor/src/services/EntryPointService/service.ts b/packages/executor/src/services/EntryPointService/service.ts index 07f46580..c5c7a2bb 100644 --- a/packages/executor/src/services/EntryPointService/service.ts +++ b/packages/executor/src/services/EntryPointService/service.ts @@ -2,6 +2,7 @@ import { UserOperation } from "@skandha/types/lib/contracts/UserOperation"; import { IDbController, Logger } from "@skandha/types/lib"; import { + SDAuthHeaders, UserOperationByHashResponse, UserOperationReceipt, } from "@skandha/types/lib/api/interfaces"; @@ -39,7 +40,8 @@ export class EntryPointService { /** View functions */ async getUserOperationByHash( - userOpHash: string + userOpHash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise { if (!userOpHash) { throw new RpcError( @@ -49,7 +51,7 @@ export class EntryPointService { } for (const [_, entryPoint] of Object.entries(this.entryPoints)) { try { - const res = entryPoint.getUserOperationByHash(userOpHash); + const res = entryPoint.getUserOperationByHash(userOpHash, sdAuthHeaders); if (res) return res; } catch (err) { /* empty */ @@ -59,7 +61,8 @@ export class EntryPointService { } async getUserOperationReceipt( - userOpHash: string + userOpHash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise { if (!userOpHash) { throw new RpcError( @@ -69,7 +72,7 @@ export class EntryPointService { } for (const [_, entryPoint] of Object.entries(this.entryPoints)) { try { - const res = entryPoint.getUserOperationReceipt(userOpHash); + const res = entryPoint.getUserOperationReceipt(userOpHash, sdAuthHeaders); if (res) return res; } catch (err) { /* empty */ diff --git a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts index 6e79e0a3..ef0505b3 100644 --- a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts +++ b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts @@ -16,6 +16,7 @@ import { Logger } from "@skandha/types/lib"; import { UserOperationReceipt, UserOperationByHashResponse, + SDAuthHeaders, } from "@skandha/types/lib/api/interfaces"; import { deepHexlify } from "@skandha/utils/lib/hexlify"; import { @@ -247,7 +248,8 @@ export class EntryPointV7Service implements IEntryPointService { /** UserOp Events */ async getUserOperationEvent( - userOpHash: Hex + userOpHash: Hex, + sdAuthHeaders: SDAuthHeaders, ) { try { const blockNumber = await this.publicClient.getBlockNumber(); @@ -296,9 +298,10 @@ export class EntryPointV7Service implements IEntryPointService { } async getUserOperationReceipt( - hash: Hex + hash: Hex, + sdAuthHeaders: SDAuthHeaders, ): Promise { - const event = await this.getUserOperationEvent(hash); + const event = await this.getUserOperationEvent(hash, sdAuthHeaders); if (!event) { return null; } @@ -318,9 +321,10 @@ export class EntryPointV7Service implements IEntryPointService { } async getUserOperationByHash( - hash: Hex + hash: Hex, + sdAuthHeaders: SDAuthHeaders, ): Promise { - const event = await this.getUserOperationEvent(hash); + const event = await this.getUserOperationEvent(hash, sdAuthHeaders); if (!event) { return null; } diff --git a/packages/executor/src/services/EntryPointService/versions/base.ts b/packages/executor/src/services/EntryPointService/versions/base.ts index 7e49db13..fc39ef2e 100644 --- a/packages/executor/src/services/EntryPointService/versions/base.ts +++ b/packages/executor/src/services/EntryPointService/versions/base.ts @@ -4,6 +4,7 @@ import { UserOperation } from "@skandha/types/lib/contracts/UserOperation"; import { IStakeManager } from "@skandha/types/lib/contracts/EPv7/core/StakeManager"; import { UserOperationEventEvent } from "@skandha/types/lib/contracts/EPv6/EntryPoint"; import { + SDAuthHeaders, UserOperationByHashResponse, UserOperationReceipt, } from "@skandha/types/lib/api/interfaces"; @@ -36,11 +37,16 @@ export interface IEntryPointService { simulateValidation(userOp: UserOperation): Promise; getUserOperationEvent( - userOpHash: string + userOpHash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise; - getUserOperationReceipt(hash: string): Promise; + getUserOperationReceipt( + hash: string, + sdAuthHeaders: SDAuthHeaders, + ): Promise; getUserOperationByHash( - hash: string + hash: string, + sdAuthHeaders: SDAuthHeaders, ): Promise; encodeHandleOps(userOps: UserOperation[], beneficiary: string): Hex; From eede63365e53185126c1847d3e973b3ab292d5eb Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 29 Oct 2025 08:08:06 +0000 Subject: [PATCH 6/7] chore: disable mempool fetch for lack of auth, straight to rpc --- packages/executor/src/modules/eth.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/executor/src/modules/eth.ts b/packages/executor/src/modules/eth.ts index eaef9df3..942fc8a3 100644 --- a/packages/executor/src/modules/eth.ts +++ b/packages/executor/src/modules/eth.ts @@ -482,6 +482,25 @@ export class Eth { hash: string, sdAuthHeaders: SDAuthHeaders, ): Promise { + // Silent Data auth is checked in the chain RPC, mempool check not supported + // const entry = await this.mempoolService.getEntryByHash(hash); + // if (entry) { + // if (entry.status < MempoolEntryStatus.Submitted || entry.transaction) { + // let transaction: GetTransactionReturnType | undefined = undefined; + // if (entry.transaction) { + // transaction = await this.publicClient.getTransaction({ + // hash: entry.transaction as Hex + // }); + // } + // return { + // userOperation: hexlifyUserOp(entry.userOp), + // entryPoint: entry.entryPoint, + // transactionHash: transaction?.hash, + // blockHash: transaction?.blockHash, + // blockNumber: transaction?.blockNumber, + // }; + // } + // } const rpcUserOp = await this.entryPointService.getUserOperationByHash( hash, sdAuthHeaders, ); From 3fa6709dc6cc543a1f2aabc954c79af32cbe1f18 Mon Sep 17 00:00:00 2001 From: zeluisping Date: Wed, 29 Oct 2025 08:16:07 +0000 Subject: [PATCH 7/7] feat: use silent data auth headers to fetch user operations --- .../EntryPointService/versions/0.0.7.ts | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts index ef0505b3..ca58bddf 100644 --- a/packages/executor/src/services/EntryPointService/versions/0.0.7.ts +++ b/packages/executor/src/services/EntryPointService/versions/0.0.7.ts @@ -57,6 +57,8 @@ import { decodeFunctionData, GetContractReturnType, toHex, + createPublicClient, + http, } from "viem"; import { USER_OPERATION_EVENT_HASH, UserOperationEventAbi } from '../../../utils/abi-events' import { isPrivateEventLogWithArgs, unwrapPrivateEvent } from '../../../utils/unwrapPrivateEvent' @@ -251,15 +253,51 @@ export class EntryPointV7Service implements IEntryPointService { userOpHash: Hex, sdAuthHeaders: SDAuthHeaders, ) { + const { + 'x-from-block': headerFromBlock, + ...headersToForward + } = sdAuthHeaders + try { const blockNumber = await this.publicClient.getBlockNumber(); - let fromBlock = blockNumber - BigInt(this.networkConfig.receiptLookupRange); + let minBlockNumber = blockNumber - BigInt(this.networkConfig.receiptLookupRange); // underflow check - if (fromBlock < 0) { - fromBlock = BigInt(0); + if (minBlockNumber < 0) { + minBlockNumber = BigInt(0); + } + + let fromBlock = headerFromBlock + ? BigInt(headerFromBlock) + : undefined + + // ensure value is within the receipt lookup range + if ( + fromBlock === undefined || + fromBlock > blockNumber || + fromBlock < minBlockNumber + ) { + this.logger.warn(`fromBlock is out of range, using minBlockNumber: ${minBlockNumber}`) + fromBlock = minBlockNumber } - const privateLogs = await this.publicClient.getLogs({ + // Create a public client with custom transport that includes sdAuthHeaders + const clientWithHeaders = createPublicClient({ + chain: this.publicClient.chain, + transport: http(this.networkConfig.rpcEndpoint, { + fetchOptions: { + headers: Object + .entries(headersToForward) + .reduce((headersToSend, [key, value]) => { + if (value !== undefined) { + headersToSend[key] = value + } + return headersToSend + }, {} as Record), + }, + }), + }) + + const privateLogs = await clientWithHeaders.getLogs({ address: this.address, event: parseAbiItem([ 'event PrivateEvent(address[] allowedViewers, bytes32 indexed eventType, bytes payload)',