From 0e9ae7dadc888015988bf0b3c3a1e039604b3257 Mon Sep 17 00:00:00 2001 From: Tenderdeve Date: Tue, 28 Apr 2026 12:37:04 +0530 Subject: [PATCH] fix: defer subscriber teardown in emit to prevent WebSocket subscription loss When a once("block") listener fires and no listeners remain, emit() immediately calls subscriber.stop(), sending eth_unsubscribe. But waitForTransaction re-registers asynchronously after an await, so the subscription is torn down before re-registration can happen. Defer the cleanup by pollingInterval ms and recheck listener count before stopping, giving once-listener callbacks time to re-register. Explicit teardown via off() and removeAllListeners() is unaffected. Fixes #5121 --- src.ts/providers/abstract-provider.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src.ts/providers/abstract-provider.ts b/src.ts/providers/abstract-provider.ts index 0d75b435cc..c18ebc94c2 100644 --- a/src.ts/providers/abstract-provider.ts +++ b/src.ts/providers/abstract-provider.ts @@ -1452,8 +1452,21 @@ export class AbstractProvider implements Provider { }); if (sub.listeners.length === 0) { - if (sub.started) { sub.subscriber.stop(); } - this.#subs.delete(sub.tag); + // Defer teardown to allow once-listeners (e.g. waitForTransaction) + // to re-subscribe before the underlying subscriber is stopped. + // Without this, socket-based providers (WebSocket) would + // eth_unsubscribe and immediately need to eth_subscribe again on + // every block during waitForTransaction, and depending on timing + // the re-subscription could be lost entirely, causing subsequent + // waitForTransaction calls to hang forever. + const tag = sub.tag; + this._setTimeout(() => { + const s = this.#subs.get(tag); + if (s && s.listeners.length === 0) { + if (s.started) { s.subscriber.stop(); } + this.#subs.delete(tag); + } + }, this.pollingInterval); } return (count > 0);