fix(limits): guard missing limit-book orders on fresh deploy (#327) #803

Merged
PlasticDigits merged 2 commits from cursor/gitlab-issue-workflow-35fc into main 2026-06-05 13:07:11 +00:00
PlasticDigits commented 2026-06-05 12:41:38 +00:00 (Migrated from gitlab.com)

Summary

Fixes https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/327 — /limits could show raw Cannot read properties of undefined (reading 'length') when switching pairs right after a fresh deploy while the indexer is still syncing.

Root cause: flatMap((p) => p.orders) treats a missing orders field as a single undefined row (the ?? [] fallback never runs). Downstream render/hint logic then throws. A secondary path was getAllPairsPaginated reading resp.pairs.length when LCD returned a body without pairs.

Fix:

  • normalizeLimitBookPageResponse in getPairLimitBookPage (defaults orders to [])
  • flattenLimitBookPages / OrderBookPanel use safe flattening
  • getAllPairsPaginated uses resp.pairs ?? []
  • Error boundary last-resort: humanize raw Cannot read properties of undefined TypeErrors

Playbook: skills/AGENTS_FRONTEND_DEEP_ORDER_BOOK.md rule #9.

Acceptance checklist

Criterion Verification Result
All pairs load or show empty/loading state — no raw JS crash npm run test -- --run src/components/trade/__tests__/OrderBookPanel.test.tsx src/pages/LimitOrdersPage.test.tsx PASS
Missing orders on limit-book page treated as empty book npm run test -- --run src/utils/__tests__/limitBookPagination.test.ts src/services/indexer/__tests__/client.test.ts PASS
Missing pairs on factory LCD page does not throw in pagination npm run test -- --run src/services/terraclassic/__tests__/factory.test.ts PASS
Error boundary does not show raw TypeError for undefined .length npm run test -- --run src/utils/__tests__/humanizeUserFacingError.test.ts PASS
Fresh deploy manual repro (pair switch during indexer sync) Not run in CI — needs LocalTerra + indexer-dev SKIP

Verification for third parties

cd frontend-dapp
export PATH="$HOME/.nvm/versions/node/$(cat ../.nvmrc)/bin:$PATH"
npm run test -- --run \
  src/utils/__tests__/limitBookPagination.test.ts \
  src/utils/__tests__/limitBookInsertHint.test.ts \
  src/services/terraclassic/__tests__/factory.test.ts \
  src/services/indexer/__tests__/client.test.ts \
  src/components/trade/__tests__/OrderBookPanel.test.tsx \
  src/utils/__tests__/humanizeUserFacingError.test.ts \
  src/pages/LimitOrdersPage.test.tsx

Manual (optional): make setup-cloud-localterra, make dev, open /limits, switch pairs before indexer finishes initial sync — expect empty book or outage banner, not a crash.

## Summary Fixes https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/327 — `/limits` could show raw `Cannot read properties of undefined (reading 'length')` when switching pairs right after a fresh deploy while the indexer is still syncing. **Root cause:** `flatMap((p) => p.orders)` treats a missing `orders` field as a single `undefined` row (the `?? []` fallback never runs). Downstream render/hint logic then throws. A secondary path was `getAllPairsPaginated` reading `resp.pairs.length` when LCD returned a body without `pairs`. **Fix:** - `normalizeLimitBookPageResponse` in `getPairLimitBookPage` (defaults `orders` to `[]`) - `flattenLimitBookPages` / `OrderBookPanel` use safe flattening - `getAllPairsPaginated` uses `resp.pairs ?? []` - Error boundary last-resort: humanize raw `Cannot read properties of undefined` TypeErrors Playbook: `skills/AGENTS_FRONTEND_DEEP_ORDER_BOOK.md` rule #9. ## Acceptance checklist | Criterion | Verification | Result | |-----------|--------------|--------| | All pairs load or show empty/loading state — no raw JS crash | `npm run test -- --run src/components/trade/__tests__/OrderBookPanel.test.tsx src/pages/LimitOrdersPage.test.tsx` | PASS | | Missing `orders` on limit-book page treated as empty book | `npm run test -- --run src/utils/__tests__/limitBookPagination.test.ts src/services/indexer/__tests__/client.test.ts` | PASS | | Missing `pairs` on factory LCD page does not throw in pagination | `npm run test -- --run src/services/terraclassic/__tests__/factory.test.ts` | PASS | | Error boundary does not show raw TypeError for undefined `.length` | `npm run test -- --run src/utils/__tests__/humanizeUserFacingError.test.ts` | PASS | | Fresh deploy manual repro (pair switch during indexer sync) | Not run in CI — needs LocalTerra + indexer-dev | SKIP | ## Verification for third parties ```bash cd frontend-dapp export PATH="$HOME/.nvm/versions/node/$(cat ../.nvmrc)/bin:$PATH" npm run test -- --run \ src/utils/__tests__/limitBookPagination.test.ts \ src/utils/__tests__/limitBookInsertHint.test.ts \ src/services/terraclassic/__tests__/factory.test.ts \ src/services/indexer/__tests__/client.test.ts \ src/components/trade/__tests__/OrderBookPanel.test.tsx \ src/utils/__tests__/humanizeUserFacingError.test.ts \ src/pages/LimitOrdersPage.test.tsx ``` Manual (optional): `make setup-cloud-localterra`, `make dev`, open `/limits`, switch pairs before indexer finishes initial sync — expect empty book or outage banner, not a crash.
PlasticDigits commented 2026-06-05 12:41:48 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-05 12:41:49 +00:00 (Migrated from gitlab.com)

mentioned in issue #327

mentioned in issue #327
PlasticDigits commented 2026-06-05 12:43:06 +00:00 (Migrated from gitlab.com)

Security review

Commit reviewed: 499e642c5a0670a1c4fda2f748ca8a09c39b51d4
Scope: Defensive frontend hardening for missing orders / pairs fields on limit-book and factory LCD responses, plus error-boundary humanization for raw TypeError messages (#327).

Outcome: FINDINGS: 0 medium+

Summary

Reviewed all added/modified production paths:

  • normalizeLimitBookPageResponse / flattenLimitBookPages / OrderBookPanel — safe coercion of missing or partial indexer pages to empty order lists; null entries filtered; no new attacker-controlled sinks (React text rendering via existing formatters; no dangerouslySetInnerHTML).
  • getPairLimitBookPage — normalization applied at fetch boundary; pairAddr still routed to configured indexer base URL only (unchanged SSRF surface).
  • getAllPairsPaginated — resp.pairs ?? [] prevents throw on malformed LCD page; pagination loop terminates cleanly on empty page (no DoS loop).
  • sanitizeOpaqueErrorMessage — replaces runtime TypeError strings with generic copy; output remains React text nodes in ErrorBoundary (no XSS vector).

Indexer integrity / empty-book display: A hostile or stale indexer could already influence displayed book depth; this MR reduces crash surface and defaults to empty state rather than introducing new trust boundaries. Limit placement still relies on on-chain contract validation for hint_after_order_id (unchanged).

Inline threads: None — no medium+ findings to anchor.


Automated security review (MR webhook).

## Security review **Commit reviewed:** `499e642c5a0670a1c4fda2f748ca8a09c39b51d4` **Scope:** Defensive frontend hardening for missing `orders` / `pairs` fields on limit-book and factory LCD responses, plus error-boundary humanization for raw TypeError messages (#327). **Outcome:** `FINDINGS: 0` medium+ ### Summary Reviewed all added/modified production paths: - `normalizeLimitBookPageResponse` / `flattenLimitBookPages` / `OrderBookPanel` — safe coercion of missing or partial indexer pages to empty order lists; null entries filtered; no new attacker-controlled sinks (React text rendering via existing formatters; no `dangerouslySetInnerHTML`). - `getPairLimitBookPage` — normalization applied at fetch boundary; `pairAddr` still routed to configured indexer base URL only (unchanged SSRF surface). - `getAllPairsPaginated` — `resp.pairs ?? []` prevents throw on malformed LCD page; pagination loop terminates cleanly on empty page (no DoS loop). - `sanitizeOpaqueErrorMessage` — replaces runtime TypeError strings with generic copy; output remains React text nodes in `ErrorBoundary` (no XSS vector). **Indexer integrity / empty-book display:** A hostile or stale indexer could already influence displayed book depth; this MR reduces crash surface and defaults to empty state rather than introducing new trust boundaries. Limit placement still relies on on-chain contract validation for `hint_after_order_id` (unchanged). **Inline threads:** None — no medium+ findings to anchor. --- Automated security review (MR webhook).
PlasticDigits commented 2026-06-05 13:07:12 +00:00 (Migrated from gitlab.com)

mentioned in commit 9d97e98108

mentioned in commit 9d97e98108c3e4314dbaefb56f5cdceb177803aa
PlasticDigits (Migrated from gitlab.com) merged commit 9d97e98108 into main 2026-06-05 13:07:12 +00:00
PlasticDigits commented 2026-06-08 08:43:12 +00:00 (Migrated from gitlab.com)

mentioned in commit a40f5e351c

mentioned in commit a40f5e351cf562c38094a0a0e3f971484cfc1712
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic!803
No description provided.