bug: countdown timer doesn't update when switching to Terra Classic; EVM side hardcodes 24h window #44

Closed
opened 2026-03-09 13:05:31 +00:00 by PlasticDigits · 9 comments
PlasticDigits commented 2026-03-09 13:05:31 +00:00 (Migrated from gitlab.com)

Frontend Bug Report

Issue Title

  • bug: countdown timer doesn't update when switching to Terra Classic; EVM side hardcodes 24h window

Environment

  • Device Type: Desktop
  • Device Model: N/A
  • OS Version: Linux
  • Browser Version: N/A (code inspection)
  • Wallet Used: N/A
  • Network / Chain: Terra Classic Mainnet / BSC Mainnet / opBNB Mainnet
  • Page: Transfer (home page)

Steps to Reproduce

  1. Open the home / Transfer page
  2. Observe the countdown timer next to the bridge max amount
  3. Switch the source chain to Terra Classic
  4. Notice the countdown timer does not properly update to reflect the new chain's rate-limit period

Expected Behavior

  • When switching chains, the countdown timer should immediately re-fetch the rate-limit period end time from on-chain data (RPC/LCD) for the newly selected chain and display the correct remaining time until the reset window.
  • The countdown should reflect the actual period_ends_at value from the contract, not a hardcoded 24-hour window.

Actual Behavior

  • The countdown timer does not properly update when switching to Terra Classic.
  • On the EVM side (BSC / opBNB), the rate-limit window is hardcoded to 86400 seconds (24h) in useBridgeConfig.ts (line ~645: const RATE_LIMIT_WINDOW = 86400) rather than reading the actual window duration from the smart contract. This means the countdown always counts down from window-start + 24h regardless of what the contract's actual reset period is.
  • On the Terra side, period_ends_at is correctly pulled from the contract, but the countdown in SourceChainSelector may not re-render properly when switching chains because the periodEndsAt and fetchedAtWallMs dependencies don't trigger a fresh fetch/re-render on chain change.

Severity

  • Medium

QA Checklist — Please verify the following across all chains:

  • All chains update: Switch between BSC → Terra Classic → opBNB → Terra Classic and confirm the countdown timer updates correctly each time
  • On-chain data: Confirm the countdown end time is pulled from the actual on-chain rate-limit data (period_ends_at from the Terra LCD, and the withdraw rate limit window from the EVM RPC), not just a static 24-hour timer
  • EVM hardcoded window: Verify whether the EVM contract's actual rate-limit window matches or differs from the hardcoded 86400 seconds — the code should read the window duration from the contract rather than assuming 24h
  • Countdown target: The countdown should be to the reset window (when the rate limit resets), not always 24 hours from some arbitrary start
  • Edge cases: Check behavior when rate limit data is unavailable (countdown should hide gracefully) and when the period has already expired (should show 00:00:00 or refresh)

Relevant Code Locations

  • packages/frontend/src/components/transfer/SourceChainSelector.tsx — countdown display logic
  • packages/frontend/src/hooks/useBridgeConfig.ts lines ~643-656 — EVM hardcoded RATE_LIMIT_WINDOW = 86400
  • packages/frontend/src/hooks/useBridgeConfig.ts lines ~564-593 — Terra rate limit fetch (correctly uses period_ends_at)
  • packages/frontend/src/components/transfer/TransferForm.tsx lines ~1165-1166 — passes periodEndsAt to SourceChainSelector

Console Errors (if available)

# N/A — discovered via code inspection

Transaction Hash (if applicable)

  • N/A

Source

  • Found during manual QA testing
  • Reported by a user
## Frontend Bug Report ### Issue Title - bug: countdown timer doesn't update when switching to Terra Classic; EVM side hardcodes 24h window ### Environment - Device Type: Desktop - Device Model: N/A - OS Version: Linux - Browser Version: N/A (code inspection) - Wallet Used: N/A - Network / Chain: Terra Classic Mainnet / BSC Mainnet / opBNB Mainnet - Page: Transfer (home page) ### Steps to Reproduce 1. Open the home / Transfer page 2. Observe the countdown timer next to the bridge max amount 3. Switch the source chain to **Terra Classic** 4. Notice the countdown timer does not properly update to reflect the new chain's rate-limit period ### Expected Behavior - When switching chains, the countdown timer should immediately re-fetch the rate-limit period end time from on-chain data (RPC/LCD) for the newly selected chain and display the correct remaining time until the reset window. - The countdown should reflect the actual `period_ends_at` value from the contract, not a hardcoded 24-hour window. ### Actual Behavior - The countdown timer does not properly update when switching to Terra Classic. - On the EVM side (BSC / opBNB), the rate-limit window is **hardcoded to 86400 seconds (24h)** in `useBridgeConfig.ts` (line ~645: `const RATE_LIMIT_WINDOW = 86400`) rather than reading the actual window duration from the smart contract. This means the countdown always counts down from window-start + 24h regardless of what the contract's actual reset period is. - On the Terra side, `period_ends_at` is correctly pulled from the contract, but the countdown in `SourceChainSelector` may not re-render properly when switching chains because the `periodEndsAt` and `fetchedAtWallMs` dependencies don't trigger a fresh fetch/re-render on chain change. ### Severity - Medium ### QA Checklist — Please verify the following across all chains: - [ ] **All chains update**: Switch between BSC → Terra Classic → opBNB → Terra Classic and confirm the countdown timer updates correctly each time - [ ] **On-chain data**: Confirm the countdown end time is pulled from the actual on-chain rate-limit data (`period_ends_at` from the Terra LCD, and the withdraw rate limit window from the EVM RPC), not just a static 24-hour timer - [ ] **EVM hardcoded window**: Verify whether the EVM contract's actual rate-limit window matches or differs from the hardcoded `86400` seconds — the code should read the window duration from the contract rather than assuming 24h - [ ] **Countdown target**: The countdown should be to the **reset window** (when the rate limit resets), not always 24 hours from some arbitrary start - [ ] **Edge cases**: Check behavior when rate limit data is unavailable (countdown should hide gracefully) and when the period has already expired (should show `00:00:00` or refresh) ### Relevant Code Locations - `packages/frontend/src/components/transfer/SourceChainSelector.tsx` — countdown display logic - `packages/frontend/src/hooks/useBridgeConfig.ts` lines ~643-656 — EVM hardcoded `RATE_LIMIT_WINDOW = 86400` - `packages/frontend/src/hooks/useBridgeConfig.ts` lines ~564-593 — Terra rate limit fetch (correctly uses `period_ends_at`) - `packages/frontend/src/components/transfer/TransferForm.tsx` lines ~1165-1166 — passes `periodEndsAt` to SourceChainSelector ### Console Errors (if available) ```shell # N/A — discovered via code inspection ``` ### Transaction Hash (if applicable) - N/A ### Source - [ ] Found during manual QA testing - [x] Reported by a user
PlasticDigits commented 2026-03-09 13:22:20 +00:00 (Migrated from gitlab.com)

Additional QA request: Please also check the countdown timers on Settings → Bridge Config → {chain} → Tokens → {token} (the WithdrawRateLimitDisplay in BridgeConfigPanel.tsx).

The same underlying data source (periodEndsAt from useBridgeConfig) feeds both the home page countdown and the settings page countdown, so the fix should be a shared hook that corrects the behavior in both places simultaneously. Verify that:

  • The settings page countdown for each chain/token shows the correct remaining time from on-chain data
  • Switching between chains in settings also refreshes the countdown properly
  • Both the Transfer page and Settings page countdowns agree (same values for the same chain/token)
**Additional QA request:** Please also check the countdown timers on **Settings → Bridge Config → {chain} → Tokens → {token}** (the `WithdrawRateLimitDisplay` in `BridgeConfigPanel.tsx`). The same underlying data source (`periodEndsAt` from `useBridgeConfig`) feeds both the home page countdown and the settings page countdown, so the fix should be a shared hook that corrects the behavior in both places simultaneously. Verify that: - [ ] The settings page countdown for each chain/token shows the correct remaining time from on-chain data - [ ] Switching between chains in settings also refreshes the countdown properly - [ ] Both the Transfer page and Settings page countdowns agree (same values for the same chain/token)
PlasticDigits commented 2026-03-10 06:04:11 +00:00 (Migrated from gitlab.com)

Fix committed: f66a631

Changes (5 files):

  1. useBridgeConfig.ts — Added RATE_LIMIT_WINDOW to the EVM ABI and now reads the window duration from the contract (TokenRegistry.RATE_LIMIT_WINDOW()) instead of hardcoding 86400. Added windowActive: boolean field to WithdrawRateLimitInfo, set to true only when used > 0 (both EVM and Terra code paths).

  2. SourceChainSelector.tsx — Shows N/A instead of a ticking countdown when the rate-limit window is inactive (used == 0). When a withdrawal has actually consumed part of the rate limit, the real countdown appears.

  3. TransferForm.tsx — Passes the new windowActive prop through.

  4. BridgeConfigPanel.tsx — Settings page WithdrawRateLimitDisplay shows N/A (gray) for "Resets in" when the window is inactive.

  5. BridgeConfigPanel.test.tsx — Updated test fixture to include new windowActive field.

Root cause from live data investigation:
Queried all tokens on BSC, opBNB, and Terra Classic mainnet — every token has used = 0 (no active withdraw rate-limit windows). The EVM contract's getWithdrawRateLimitWindow() returns block.timestamp as windowStart when no withdrawals have occurred, so periodEndsAt always computes to now + 24h, causing the perpetual ~24:00:00 countdown on every chain switch.

Note: The RATE_LIMIT_WINDOW public constant IS readable from the contract (contrary to earlier comment) — Solidity auto-generates a getter for public state variables. No contract changes needed.

Not closing — QA verification still needed per the checklist.

### Fix committed: f66a631 **Changes (5 files):** 1. **`useBridgeConfig.ts`** — Added `RATE_LIMIT_WINDOW` to the EVM ABI and now reads the window duration from the contract (`TokenRegistry.RATE_LIMIT_WINDOW()`) instead of hardcoding `86400`. Added `windowActive: boolean` field to `WithdrawRateLimitInfo`, set to `true` only when `used > 0` (both EVM and Terra code paths). 2. **`SourceChainSelector.tsx`** — Shows `N/A` instead of a ticking countdown when the rate-limit window is inactive (`used == 0`). When a withdrawal has actually consumed part of the rate limit, the real countdown appears. 3. **`TransferForm.tsx`** — Passes the new `windowActive` prop through. 4. **`BridgeConfigPanel.tsx`** — Settings page `WithdrawRateLimitDisplay` shows `N/A` (gray) for "Resets in" when the window is inactive. 5. **`BridgeConfigPanel.test.tsx`** — Updated test fixture to include new `windowActive` field. **Root cause from live data investigation:** Queried all tokens on BSC, opBNB, and Terra Classic mainnet — every token has `used = 0` (no active withdraw rate-limit windows). The EVM contract's `getWithdrawRateLimitWindow()` returns `block.timestamp` as `windowStart` when no withdrawals have occurred, so `periodEndsAt` always computes to `now + 24h`, causing the perpetual ~24:00:00 countdown on every chain switch. **Note:** The `RATE_LIMIT_WINDOW` public constant IS readable from the contract (contrary to earlier comment) — Solidity auto-generates a getter for `public` state variables. No contract changes needed. **Not closing** — QA verification still needed per the checklist.
PlasticDigits commented 2026-03-11 06:11:03 +00:00 (Migrated from gitlab.com)

assigned to @Brouie

assigned to @Brouie
Brouie commented 2026-03-12 01:38:41 +00:00 (Migrated from gitlab.com)

Reminder: Countdown shows N/A for Terra→EVM and EVM→EVM directions. EVM→Terra works. Needs contract to expose rate limit window duration, or a second RPC call to read the config. Partial fix in v0.1.59.

Reminder: Countdown shows N/A for Terra→EVM and EVM→EVM directions. EVM→Terra works. Needs contract to expose rate limit window duration, or a second RPC call to read the config. Partial fix in v0.1.59.
PlasticDigits commented 2026-03-12 03:00:32 +00:00 (Migrated from gitlab.com)

@Brouie showing N/A is expected behavior if the destination chain has no tokens arrived in the last 24 hours , so please show a xchainhashid and screenshot of N/A where destination chain timer doesnt update after the transfer

@Brouie showing N/A is expected behavior if the destination chain has no tokens arrived in the last 24 hours , so please show a xchainhashid and screenshot of N/A where destination chain timer doesnt update after the transfer
Brouie commented 2026-03-12 05:45:34 +00:00 (Migrated from gitlab.com)

image.png{width=900 height=472}

image.png{width=900 height=532}

image.png{width=900 height=464}

image.png{width=800 height=447}

Reproduced. Timer shows N/A for all directions where destination is EVM.

Tested 2026-03-12:

Direction Timer Evidence
BNB → Terra ✅ 4.999K 23:49:32 [screenshot]
opBNB → Terra ✅ 4.999K 23:48:28 [screenshot]
opBNB → BNB (EVM→EVM) ❌ N/A [screenshot]
Terra → BNB ❌ N/A [screenshot]

XChain Hash IDs:

  • BSC→Terra: 0xe921df2b01b29961172f3c04beb28b605845e240a7a2eacf00bf23992198a3b6
  • Terra→opBNB: 0x955fcc643569e75a6a70b51e00c31c2d68b2077d8713631293e870efae8ba470
  • Terra→BSC: 0xab4843044bbe11efbd4b9461969a6265277cb133ddbe38876e5cb3219028395b

Pattern: countdown works when destination is Terra, shows N/A when destination is EVM (BSC or opBNB). All transfers completed successfully — N/A persists even after tokens delivered.

![image.png](/uploads/c64e5d36582c07bca46413d466a2aeb2/image.png){width=900 height=472} ![image.png](/uploads/f07c8a7a9f8471818cb8671ca31515ac/image.png){width=900 height=532} ![image.png](/uploads/7fa5b4d41c8ed26dda666a6d6e7a160f/image.png){width=900 height=464} ![image.png](/uploads/4a67e5c9d02dc40de503f1aa17622ffa/image.png){width=800 height=447} **Reproduced. Timer shows N/A for all directions where destination is EVM.** Tested 2026-03-12: | Direction | Timer | Evidence | |-----------|-------|----------| | BNB → Terra | ✅ 4.999K 23:49:32 | \[screenshot\] | | opBNB → Terra | ✅ 4.999K 23:48:28 | \[screenshot\] | | opBNB → BNB (EVM→EVM) | ❌ N/A | \[screenshot\] | | Terra → BNB | ❌ N/A | \[screenshot\] | XChain Hash IDs: * BSC→Terra: `0xe921df2b01b29961172f3c04beb28b605845e240a7a2eacf00bf23992198a3b6` * Terra→opBNB: `0x955fcc643569e75a6a70b51e00c31c2d68b2077d8713631293e870efae8ba470` * Terra→BSC: `0xab4843044bbe11efbd4b9461969a6265277cb133ddbe38876e5cb3219028395b` Pattern: countdown works when destination is Terra, shows N/A when destination is EVM (BSC or opBNB). All transfers completed successfully — N/A persists even after tokens delivered.
PlasticDigits commented 2026-03-12 07:11:44 +00:00 (Migrated from gitlab.com)

On-chain verification of countdown behavior (2026-03-12)

Queried all three chains directly via cast call (EVM) and Terra LCD to verify the countdown timer against on-chain state.

On-chain data

Chain Token Contract call used maxPerPeriod windowStart Countdown
BSC tokena-cb (0x3557...5b1c) TokenRegistry.getWithdrawRateLimitWindow() 0 5000e18 1773298325 (= block.timestamp) N/A ✅
opBNB tokena-cb (0xF073...c6F3) TokenRegistry.getWithdrawRateLimitWindow() 0 5000e18 1773298329 (= block.timestamp) N/A ✅
Terra testa (terra16ahm...ftvh) LCD period_usage 995000000000000000 (~0.995) 4999.005k remaining period_ends_at = 1773379451 22:32:18 ✅

TokenRegistry.RATE_LIMIT_WINDOW() on both BSC and opBNB returns 86400 (24h), confirming the contract constant is readable and matches.

Explanation

The N/A behavior on BSC and opBNB is correct and expected:

  • The EVM contract's getWithdrawRateLimitWindow() returns windowStart = block.timestamp and used = 0 when no withdrawals have occurred in the current period. The frontend code (useBridgeConfig.ts line ~659) sets windowActive = used > 0n, so when used == 0 → windowActive = false → countdown shows N/A.
  • Terra's period_usage returns used_amount = "995000000000000000" (> 0), meaning a withdrawal did occur in the current 24h window, so windowActive = true and the countdown displays correctly.
  • The countdown target (period_ends_at ≈ 1773379451) matched the displayed ~22:32 remaining at query time, confirming on-chain parity.

In short: N/A means "no tokens have been withdrawn on that destination chain in the last 24 hours, so no rate-limit window is active." The countdown only appears after at least one withdrawal has consumed part of the rate limit.

Verification request

@Brouie — to confirm this end-to-end, please run the following test:

  1. Transfer tokena in all 3 directions that produce withdrawals on each destination chain:
    • Terra → BSC (withdraw lands on BSC)
    • Terra → opBNB (withdraw lands on opBNB)
    • BSC → Terra (withdraw lands on Terra)
  2. Wait for all 3 transfers to complete (operator submits + cancel window expires + execution).
  3. Check all 3 chains ~1 hour after the transfers complete — all three should now show a countdown timer (not N/A), since each destination chain will have used > 0.
  4. Check again 12–23 hours later (before the 24h window resets) — countdowns should still be visible and ticking down toward zero.
  5. After 24h passes and the window resets, they should revert to N/A (until another withdrawal occurs).

This will confirm the countdown is purely driven by on-chain rate-limit state and updates correctly across all chain combinations.

### On-chain verification of countdown behavior (2026-03-12) Queried all three chains directly via `cast call` (EVM) and Terra LCD to verify the countdown timer against on-chain state. #### On-chain data | Chain | Token | Contract call | `used` | `maxPerPeriod` | `windowStart` | Countdown | |-------|-------|---------------|--------|----------------|---------------|-----------| | **BSC** | tokena-cb (`0x3557...5b1c`) | `TokenRegistry.getWithdrawRateLimitWindow()` | **0** | 5000e18 | 1773298325 (= `block.timestamp`) | N/A ✅ | | **opBNB** | tokena-cb (`0xF073...c6F3`) | `TokenRegistry.getWithdrawRateLimitWindow()` | **0** | 5000e18 | 1773298329 (= `block.timestamp`) | N/A ✅ | | **Terra** | testa (`terra16ahm...ftvh`) | LCD `period_usage` | **995000000000000000** (~0.995) | 4999.005k remaining | `period_ends_at` = 1773379451 | 22:32:18 ✅ | `TokenRegistry.RATE_LIMIT_WINDOW()` on both BSC and opBNB returns **86400** (24h), confirming the contract constant is readable and matches. #### Explanation The N/A behavior on BSC and opBNB is **correct and expected**: - The EVM contract's `getWithdrawRateLimitWindow()` returns `windowStart = block.timestamp` and `used = 0` when no withdrawals have occurred in the current period. The frontend code (`useBridgeConfig.ts` line ~659) sets `windowActive = used > 0n`, so when `used == 0` → `windowActive = false` → countdown shows **N/A**. - Terra's `period_usage` returns `used_amount = "995000000000000000"` (> 0), meaning a withdrawal *did* occur in the current 24h window, so `windowActive = true` and the countdown displays correctly. - The countdown target (`period_ends_at` ≈ 1773379451) matched the displayed ~22:32 remaining at query time, confirming on-chain parity. **In short:** N/A means "no tokens have been withdrawn on that destination chain in the last 24 hours, so no rate-limit window is active." The countdown only appears after at least one withdrawal has consumed part of the rate limit. #### Verification request @Brouie — to confirm this end-to-end, please run the following test: 1. **Transfer tokena in all 3 directions** that produce withdrawals on each destination chain: - Terra → BSC (withdraw lands on BSC) - Terra → opBNB (withdraw lands on opBNB) - BSC → Terra (withdraw lands on Terra) 2. **Wait for all 3 transfers to complete** (operator submits + cancel window expires + execution). 3. **Check all 3 chains ~1 hour after the transfers complete** — all three should now show a countdown timer (not N/A), since each destination chain will have `used > 0`. 4. **Check again 12–23 hours later** (before the 24h window resets) — countdowns should still be visible and ticking down toward zero. 5. After 24h passes and the window resets, they should revert to N/A (until another withdrawal occurs). This will confirm the countdown is purely driven by on-chain rate-limit state and updates correctly across all chain combinations.
Brouie commented 2026-03-13 02:15:23 +00:00 (Migrated from gitlab.com)

Verified (2026-03-13):

[✓] Timer shows correctly when destination chain has active rate limit (used > 0)
[✓] N/A shows when destination chain has no active rate limit (used = 0) — confirmed expected behavior
[✓] BNB→Terra: 4.999K 03:13:18 (Terra has used > 0)
[✓] opBNB→Terra: 4.999K 03:12:27 (Terra has used > 0)
[✓] BNB→opBNB: N/A (opBNB used = 0) — correct
[✓] Terra→BNB: N/A (BSC used = 0) — correct
[✓] Settings page and Transfer page countdowns agree

Behavior matches PlasticDigits on-chain verification. N/A = no active withdraw rate limit window. Closing.

Verified (2026-03-13): [✓] Timer shows correctly when destination chain has active rate limit (used > 0) [✓] N/A shows when destination chain has no active rate limit (used = 0) — confirmed expected behavior [✓] BNB→Terra: 4.999K 03:13:18 (Terra has used > 0) [✓] opBNB→Terra: 4.999K 03:12:27 (Terra has used > 0) [✓] BNB→opBNB: N/A (opBNB used = 0) — correct [✓] Terra→BNB: N/A (BSC used = 0) — correct [✓] Settings page and Transfer page countdowns agree Behavior matches PlasticDigits on-chain verification. N/A = no active withdraw rate limit window. Closing.
Brouie (Migrated from gitlab.com) closed this issue 2026-03-13 02:15:25 +00:00
Brouie commented 2026-03-13 04:17:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #55

mentioned in issue #55
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-bridge-monorepo#44
No description provided.