@owlprotocol/contracts-account-abstraction
Version:
ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.
126 lines (111 loc) • 4.36 kB
text/typescript
import {
BundlerRpcSchema,
decodeEventLog,
EIP1193Parameters,
EIP1193RequestFn,
encodeEventTopics,
Hex,
hexToNumber,
numberToHex,
Prettify,
PublicRpcSchema,
RpcTransactionReceipt,
} from "viem";
import { entryPoint07Address, UserOperationReceipt } from "viem/account-abstraction";
import { sei, seiDevnet, seiTestnet } from "viem/chains";
import { UserOperationEvent } from "../../artifacts/IEntryPoint.js";
export type RpcGetUserOperationReceiptReturnType07 = Prettify<
UserOperationReceipt<"0.7", Hex, Hex, "0x0" | "0x1">
> | null;
export function createRequestGetUserOperationReceipt(request: EIP1193RequestFn<PublicRpcSchema>) {
return async function (
args: EIP1193Parameters<BundlerRpcSchema> & { method: "eth_getUserOperationReceipt" },
): Promise<RpcGetUserOperationReceiptReturnType07> {
const [hash] = args.params;
const chainId = await request({ method: "eth_chainId" });
const chainIdNumber = hexToNumber(chainId);
const blockNumberHex = await request({
method: "eth_blockNumber",
});
//TODO: Parametrize rpc max range
// Certain RPCs enforce a max block range
let rpcMaxRange = 90_000;
if (chainIdNumber === sei.id || chainIdNumber === seiDevnet.id || chainIdNumber === seiTestnet.id) {
rpcMaxRange = 2_000;
}
const blockNumber = hexToNumber(blockNumberHex);
const fromBlock = Math.max(blockNumber - rpcMaxRange, 0);
const filterResult = await request({
method: "eth_getLogs",
params: [
{
address: entryPoint07Address,
topics: encodeEventTopics({
abi: [UserOperationEvent],
eventName: "UserOperationEvent",
args: {
userOpHash: hash,
},
}),
fromBlock: numberToHex(fromBlock),
toBlock: "latest",
},
],
});
if (filterResult.length === 0) {
return null;
}
const userOperationLog = filterResult[0];
const userOperationEvent = decodeEventLog({
abi: [UserOperationEvent],
eventName: "UserOperationEvent",
data: userOperationLog.data,
topics: userOperationLog.topics,
strict: true,
});
const transactionHash = userOperationLog.transactionHash;
if (transactionHash === null) {
return null;
}
const receipt = (await request({
method: "eth_getTransactionReceipt",
params: [transactionHash],
})) as RpcTransactionReceipt | null;
if (!receipt) {
return null;
}
//We will filter the receipt logs
const logs = receipt.logs;
//This logic filters logs emitted for this UserOp
let startIndex = -1;
let endIndex = -1;
logs.forEach((log, index) => {
if (log?.topics[0] === userOperationLog.topics[0]) {
// process UserOperationEvent
if (log.topics[1] === userOperationLog.topics[1]) {
// it's our userOpHash. save as end of logs array
endIndex = index;
} 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 (endIndex === -1) {
// Should not happen
throw new Error("fatal: no UserOperationEvent in logs");
}
const filteredLogs = logs.slice(startIndex + 1, endIndex);
return {
entryPoint: entryPoint07Address,
userOpHash: hash,
sender: userOperationEvent.args.sender,
nonce: numberToHex(userOperationEvent.args.nonce),
actualGasUsed: numberToHex(userOperationEvent.args.actualGasUsed),
actualGasCost: numberToHex(userOperationEvent.args.actualGasCost),
success: userOperationEvent.args.success,
receipt,
logs: filteredLogs,
};
};
}