From 8c3dcf77c2481b11622a0603c8a09a5b1fb5f787 Mon Sep 17 00:00:00 2001 From: alvarius Date: Tue, 16 Apr 2024 14:58:16 +0100 Subject: [PATCH] feat(store-sync): add status and block number to return type of waitForTransaction (#2668) --- .changeset/warm-flies-reply.md | 5 +++++ packages/store-sync/src/common.ts | 6 ++++-- packages/store-sync/src/createStoreSync.ts | 22 +++++++++++++++------- 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 .changeset/warm-flies-reply.md diff --git a/.changeset/warm-flies-reply.md b/.changeset/warm-flies-reply.md new file mode 100644 index 0000000000..2e48423785 --- /dev/null +++ b/.changeset/warm-flies-reply.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/store-sync": patch +--- + +`waitForTransaction` now returns a `Promise<{ blockNumber: bigint, status: "success" | "reverted" }>` instead of `Promise`, to allow consumers to react to reverted transactions without refetching the transaction receipt. diff --git a/packages/store-sync/src/common.ts b/packages/store-sync/src/common.ts index e1ba55fd76..72ccd53248 100644 --- a/packages/store-sync/src/common.ts +++ b/packages/store-sync/src/common.ts @@ -1,4 +1,4 @@ -import { Address, Block, Hex, Log, PublicClient } from "viem"; +import { Address, Block, Hex, Log, PublicClient, TransactionReceipt } from "viem"; import { StoreEventsAbiItem, StoreEventsAbi } from "@latticexyz/store"; import { resolveConfig } from "@latticexyz/store/internal"; import { Observable } from "rxjs"; @@ -113,11 +113,13 @@ export type SyncOptions = { }; }; +export type WaitForTransactionResult = Pick; + export type SyncResult = { latestBlock$: Observable; latestBlockNumber$: Observable; storedBlockLogs$: Observable; - waitForTransaction: (tx: Hex) => Promise; + waitForTransaction: (tx: Hex) => Promise; }; // TODO: add optional, original log to this? diff --git a/packages/store-sync/src/createStoreSync.ts b/packages/store-sync/src/createStoreSync.ts index 4431a27003..07c8279ab1 100644 --- a/packages/store-sync/src/createStoreSync.ts +++ b/packages/store-sync/src/createStoreSync.ts @@ -8,6 +8,7 @@ import { SyncOptions, SyncResult, internalTableIds, + WaitForTransactionResult, } from "./common"; import { createBlockStream } from "@latticexyz/block-logs-stream"; import { @@ -262,7 +263,7 @@ export async function createStoreSync( ); // TODO: move to its own file so we can test it, have its own debug instance, etc. - async function waitForTransaction(tx: Hex): Promise { + async function waitForTransaction(tx: Hex): Promise { debug("waiting for tx", tx); // This currently blocks for async call on each block processed @@ -271,17 +272,24 @@ export async function createStoreSync( // We use `mergeMap` instead of `concatMap` here to send the fetch request immediately when a new block range appears, // instead of sending the next request only when the previous one completed. mergeMap(async (blocks) => { - const txs = blocks.flatMap((block) => block.logs.map((op) => op.transactionHash).filter(isDefined)); - if (txs.includes(tx)) return true; + for (const block of blocks) { + const txs = block.logs.map((op) => op.transactionHash); + // If the transaction caused a log, it must have succeeded + if (txs.includes(tx)) { + return { blockNumber: block.blockNumber, status: "success" as const, transactionHash: tx }; + } + } try { const lastBlock = blocks[0]; debug("fetching tx receipt for block", lastBlock.blockNumber); - const receipt = await publicClient.getTransactionReceipt({ hash: tx }); - return lastBlock.blockNumber >= receipt.blockNumber; + const { status, blockNumber, transactionHash } = await publicClient.getTransactionReceipt({ hash: tx }); + if (lastBlock.blockNumber >= blockNumber) { + return { status, blockNumber, transactionHash }; + } } catch (error) { if (error instanceof TransactionReceiptNotFoundError) { - return false; + return; } throw error; } @@ -289,7 +297,7 @@ export async function createStoreSync( tap((result) => debug("has tx?", tx, result)), ); - await firstValueFrom(hasTransaction$.pipe(filter(identity))); + return await firstValueFrom(hasTransaction$.pipe(filter(isDefined))); } return {