@owlprotocol/contracts-account-abstraction
Version:
ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.
136 lines (119 loc) • 4.42 kB
text/typescript
import {
Address,
BundlerRpcSchema,
decodeEventLog,
EIP1193Parameters,
EIP1193RequestFn,
encodeEventTopics,
Hash,
Hex,
hexToNumber,
numberToHex,
Prettify,
PublicRpcSchema,
RpcTransaction,
} from "viem";
import { entryPoint07Address, UserOperation } from "viem/account-abstraction";
import { decodeFunctionData } from "viem/utils";
import { sei, seiDevnet, seiTestnet } from "viem/chains";
import { UserOperationEvent, handleOps } from "../../artifacts/IEntryPoint.js";
import { PackedUserOperation, toUserOperationEncoded } from "../../models/PackedUserOperation.js";
export type RpcGetUserOperationReturnType07 = Prettify<{
/** The block hash the User Operation was included on. */
blockHash: Hash;
/** The block number the User Operation was included on. */
blockNumber: Hex;
/** The EntryPoint which handled the User Operation. */
entryPoint: Address;
/** The hash of the transaction which included the User Operation. */
transactionHash: Hash;
/* The transaction (optional to align with official interface) */
transaction?: RpcTransaction;
/** The User Operation. */
userOperation: UserOperation<"0.7", Hex>;
}> | null;
export function createRequestGetUserOperationByHash(request: EIP1193RequestFn<PublicRpcSchema>) {
return async function (
args: EIP1193Parameters<BundlerRpcSchema> & { method: "eth_getUserOperationByHash" },
): Promise<RpcGetUserOperationReturnType07> {
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 transaction = (await request({
method: "eth_getTransactionByHash",
params: [transactionHash],
})) as RpcTransaction<false> | null;
if (!transaction) {
return null;
}
let op: PackedUserOperation | undefined = undefined;
try {
const decoded = decodeFunctionData({
abi: [handleOps],
data: transaction.input,
});
const ops = decoded.args[0];
op = ops.find(
(op: PackedUserOperation) =>
op.sender === userOperationEvent.args.sender && op.nonce === userOperationEvent.args.nonce,
);
} catch {
return null;
}
if (op === undefined) {
return null;
}
const userOperation = toUserOperationEncoded(op);
return {
blockHash: transaction.blockHash,
blockNumber: transaction.blockNumber,
entryPoint: entryPoint07Address,
transactionHash,
transaction,
userOperation,
};
};
}