Untitled
unknown
plain_text
a year ago
10 kB
10
Indexable
import "dotenv/config";
import fastify, { FastifyInstance } from "fastify";
import cors from "@fastify/cors";
import fs from "fs/promises";
import {
Address,
createPublicClient,
decodeEventLog,
encodeEventTopics,
getAbiItem,
Hex,
http,
parseAbi,
parseEventLogs,
toHex,
TransactionReceipt,
TransactionReceiptNotFoundError,
zeroAddress,
} from "viem";
import {
entryPoint06Abi,
entryPoint06Address,
entryPoint07Abi,
entryPoint07Address,
} from "viem/account-abstraction";
import { base } from "viem/chains";
// Define types for JSON-RPC
interface JsonRpcRequest {
jsonrpc: "2.0";
method: string;
params: any;
id: number | string;
}
interface JsonRpcResponse {
jsonrpc: "2.0";
result?: any;
error?: {
code: number;
message: string;
};
id: number | string;
}
// Create fastify instance
const server: FastifyInstance = fastify({ logger: true });
// Register CORS
server.register(cors, {
origin: true,
});
// JSON-RPC endpoint
server.post<{
Body: JsonRpcRequest;
}>("/", async (request, reply) => {
const { method, params, id } = request.body;
// Handle different methods
switch (method) {
case "echo":
const response: JsonRpcResponse = {
jsonrpc: "2.0",
result: params,
id,
};
return response;
case "eth_getUserOperationReceipt":
// @ts-ignore
const { provider } = request.query;
console.log(provider);
const receipt = await getUserOperationReceipt(params, provider);
return receipt;
default:
const errorResponse: JsonRpcResponse = {
jsonrpc: "2.0",
error: {
code: -32601,
message: "Method not found",
},
id,
};
return errorResponse;
}
});
async function getUserOperationReceipt(
params: any,
provider: string
): Promise<JsonRpcResponse> {
const startTime = performance.now();
let rpcUrl: string;
switch (provider) {
case "tenderly":
rpcUrl = process.env.TENDERLY_RPC_URL ?? "";
break;
case "alchemy":
rpcUrl = process.env.ALCHEMY_RPC_URL ?? "";
break;
case "ankr":
rpcUrl = process.env.ANKR_RPC_URL ?? "";
break;
default:
rpcUrl = process.env.TENDERLY_RPC_URL ?? "";
}
console.log(rpcUrl);
const userOperationHash = params[0];
const userOperationEventAbiItem = getAbiItem({
abi: entryPoint07Abi,
name: "UserOperationEvent",
});
let fromBlock: bigint | undefined = undefined;
let toBlock: "latest" | undefined = undefined;
const maxBlockRange = 500;
const publicClient = createPublicClient({
chain: base,
transport: http(rpcUrl),
});
if (maxBlockRange !== undefined) {
const latestBlock = await publicClient.getBlockNumber();
fromBlock = latestBlock - BigInt(maxBlockRange);
if (fromBlock < 0n) {
fromBlock = 0n;
}
fromBlock = 0n;
toBlock = "latest";
}
const result = await publicClient.getLogs({
address: [entryPoint06Address, entryPoint07Address],
event: userOperationEventAbiItem,
fromBlock,
toBlock,
args: {
userOpHash: userOperationHash,
},
});
if (result.length === 0) {
return {
jsonrpc: "2.0",
result: null,
id: 1,
};
}
const endTime = performance.now();
const duration = endTime - startTime;
// Log performance data only for non-null results
const perfData = {
timestamp: new Date().toISOString(),
provider,
userOperationHash,
duration_ms: duration,
success: true,
};
await logPerformance(perfData);
const userOperationEvent = result[0];
// throw if any of the members of userOperationEvent are undefined
if (
userOperationEvent.args.actualGasCost === undefined ||
userOperationEvent.args.sender === undefined ||
userOperationEvent.args.nonce === undefined ||
userOperationEvent.args.userOpHash === undefined ||
userOperationEvent.args.success === undefined ||
userOperationEvent.args.paymaster === undefined ||
userOperationEvent.args.actualGasUsed === undefined
) {
throw new Error("userOperationEvent has undefined members");
}
const txHash = userOperationEvent.transactionHash;
if (txHash === null) {
// transaction pending
return {
jsonrpc: "2.0",
result: null,
id: 1,
};
}
const getTransactionReceipt = async (
txHash: Hex
): Promise<TransactionReceipt> => {
while (true) {
try {
const transactionReceipt = await publicClient.getTransactionReceipt({
hash: txHash,
});
let effectiveGasPrice: bigint | undefined =
transactionReceipt.effectiveGasPrice ??
(transactionReceipt as any).gasPrice ??
undefined;
if (effectiveGasPrice === undefined) {
const tx = await publicClient.getTransaction({
hash: txHash,
});
effectiveGasPrice = tx.gasPrice ?? undefined;
}
if (effectiveGasPrice) {
transactionReceipt.effectiveGasPrice = effectiveGasPrice;
}
return transactionReceipt;
} catch (e) {
if (e instanceof TransactionReceiptNotFoundError) {
continue;
}
throw e;
}
}
};
const receipt = await getTransactionReceipt(txHash);
const logs = receipt.logs;
if (
logs.some(
(log) =>
log.blockHash === null ||
log.blockNumber === null ||
log.transactionIndex === null ||
log.transactionHash === null ||
log.logIndex === null ||
log.topics.length === 0
)
) {
// transaction pending
return {
jsonrpc: "2.0",
result: null,
id: 1,
};
}
const userOperationReceipt = parseUserOperationReceipt(
userOperationHash,
receipt
);
return {
jsonrpc: "2.0",
result: userOperationReceipt,
id: 1,
};
}
async function logPerformance(data: any) {
const logFile = 'performance_logs.json';
let logs = [];
try {
// Check if file exists
try {
await fs.access(logFile);
const existingData = await fs.readFile(logFile, 'utf8');
logs = JSON.parse(existingData);
} catch {
// File doesn't exist, create it with empty array
await fs.writeFile(logFile, JSON.stringify([], null, 2));
}
logs.push(data);
await fs.writeFile(logFile, JSON.stringify(logs, null, 2));
} catch (error) {
console.error('Failed to log performance data:', error);
}
}
export function parseUserOperationReceipt(
userOpHash: Hex,
receipt: TransactionReceipt
) {
const userOperationRevertReasonAbi = parseAbi([
"event UserOperationRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason)",
]);
const userOperationEventTopic = encodeEventTopics({
abi: entryPoint06Abi,
eventName: "UserOperationEvent",
});
const userOperationRevertReasonTopicEvent = encodeEventTopics({
abi: userOperationRevertReasonAbi,
})[0];
let entryPoint: Address = zeroAddress;
let revertReason = undefined;
let startIndex = -1;
let endIndex = -1;
receipt.logs.forEach((log, index) => {
if (log?.topics[0] === userOperationEventTopic[0]) {
// process UserOperationEvent
if (log.topics[1] === userOpHash) {
// it's our userOpHash. save as end of logs array
endIndex = index;
entryPoint = log.address;
} else if (endIndex === -1) {
// it's a different hash. remember it as beginning index, but only if we didn't find our end index yet.
startIndex = index;
}
}
if (log?.topics[0] === userOperationRevertReasonTopicEvent) {
// process UserOperationRevertReason
if (log.topics[1] === userOpHash) {
// it's our userOpHash. capture revert reason.
const decodedLog = decodeEventLog({
abi: userOperationRevertReasonAbi,
data: log.data,
topics: log.topics,
});
revertReason = decodedLog.args.revertReason;
}
}
});
if (endIndex === -1) {
throw new Error("fatal: no UserOperationEvent in logs");
}
const filteredLogs = receipt.logs.slice(startIndex + 1, endIndex);
const userOperationEvent = parseEventLogs({
abi: entryPoint06Abi,
eventName: "UserOperationEvent",
args: {
userOpHash,
},
logs: receipt.logs,
})[0];
let paymaster: Address | undefined = userOperationEvent.args.paymaster;
paymaster = paymaster === zeroAddress ? undefined : paymaster;
const userOperationReceipt = {
userOpHash,
entryPoint,
sender: userOperationEvent.args.sender,
nonce: userOperationEvent.args.nonce,
paymaster,
actualGasUsed: userOperationEvent.args.actualGasUsed,
actualGasCost: userOperationEvent.args.actualGasCost,
success: userOperationEvent.args.success,
reason: revertReason,
logs: filteredLogs,
receipt: {
...receipt,
status: receipt.status === "success" ? 1 : 0,
},
};
return deepHexlify(userOperationReceipt);
}
// biome-ignore lint/suspicious/noExplicitAny: it's a generic type
export function deepHexlify(obj: any): any {
if (typeof obj === "function") {
return undefined
}
if (obj == null || typeof obj === "string" || typeof obj === "boolean") {
return obj
}
if (typeof obj === "bigint") {
return toHex(obj)
}
if (obj._isBigNumber != null || typeof obj !== "object") {
return toHex(obj).replace(/^0x0/, "0x")
}
if (Array.isArray(obj)) {
return obj.map((member) => deepHexlify(member))
}
return Object.keys(obj).reduce(
// biome-ignore lint/suspicious/noExplicitAny: it's a recursive function, so it's hard to type
(set: any, key: string) => {
set[key] = deepHexlify(obj[key])
return set
},
{}
)
}
// Start server
const start = async (): Promise<void> => {
try {
await server.listen({ port: 3000, host: "0.0.0.0" });
} catch (err) {
server.log.error(err);
process.exit(1);
}
};
start();Editor is loading...
Leave a Comment