Conversation
viem's watchEvent uses eth_newFilter/eth_getFilterChanges internally, which fails on high-throughput chains like Arbitrum One when the filter accumulates more blocks than the RPC provider allows (Alchemy caps at 2000 blocks). This caused repeated InvalidInputRpcError crashes that kept the spot-indexer ECS task in a crash-loop on LMN. Switch to explicit eth_getLogs calls with a 1900-block cap per request, polling every 2 s. Transient RPC errors are retried with exponential back-off (up to 30 s) instead of immediately crashing. After 50 consecutive failures the task exits so ECS can restart it cleanly. New contract addresses from contractCreated events are added to the watch set dynamically — no need to tear down and recreate the watcher. Made-with: Cursor
## Summary - **Root cause:** `viem`'s `watchEvent` internally uses `eth_newFilter` / `eth_getFilterChanges`, which fails on high-throughput chains like Arbitrum One when the filter accumulates more blocks than the RPC provider allows (Alchemy caps at 2 000 blocks). This caused repeated `InvalidInputRpcError: invalid block range params` crashes that kept the spot-indexer ECS task in a crash-loop on LMN (production). - **Fix:** Replace `watchEvent` with explicit `eth_getLogs` calls capped at 1 900 blocks per request, polling every 2 seconds. Transient RPC errors are retried with exponential back-off (up to 30 s) instead of immediately crashing the process. After 50 consecutive failures the task exits so ECS can restart it cleanly. - **Bonus:** New contract addresses from `contractCreated` events are now added to the watch set dynamically — no need to tear down and recreate the watcher (simpler, fewer edge cases). ## What changed `src/services/listener.ts` — full rewrite of the event polling mechanism: | Before | After | |--------|-------| | `pc.watchEvent()` with `poll: true, pollingInterval: 1000` | Manual `pc.getLogs()` loop with bounded `fromBlock`/`toBlock` (max 1 900 blocks) | | Any error → `process.exit(1)` | Transient errors → exponential backoff retry; fatal after 50 consecutive failures | | `contractCreated` → unwatch + restart watcher | `contractCreated` → add to Set, next poll picks it up | ## Test plan - [ ] `npx tsc --noEmit` passes (verified locally) - [ ] `npx biome check` passes (verified locally) - [ ] Deploy to dev — confirm spot-indexer stays healthy and processes events - [ ] Deploy to LMN — confirm crash-loop resolves and indexer catches up - [ ] Monitor CloudWatch logs for `Starting bounded getLogs polling` and absence of `InvalidInputRpcError` Made with [Cursor](https://cursor.com)
The workflow had placeholder names (ecs-lumerin-marketplace-*, svc-lumerin-indexer-*,
tsk-lumerin-indexer) that don't match the actual AWS resources created by
Terraform in the hashprice-oracle stack:
- Cluster: ecs-hashprice-oracle-{env}
- Service: svc-spot-indexer-{env}
- Task: tsk-spot-indexer
Made-with: Cursor
## Summary
The deploy workflow had placeholder ECS names that don't match the
actual AWS resources created by Terraform in the hashprice-oracle stack:
| Field | Before (wrong) | After (correct) |
|-------|----------------|-----------------|
| Cluster | `ecs-lumerin-marketplace-{env}-use1` |
`ecs-hashprice-oracle-{env}` |
| Service | `svc-lumerin-indexer-{env}-use1` | `svc-spot-indexer-{env}`
|
| Task | `tsk-lumerin-indexer` | `tsk-spot-indexer` |
This caused the deploy step to fail with `AccessDeniedException` (and
would have failed with `ClusterNotFoundException` even with full
permissions).
## Test plan
- [ ] Merge and verify the push-triggered deploy succeeds with the
corrected names
Made with [Cursor](https://cursor.com)
LumerinIO
approved these changes
Mar 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes STG to MAIN for LMN (production) deployment. Fixes the spot-indexer crash-loop caused by Alchemy's block range limits on Arbitrum One.
Changes included
src/services/listener.ts— Replaceviem'swatchEvent(which useseth_newFilter/eth_getFilterChangesand exceeds Alchemy's 2000-block limit on Arbitrum) with boundedeth_getLogspolling (max 1900 blocks/request). Transient RPC errors are retried with exponential backoff instead of crashing..github/workflows/build.yml— Correct ECS cluster/service/task names to match actual Terraform-managed resources (ecs-hashprice-oracle-{env},svc-spot-indexer-{env},tsk-spot-indexer).Validation
Test plan
npx tsc --noEmitpassesnpx biome checkpassesMade with Cursor