Skip to content

Commit 5a9e238

Browse files
authored
fix(store-sync): handle user ops in waitForTransaction (#3518)
1 parent 9d3fa56 commit 5a9e238

File tree

2 files changed

+52
-18
lines changed

2 files changed

+52
-18
lines changed

.changeset/empty-shoes-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@latticexyz/store-sync": patch
3+
---
4+
5+
Updated `waitForTransaction` to handle receipt status for user operations.

packages/store-sync/src/createStoreSync.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { storeEventsAbi } from "@latticexyz/store";
2-
import { GetTransactionReceiptErrorType, Hex } from "viem";
2+
import { GetTransactionReceiptErrorType, Hex, parseEventLogs } from "viem";
3+
import { entryPoint07Abi } from "viem/account-abstraction";
34
import {
45
StorageAdapter,
56
StorageAdapterBlock,
@@ -42,6 +43,7 @@ import { toStorageAdatperBlock } from "./indexer-client/toStorageAdapterBlock";
4243
import { watchLogs } from "./watchLogs";
4344
import { getAction } from "viem/utils";
4445
import { getChainId, getTransactionReceipt } from "viem/actions";
46+
import packageJson from "../package.json";
4547

4648
const debug = parentDebug.extend("createStoreSync");
4749

@@ -324,7 +326,7 @@ export async function createStoreSync({
324326
// keep 10 blocks worth processed transactions in memory
325327
const recentBlocksWindow = 10;
326328
// most recent block first, for ease of pulling the first one off the array
327-
const recentBlocks$ = storedBlockLogs$.pipe(
329+
const recentStoredBlocks$ = storedBlockLogs$.pipe(
328330
scan<StorageAdapterBlock, StorageAdapterBlock[]>(
329331
(recentBlocks, block) => [block, ...recentBlocks].slice(0, recentBlocksWindow),
330332
[],
@@ -339,29 +341,56 @@ export async function createStoreSync({
339341

340342
// This currently blocks for async call on each block processed
341343
// We could potentially speed this up a tiny bit by racing to see if 1) tx exists in processed block or 2) fetch tx receipt for latest block processed
342-
const hasTransaction$ = recentBlocks$.pipe(
344+
const hasTransaction$ = recentStoredBlocks$.pipe(
343345
// We use `mergeMap` instead of `concatMap` here to send the fetch request immediately when a new block range appears,
344346
// instead of sending the next request only when the previous one completed.
345-
mergeMap(async (blocks) => {
346-
for (const block of blocks) {
347-
const txs = block.logs.map((op) => op.transactionHash);
348-
// If the transaction caused a log, it must have succeeded
349-
if (txs.includes(tx)) {
350-
return { blockNumber: block.blockNumber, status: "success" as const, transactionHash: tx };
347+
mergeMap(async (storedBlocks) => {
348+
for (const storedBlock of storedBlocks) {
349+
// If stored block had Store event logs associated with tx, it must have succeeded.
350+
if (storedBlock.logs.some((log) => log.transactionHash === tx)) {
351+
return { blockNumber: storedBlock.blockNumber, status: "success", transactionHash: tx } as const;
351352
}
352353
}
353354

354355
try {
355-
const lastBlock = blocks[0];
356-
debug("fetching tx receipt for block", lastBlock.blockNumber);
357-
const { status, blockNumber, transactionHash } = await getAction(
358-
publicClient,
359-
getTransactionReceipt,
360-
"getTransactionReceipt",
361-
)({ hash: tx });
362-
if (lastBlock.blockNumber >= blockNumber) {
363-
return { status, blockNumber, transactionHash };
356+
const lastStoredBlock = storedBlocks[0];
357+
debug("fetching tx receipt for block", lastStoredBlock.blockNumber);
358+
const receipt = await getAction(publicClient, getTransactionReceipt, "getTransactionReceipt")({ hash: tx });
359+
360+
// Skip if sync hasn't caught up to this transaction's block.
361+
if (lastStoredBlock.blockNumber < receipt.blockNumber) return;
362+
363+
// Check if this was a user op so we can get the internal user op status.
364+
const userOpEvents = parseEventLogs({
365+
logs: receipt.logs,
366+
abi: entryPoint07Abi,
367+
eventName: "UserOperationEvent" as const,
368+
});
369+
if (userOpEvents.length) {
370+
debug("tx receipt appears to be a user op, using user op status instead");
371+
if (userOpEvents.length > 1) {
372+
const issueLink = `https://github.com/latticexyz/mud/issues/new${new URLSearchParams({
373+
title: "waitForTransaction found more than one user op",
374+
body: `MUD version: ${packageJson.version}\nChain ID: ${chainId}\nTransaction: ${tx}\n`,
375+
})}`;
376+
console.warn(
377+
// eslint-disable-next-line max-len
378+
`Receipt for transaction ${tx} had more than one \`UserOperationEvent\`. This may have unexpected behavior.\n\nIf you encounter this, please open an issue:\n${issueLink}`,
379+
);
380+
}
381+
382+
return {
383+
status: userOpEvents[0].args.success ? "success" : "reverted",
384+
blockNumber: receipt.blockNumber,
385+
transactionHash: receipt.transactionHash,
386+
} as const;
364387
}
388+
389+
return {
390+
status: receipt.status,
391+
blockNumber: receipt.blockNumber,
392+
transactionHash: receipt.transactionHash,
393+
};
365394
} catch (e) {
366395
const error = e as GetTransactionReceiptErrorType;
367396
if (error.name === "TransactionReceiptNotFoundError") {

0 commit comments

Comments
 (0)