Post-limit-placement cancel ID polling silently blocked by CORS; cancel field never auto-fills #131

Closed
opened 2026-05-05 15:59:45 +00:00 by totdking · 11 comments
totdking commented 2026-05-05 15:59:45 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-05-05 16:04:35 +00:00 (Migrated from gitlab.com)

Issue Summary

After a successful limit order placement, the frontend polls the indexer to retrieve the new order ID and auto-fill the cancel field. All poll requests are blocked by the browser's CORS policy due to a host mismatch between the indexer's allowed origin (http://localhost:5173) and the indexer URL used in the app (http://127.0.0.1:3001). The catch {} block discards all errors silently, so the cancel field stays empty and the user cannot cancel their live order without manually locating the order ID.


Reproduction Steps

  1. Complete full local setup (LocalTerra running, contracts deployed, indexer running, frontend running)
  2. Connect a Keplr wallet with sufficient LUNC and escrow tokens
  3. Navigate to /limits, select a pair, enter a valid amount, click Place Limit
  4. Approve both transactions — tx confirms on-chain successfully
  5. Observe the cancel order ID field remains empty after placement
  6. Open DevTools → Network → filter for limit-placements — observe net::ERR_FAILED 200 (OK) on every poll request

Expected Behavior

After a successful limit placement, the frontend should retrieve the new order ID from the indexer and auto-fill the cancel field so the user can immediately cancel if needed.


Actual Behavior

Every poll request to http://127.0.0.1:3001/api/v1/pairs/<pair>/limit-placements?limit=100 is blocked by CORS. The browser receives a 200 response but rejects it because the Access-Control-Allow-Origin header (http://localhost:5173) does not match the 127.0.0.1 host. The catch {} block in LimitOrdersPage.tsx:354 discards all errors silently. The cancel field is never populated.

Console error observed:

GET http://127.0.0.1:3001/api/v1/pairs/<pair_address>/limit-placements?limit=100 net::ERR_FAILED 200 (OK)
fetchJson    @    client.ts:37
(anonymous)  @    client.ts:188
(anonymous)  @    LimitOrdersPage.tsx:140
(anonymous)  @    LimitOrdersPage.tsx:358

Workaround: manually open http://127.0.0.1:3001/api/v1/pairs/<pair_address>/limit-placements?limit=100 in the browser, filter by your wallet address, and paste the order_id into the cancel field.


Screenshots

  1. Executing the transaction

Screenshot 2026-05-05 at 16.49.02.png{width="900" height="547"}2. After the tx is carried out successfully

Screenshot 2026-05-05 at 16.49.30.png{width="900" height="547"}3. Viewing the transaction logs to manually fetch the id

Screenshot 2026-05-05 at 16.49.55.png{width="900" height="547"}


Root Cause

VITE_INDEXER_URL=http://127.0.0.1:3001 in .env.local and the indexer's CORS_ORIGINS default of http://localhost:5173 (config.rs:267) are mismatched. The browser treats localhost and 127.0.0.1 as different origins and blocks the response even though the server returns 200.

Relevant files:

  • frontend-dapp/.env.local — VITE_INDEXER_URL=http://127.0.0.1:3001
  • indexer/src/config.rs:267 — local dev CORS_ORIGINS default
  • frontend-dapp/src/pages/LimitOrdersPage.tsx:354 — silent catch {} hides CORS errors

Environment Details

Field Value
OS macOS (Apple M1 Pro, 14-inch)
Browser Google Chrome (desktop)
Network localterra (local Docker)
Frontend VITE_NETWORK=local npm run dev
Indexer Running (make indexer-dev)
Contracts Deployed via make deploy-local

Wallet / Device Details

  • Wallet: Keplr browser extension
  • Network configured in Keplr: localterra - RPC http://localhost:26657

Severity / Impact

local dev only. No production impact.

Production uses real domain names; the localhost vs 127.0.0.1 ambiguity does not exist there. In local dev, this silently breaks the post-placement UX on every session. The user cannot cancel a live limit order without manually querying the indexer.

CC: @PlasticDigits

### Issue Summary After a successful limit order placement, the frontend polls the indexer to retrieve the new order ID and auto-fill the cancel field. All poll requests are blocked by the browser's CORS policy due to a host mismatch between the indexer's allowed origin (`http://localhost:5173`) and the indexer URL used in the app (`http://127.0.0.1:3001`). The `catch {}` block discards all errors silently, so the cancel field stays empty and the user cannot cancel their live order without manually locating the order ID. --- ### Reproduction Steps 1. Complete full local setup (LocalTerra running, contracts deployed, indexer running, frontend running) 2. Connect a Keplr wallet with sufficient LUNC and escrow tokens 3. Navigate to `/limits`, select a pair, enter a valid amount, click **Place Limit** 4. Approve both transactions — tx confirms on-chain successfully 5. Observe the cancel order ID field remains empty after placement 6. Open DevTools → Network → filter for `limit-placements` — observe `net::ERR_FAILED 200 (OK)` on every poll request --- ### Expected Behavior After a successful limit placement, the frontend should retrieve the new order ID from the indexer and auto-fill the cancel field so the user can immediately cancel if needed. --- ### Actual Behavior Every poll request to `http://127.0.0.1:3001/api/v1/pairs/<pair>/limit-placements?limit=100` is blocked by CORS. The browser receives a 200 response but rejects it because the `Access-Control-Allow-Origin` header (`http://localhost:5173`) does not match the `127.0.0.1` host. The `catch {}` block in `LimitOrdersPage.tsx:354` discards all errors silently. The cancel field is never populated. Console error observed: ``` GET http://127.0.0.1:3001/api/v1/pairs/<pair_address>/limit-placements?limit=100 net::ERR_FAILED 200 (OK) fetchJson @ client.ts:37 (anonymous) @ client.ts:188 (anonymous) @ LimitOrdersPage.tsx:140 (anonymous) @ LimitOrdersPage.tsx:358 ``` Workaround: manually open `http://127.0.0.1:3001/api/v1/pairs/<pair_address>/limit-placements?limit=100` in the browser, filter by your wallet address, and paste the `order_id` into the cancel field. --- ### Screenshots 1. Executing the transaction ![Screenshot 2026-05-05 at 16.49.02.png](/uploads/2de21d146f0472eae6c770568b27f6cb/Screenshot_2026-05-05_at_16.49.02.png){width="900" height="547"}2. After the tx is carried out successfully ![Screenshot 2026-05-05 at 16.49.30.png](/uploads/f90e35d890d8c4aace489a49e6fff622/Screenshot_2026-05-05_at_16.49.30.png){width="900" height="547"}3. Viewing the transaction logs to manually fetch the id ![Screenshot 2026-05-05 at 16.49.55.png](/uploads/000d3e20b5e948064441f3aff802485c/Screenshot_2026-05-05_at_16.49.55.png){width="900" height="547"} --- ### Root Cause `VITE_INDEXER_URL=http://127.0.0.1:3001` in `.env.local` and the indexer's `CORS_ORIGINS` default of `http://localhost:5173` (`config.rs:267`) are mismatched. The browser treats `localhost` and `127.0.0.1` as different origins and blocks the response even though the server returns 200. Relevant files: - `frontend-dapp/.env.local` — `VITE_INDEXER_URL=http://127.0.0.1:3001` - `indexer/src/config.rs:267` — local dev `CORS_ORIGINS` default - `frontend-dapp/src/pages/LimitOrdersPage.tsx:354` — silent `catch {}` hides CORS errors --- ### Environment Details | Field | Value | |-------|-------| | OS | macOS (Apple M1 Pro, 14-inch) | | Browser | Google Chrome (desktop) | | Network | `localterra` (local Docker) | | Frontend | `VITE_NETWORK=local npm run dev` | | Indexer | Running (`make indexer-dev`) | | Contracts | Deployed via `make deploy-local` | --- ### Wallet / Device Details - **Wallet:** Keplr browser extension - **Network configured in Keplr:** localterra - RPC `http://localhost:26657` --- ### Severity / Impact **local dev only. No production impact.** Production uses real domain names; the `localhost` vs `127.0.0.1` ambiguity does not exist there. In local dev, this silently breaks the post-placement UX on every session. The user cannot cancel a live limit order without manually querying the indexer. CC: @PlasticDigits
Brouie commented 2026-05-05 23:55:35 +00:00 (Migrated from gitlab.com)

mentioned in issue #133

mentioned in issue #133
Brouie commented 2026-05-06 04:44:07 +00:00 (Migrated from gitlab.com)

@PlasticDigits — adding context on this one. the bare catch in the post-place poll is the proximate reason the CORS error stayed invisible. there is no console signal, so any failure mode (CORS misconfig, indexer 5xx, JSON parse error, DNS) all look identical to "indexer not running locally" — which is the comment intent.

specifically:

LimitOrdersPage.tsx:148-150

} catch {
  /* indexer may be down in local dev */
}

TradeOrderTicket.tsx:131-133 has the same pattern.

minimum visibility fix is one line per site: catch (err) { console.warn("[limit-place] indexer poll failed:", err) }. that would not have prevented the CORS misconfig but it would have surfaced the failure in devtools immediately rather than requiring a network-tab inspection to find it.

a heavier option would be a one-time toast after N consecutive failures of the same shape ("Indexer unreachable, manual order ID lookup required") so non-power-users get a hint that the cancel-id field is not going to populate. but that is belt-and-suspenders on top of the actual CORS fix you would already be making.

the underlying fix for #131 itself is still the localhost vs 127.0.0.1 origin mismatch — VITE_INDEXER_URL and CORS_ORIGINS need to agree. just wanted to flag that even after that is fixed, the silent catch will keep masking future issues unless the logging goes in.

@PlasticDigits — adding context on this one. the bare `catch` in the post-place poll is the proximate reason the CORS error stayed invisible. there is no console signal, so any failure mode (CORS misconfig, indexer 5xx, JSON parse error, DNS) all look identical to "indexer not running locally" — which is the comment intent. specifically: `LimitOrdersPage.tsx:148-150` ``` } catch { /* indexer may be down in local dev */ } ``` `TradeOrderTicket.tsx:131-133` has the same pattern. minimum visibility fix is one line per site: `catch (err) { console.warn("[limit-place] indexer poll failed:", err) }`. that would not have prevented the CORS misconfig but it would have surfaced the failure in devtools immediately rather than requiring a network-tab inspection to find it. a heavier option would be a one-time toast after N consecutive failures of the same shape ("Indexer unreachable, manual order ID lookup required") so non-power-users get a hint that the cancel-id field is not going to populate. but that is belt-and-suspenders on top of the actual CORS fix you would already be making. the underlying fix for #131 itself is still the localhost vs 127.0.0.1 origin mismatch — `VITE_INDEXER_URL` and `CORS_ORIGINS` need to agree. just wanted to flag that even after that is fixed, the silent catch will keep masking future issues unless the logging goes in.
PlasticDigits commented 2026-05-06 08:23:10 +00:00 (Migrated from gitlab.com)

OK, we need to make sure we have full logging

OK, we need to make sure we have full logging
PlasticDigits commented 2026-05-06 08:27:05 +00:00 (Migrated from gitlab.com)

mentioned in commit 3837558ca8

mentioned in commit 3837558ca82d43bd3244e5ffd96b2530fa896614
PlasticDigits commented 2026-05-06 08:27:43 +00:00 (Migrated from gitlab.com)

mentioned in commit 454bad0f7f

mentioned in commit 454bad0f7f909b216cc59afaf54e3068f0bb3016
PlasticDigits commented 2026-05-06 08:27:56 +00:00 (Migrated from gitlab.com)

Landed on main (3837558 + 454bad0)

What changed

  • Indexer sample env: indexer/.env.example now lists both http://localhost:5173 and http://127.0.0.1:5173 (plus matching :4173 preview origins) in CORS_ORIGINS, aligned with scripts/deploy-dex-local.sh so copy-paste local setups do not trip cross-host CORS when Vite is opened via 127.0.0.1.
  • Frontend visibility: shared warnIndexerPlacementPollFailed logs [limit-place] indexer poll failed: from LimitOrdersPage and TradeOrderTicket post-place polls (Vitest in warnIndexerPlacementPollFailed.test.ts).
  • Docs / agent cross-links: docs/frontend.md § Local dev indexer CORS, docs/indexer-invariants.md § Local dev CORS (stable anchor), docs/environment-matrix.md, docs/limit-orders.md, docs/testing.md, frontend-dapp/.env.example comment; skills/AGENTS_LOCALNET_TRADING_SWARM.md + skills/AGENTS_FRONTEND_PRODUCTION_BUILD.md.

Invariant (local dev)

Browsers treat localhost and 127.0.0.1 as different origins. Indexer Access-Control-Allow-Origin must echo the page Origin; listing only one hostname breaks fetch from the other even when the HTTP status is 200.

Verification checklist

  • Copy fresh indexer/.env.example → local indexer env (or merge the new CORS_ORIGINS line); restart indexer.
  • Open the dApp at http://127.0.0.1:5173, place a limit with indexer up → Cancel Order ID auto-fills after indexer catches the placement (no CORS errors in console).
  • Repeat with http://localhost:5173 → same behavior.
  • Deliberately misconfigure CORS_ORIGINS (only one hostname) → console shows [limit-place] indexer poll failed: while polling (DevTools visible signal).
  • cd frontend-dapp && npm ci && npm run test:run passes.

@Requesting verification from @totdking — please confirm on your macOS/Chrome LocalTerra setup; leaving this issue open until you sign off.

## Landed on `main` (3837558 + 454bad0) ### What changed - **Indexer sample env:** `indexer/.env.example` now lists **both** `http://localhost:5173` and `http://127.0.0.1:5173` (plus matching `:4173` preview origins) in `CORS_ORIGINS`, aligned with `scripts/deploy-dex-local.sh` so copy-paste local setups do not trip cross-host CORS when Vite is opened via `127.0.0.1`. - **Frontend visibility:** shared `warnIndexerPlacementPollFailed` logs `[limit-place] indexer poll failed:` from **LimitOrdersPage** and **TradeOrderTicket** post-place polls (Vitest in `warnIndexerPlacementPollFailed.test.ts`). - **Docs / agent cross-links:** `docs/frontend.md` § Local dev indexer CORS, `docs/indexer-invariants.md` § Local dev CORS (stable anchor), `docs/environment-matrix.md`, `docs/limit-orders.md`, `docs/testing.md`, `frontend-dapp/.env.example` comment; `skills/AGENTS_LOCALNET_TRADING_SWARM.md` + `skills/AGENTS_FRONTEND_PRODUCTION_BUILD.md`. ### Invariant (local dev) Browsers treat **`localhost` and `127.0.0.1` as different origins.** Indexer `Access-Control-Allow-Origin` must echo the page `Origin`; listing only one hostname breaks `fetch` from the other even when the HTTP status is 200. ### Verification checklist - [ ] Copy fresh `indexer/.env.example` → local indexer env (or merge the new `CORS_ORIGINS` line); restart indexer. - [ ] Open the dApp at **`http://127.0.0.1:5173`**, place a limit with indexer up → **Cancel Order ID** auto-fills after indexer catches the placement (no CORS errors in console). - [ ] Repeat with **`http://localhost:5173`** → same behavior. - [ ] Deliberately misconfigure `CORS_ORIGINS` (only one hostname) → console shows `[limit-place] indexer poll failed:` while polling (DevTools visible signal). - [ ] `cd frontend-dapp && npm ci && npm run test:run` passes. @Requesting verification from @totdking — please confirm on your macOS/Chrome LocalTerra setup; leaving this issue **open** until you sign off.
totdking commented 2026-05-06 13:56:22 +00:00 (Migrated from gitlab.com)

Verification checklist

  • Copy fresh indexer/.env.example → local indexer env (or merge the new CORS_ORIGINS line); restart indexer.
  • Open the dApp at http://127.0.0.1:5173, place a limit with indexer up → Cancel Order ID auto-fills after indexer catches the placement (no CORS errors in console). (Actual port is 3000, not 5173 , vite.config.ts hardcodes port: 3000)
  • Repeat with http://localhost:5173 → same behavior. (Actual port is 3000, not 5173 , vite.config.ts hardcodes port: 3000)
  • Deliberately misconfigure CORS_ORIGINS (only one hostname) → console shows [limit-place] indexer poll failed: while polling (DevTools visible signal).
  • cd frontend-dapp && npm ci && npm run test:run passes.

Visual checklist

cancel ID polling after limit placement.

Observed: I firstly "delibrately misconfiguring" CORS_ORIGINS ((CORS_ORIGINS="http://localhost:3000" cargo run). Normal make indexer-dev resolves the cancel ID correctly.),

Then placing a limit order, the frontend polls the indexer for the new order ID. This poll is being blocked by CORS in the browser:

http://127.0.0.1:3001/api/v1/pairs/.../limit-placements?limit=100                                                                       
from origin 'http://127.0.0.1:3000' blocked — no Access-Control-Allow-Origin header.                                                    
                                                                                                                                        
GET http://127.0.0.1:3001/...  net::ERR_FAILED 200 (OK)                                                                                 
[limit-place] indexer poll failed: TypeError: Failed to fetch                                                                           

The indexer returns a valid 200 response but the browser blocks the frontend from reading it because http://127.0.0.1:3000 is not in the indexer's CORS_ORIGINS. The default in config.rs:267 only allows http://localhost:5173, neither the actual frontend port (3000) nor
the 127.0.0.1 hostname are covered.

The warnIndexerPlacementPollFailed.ts logging is working the failure is now visible in console instead of silent.

Fix needed: add http://127.0.0.1:3000 and http://localhost:3000 to CORS_ORIGINS defaults in config.rs:267.

Environment: Chrome, 1470×864, Station wallet, LocalTerra local stack.

cc : @PlasticDigits

### Verification checklist * [x] Copy fresh `indexer/.env.example` → local indexer env (or merge the new `CORS_ORIGINS` line); restart indexer. * [x] Open the dApp at **`http://127.0.0.1:5173`**, place a limit with indexer up → **Cancel Order ID** auto-fills after indexer catches the placement (no CORS errors in console). (Actual port is 3000, not 5173 , vite.config.ts hardcodes port: 3000) * [x] Repeat with **`http://localhost:5173`** → same behavior. (Actual port is 3000, not 5173 , vite.config.ts hardcodes port: 3000) * [x] Deliberately misconfigure `CORS_ORIGINS` (only one hostname) → console shows `[limit-place] indexer poll failed:` while polling (DevTools visible signal). * [x] `cd frontend-dapp && npm ci && npm run test:run` passes. ### Visual checklist cancel ID polling after limit placement. Observed: I firstly "delibrately misconfiguring" `CORS_ORIGINS` ((CORS_ORIGINS="[http://localhost:3000](http://localhost:3000)" cargo run). Normal make indexer-dev resolves the cancel ID correctly.), Then placing a limit order, the frontend polls the indexer for the new order ID. This poll is being blocked by CORS in the browser: ``` http://127.0.0.1:3001/api/v1/pairs/.../limit-placements?limit=100 from origin 'http://127.0.0.1:3000' blocked — no Access-Control-Allow-Origin header. GET http://127.0.0.1:3001/... net::ERR_FAILED 200 (OK) [limit-place] indexer poll failed: TypeError: Failed to fetch ``` The indexer returns a valid 200 response but the browser blocks the frontend from reading it because [http://127.0.0.1:3000](http://127.0.0.1:3000) is not in the indexer's CORS_ORIGINS. The default in config.rs:267 only allows [http://localhost:5173](http://localhost:5173), neither the actual frontend port (3000) nor\ the 127.0.0.1 hostname are covered. The warnIndexerPlacementPollFailed.ts logging is working the failure is now visible in console instead of silent. Fix needed: add [http://127.0.0.1:3000](http://127.0.0.1:3000) and [http://localhost:3000](http://localhost:3000) to CORS_ORIGINS defaults in config.rs:267. Environment: Chrome, 1470×864, Station wallet, LocalTerra local stack. cc : @PlasticDigits
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-06 14:01:09 +00:00
PlasticDigits commented 2026-05-06 14:01:11 +00:00 (Migrated from gitlab.com)

Great, thanks for testing the cors via misconfiguration. Clsoing, cors origins should not have localhost defaults, should require setting cors properly

Great, thanks for testing the cors via misconfiguration. Clsoing, cors origins should not have localhost defaults, should require setting cors properly
PlasticDigits commented 2026-06-07 12:14:12 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
PlasticDigits commented 2026-08-27 08:27:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #629

mentioned in issue #629
Sign in to join this conversation.
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#131
No description provided.