Describe the bug
RequestOptions.signal cancels an active fetch, but it does not cancel the rate-limit backoff between attempts.
Both OpenSeaAPI.get and OpenSeaAPI.request pass the signal to _fetch, then call executeWithRateLimit with only the logger. After a 429/599 response, executeWithRateLimit waits in a non-abortable sleep(delayMs). If the caller aborts during that wait, the returned promise remains pending until the full Retry-After delay expires (currently capped at five minutes). Only the next _fetch attempt observes the already-aborted signal.
Observed states on v12.0.1:
| Abort timing |
Current result |
| Before the first request |
Rejects immediately with Request aborted |
| During an active fetch |
The fetch signal is aborted |
| During 429/599 retry backoff |
Remains pending until the delay expires |
Steps to reproduce
- Mock
fetch to return HTTP 429 with Retry-After: 30.
- Call
api.get(path, {}, { signal: controller.signal }) (the same behavior occurs through request/post).
- After the first attempt enters the retry delay, call
controller.abort().
- Observe that the API promise is still pending and no second fetch has run.
- Advance the retry timer by 30 seconds; only then does the next attempt observe the aborted signal and reject.
This is deterministic with Vitest fake timers: immediately after abort, the outcome remains pending; after advancing the backoff timer, it becomes rejected.
Expected behavior
Aborting the supplied signal while a request is waiting to retry should reject promptly and should not start another fetch attempt.
Environment
- Package version: 12.0.1
- Node.js version: 22.x
- Operating system: macOS
- Chain / network: not chain-specific
Additional context
A narrow, backward-compatible fix appears possible: allow RateLimitOptions to receive an optional AbortSignal, make only the retry delay abortable, and pass the existing request signal from get and request. This would preserve retry counts, Retry-After parsing, timeout behavior, and the existing Request aborted error shape.
Describe the bug
RequestOptions.signalcancels an active fetch, but it does not cancel the rate-limit backoff between attempts.Both
OpenSeaAPI.getandOpenSeaAPI.requestpass the signal to_fetch, then callexecuteWithRateLimitwith only the logger. After a 429/599 response,executeWithRateLimitwaits in a non-abortablesleep(delayMs). If the caller aborts during that wait, the returned promise remains pending until the fullRetry-Afterdelay expires (currently capped at five minutes). Only the next_fetchattempt observes the already-aborted signal.Observed states on v12.0.1:
Request abortedSteps to reproduce
fetchto return HTTP 429 withRetry-After: 30.api.get(path, {}, { signal: controller.signal })(the same behavior occurs throughrequest/post).controller.abort().This is deterministic with Vitest fake timers: immediately after abort, the outcome remains
pending; after advancing the backoff timer, it becomesrejected.Expected behavior
Aborting the supplied signal while a request is waiting to retry should reject promptly and should not start another fetch attempt.
Environment
Additional context
A narrow, backward-compatible fix appears possible: allow
RateLimitOptionsto receive an optionalAbortSignal, make only the retry delay abortable, and pass the existing request signal fromgetandrequest. This would preserve retry counts,Retry-Afterparsing, timeout behavior, and the existingRequest abortederror shape.