MegaETH → Terra tdec hits 'EXECUTION BLOCKED: exceeds maximum daily rate limit' on tiny 0.001 amount after multiple successful prior rows #130

Open
opened 2026-05-01 07:54:28 +00:00 by Brouie · 13 comments
Brouie commented 2026-05-01 07:54:28 +00:00 (Migrated from gitlab.com)

Reproducible 2026-05-01: MegaETH → Terra tdec, 0.001 tokenc-cb input (0.000995 after fee), wallet 0xc46b15...80650.

Stepper goes through DEPOSIT + SUBMIT HASH + APPROVAL clean, then on COMPLETE step shows 'EXECUTION BLOCKED — The transfer amount exceeds the maximum daily rate limit. It cannot be executed even after the rate window resets. Contact support for assistance.'

xchain hash: 0xea7479193a3391ec98a61389576f9d465b40e8703816842bd7341960dd16535c
source tx: 0xcb13430aa30a65972960aa0b4df65da71137555c1abc0aa492ac98241e7a57bb
destination tx: 44C57ACE9139CEED38C2D6D257560A17022A98B600C0D4FEDF52873ACE49943A

Behavior is wrong because:

  • 0.001 tdec is far below any sensible per-tx cap (~$0 worth)
  • Min for this route shows 0.000001005025125628 tokenc-cb so 0.001 is 1000x above min
  • 'cannot be executed even after rate window resets' wording suggests the cap accounting is treating this row as outsized, which it isn't

Likely a daily-cumulative cap collision: prior MegaETH outbound rows in this session (testa, testb, tdec to BSC/opBNB/Terra/Solana) may have filled the per-token-per-destination daily window and frontend is reporting 'exceeds maximum' on subsequent. Worth checking the daily cap accounting on tdec → Terra path specifically.

Same row family as #127 in the sense that the user-facing copy doesn't match what's actually wrong.

P2 medium. cc @PlasticDigits

Reproducible 2026-05-01: MegaETH → Terra tdec, 0.001 tokenc-cb input (0.000995 after fee), wallet 0xc46b15...80650. Stepper goes through DEPOSIT + SUBMIT HASH + APPROVAL clean, then on COMPLETE step shows 'EXECUTION BLOCKED — The transfer amount exceeds the maximum daily rate limit. It cannot be executed even after the rate window resets. Contact support for assistance.' xchain hash: 0xea7479193a3391ec98a61389576f9d465b40e8703816842bd7341960dd16535c source tx: 0xcb13430aa30a65972960aa0b4df65da71137555c1abc0aa492ac98241e7a57bb destination tx: 44C57ACE9139CEED38C2D6D257560A17022A98B600C0D4FEDF52873ACE49943A Behavior is wrong because: - 0.001 tdec is far below any sensible per-tx cap (~$0 worth) - Min for this route shows 0.000001005025125628 tokenc-cb so 0.001 is 1000x above min - 'cannot be executed even after rate window resets' wording suggests the cap accounting is treating this row as outsized, which it isn't Likely a daily-cumulative cap collision: prior MegaETH outbound rows in this session (testa, testb, tdec to BSC/opBNB/Terra/Solana) may have filled the per-token-per-destination daily window and frontend is reporting 'exceeds maximum' on subsequent. Worth checking the daily cap accounting on tdec → Terra path specifically. Same row family as #127 in the sense that the user-facing copy doesn't match what's actually wrong. P2 medium. cc @PlasticDigits
Brouie commented 2026-05-01 07:57:47 +00:00 (Migrated from gitlab.com)

image.png{width=640 height=600}

![image.png](/uploads/e378f10f490fa399825422325b037f6b/image.png){width=640 height=600}
Brouie commented 2026-05-01 07:58:33 +00:00 (Migrated from gitlab.com)

mentioned in issue #123

mentioned in issue #123
Brouie commented 2026-05-01 16:03:18 +00:00 (Migrated from gitlab.com)

Source-level analysis surfaces the root cause — decimal-scale comparison bug in the Terra rate-limit classifier.

The bug

src/services/terraBridgeQueries.ts:344 has this comparison:

const permanentlyBlocked =
  payoutAmount > maxPerPeriod || amount > maxPerPeriod;

The || amount > maxPerPeriod clause compares raw source-decimal amount to destination-decimal maxPerPeriod. When srcDecimals > destDecimals (which is the case for every MegaETH → Terra outbound: 18 → 6), this falsely fires permanently-blocked even on tiny payouts.

Repro mapping (my #130 filing)

  • MegaETH src (18 decimals) → Terra dest (6 decimals)
  • 0.001 tdec input in 18-decimal raw = 1_000_000_000_000_000n (1e15)
  • maxPerPeriod stored in Terra contract in 6 decimals; even generously 1000 tokens cap = 1_000_000_000n (1e9)
  • amount > maxPerPeriod → 1e15 > 1e9 → permanently-blocked falsely fires
  • payoutAmount > maxPerPeriod would correctly evaluate 1e3 > 1e9 → false (the small-amount intent)

The || short-circuits to permanently-blocked before the correct payoutAmount comparison can pass.

Why the new EVM classifier (#127, eaa3d0a) does NOT have this bug

src/services/evmExecutionRateLimit.ts (new code from your #127 fix):

if (payoutAmount > maxPerPeriod) {
  return { kind: 'permanently-blocked', ... }
}

Single comparison on normalized payoutAmount only. Architecturally correct.

The Terra classifier predates this work and never got the same fix.

Suggested fix

Drop the || amount > maxPerPeriod clause in terraBridgeQueries.ts:344-345:

// before
const permanentlyBlocked =
  payoutAmount > maxPerPeriod || amount > maxPerPeriod;

// after (match EVM classifier shape)
const permanentlyBlocked = payoutAmount > maxPerPeriod;

The payoutAmount value is already normalized to dest decimals via normalizeBridgeAmountToDestDecimals(amount, srcDecimals, destDecimals) at line 329 — same helper that mirrors Bridge._normalizeDecimals 1:1.

Severity

P2-medium (unchanged from original filing). Affects every MegaETH/EVM → Terra outbound where srcDecimals > destDecimals — i.e., essentially all of them, since MegaETH is 18-decimal and Terra native tokens are typically 6 decimals.

The on-chain transfer itself is fine (deposit + submit hash + approval all clean per my repro hashes); only the frontend's pre-execution gate is falsely blocking the COMPLETE step.

Acceptance

  • terraBridgeQueries.ts:344 permanently-blocked check uses only payoutAmount > maxPerPeriod (parity with EVM classifier).
  • Add a regression test mirroring evmExecutionRateLimit.test.ts:77-82 ("normalizes decimals like the bridge contract") for the Terra path: 18-decimal source 0.001, 6-decimal dest, sensible maxPerPeriod → expect kind === 'ok', not 'permanently-blocked'.
  • My #130 repro (MegaETH → Terra tdec 0.001) completes COMPLETE step.

cc @PlasticDigits

Source-level analysis surfaces the root cause — decimal-scale comparison bug in the Terra rate-limit classifier. ### The bug `src/services/terraBridgeQueries.ts:344` has this comparison: ```ts const permanentlyBlocked = payoutAmount > maxPerPeriod || amount > maxPerPeriod; ``` The **`|| amount > maxPerPeriod`** clause compares **raw source-decimal `amount`** to **destination-decimal `maxPerPeriod`**. When `srcDecimals > destDecimals` (which is the case for every MegaETH → Terra outbound: 18 → 6), this falsely fires `permanently-blocked` even on tiny payouts. ### Repro mapping (my #130 filing) - MegaETH src (18 decimals) → Terra dest (6 decimals) - 0.001 tdec input in 18-decimal raw = `1_000_000_000_000_000n` (`1e15`) - `maxPerPeriod` stored in Terra contract in 6 decimals; even generously 1000 tokens cap = `1_000_000_000n` (`1e9`) - `amount > maxPerPeriod` → `1e15 > 1e9` → **`permanently-blocked` falsely fires** - `payoutAmount > maxPerPeriod` would correctly evaluate `1e3 > 1e9` → false (the small-amount intent) The `||` short-circuits to permanently-blocked before the correct `payoutAmount` comparison can pass. ### Why the new EVM classifier (#127, `eaa3d0a`) does NOT have this bug `src/services/evmExecutionRateLimit.ts` (new code from your #127 fix): ```ts if (payoutAmount > maxPerPeriod) { return { kind: 'permanently-blocked', ... } } ``` Single comparison on **normalized** `payoutAmount` only. Architecturally correct. The Terra classifier predates this work and never got the same fix. ### Suggested fix Drop the `|| amount > maxPerPeriod` clause in `terraBridgeQueries.ts:344-345`: ```ts // before const permanentlyBlocked = payoutAmount > maxPerPeriod || amount > maxPerPeriod; // after (match EVM classifier shape) const permanentlyBlocked = payoutAmount > maxPerPeriod; ``` The `payoutAmount` value is already normalized to dest decimals via `normalizeBridgeAmountToDestDecimals(amount, srcDecimals, destDecimals)` at line 329 — same helper that mirrors `Bridge._normalizeDecimals` 1:1. ### Severity P2-medium (unchanged from original filing). Affects every MegaETH/EVM → Terra outbound where `srcDecimals > destDecimals` — i.e., essentially all of them, since MegaETH is 18-decimal and Terra native tokens are typically 6 decimals. The on-chain transfer itself is fine (deposit + submit hash + approval all clean per my repro hashes); only the frontend's pre-execution gate is falsely blocking the COMPLETE step. ### Acceptance - [ ] `terraBridgeQueries.ts:344` permanently-blocked check uses only `payoutAmount > maxPerPeriod` (parity with EVM classifier). - [ ] Add a regression test mirroring `evmExecutionRateLimit.test.ts:77-82` ("normalizes decimals like the bridge contract") for the Terra path: 18-decimal source 0.001, 6-decimal dest, sensible `maxPerPeriod` → expect `kind === 'ok'`, not `'permanently-blocked'`. - [ ] My #130 repro (MegaETH → Terra tdec 0.001) completes COMPLETE step. cc @PlasticDigits
PlasticDigits commented 2026-05-02 04:44:20 +00:00 (Migrated from gitlab.com)

mentioned in commit 5411c7d1c5

mentioned in commit 5411c7d1c553111f14336473791ac54236e11ba4
PlasticDigits commented 2026-05-02 04:44:20 +00:00 (Migrated from gitlab.com)

mentioned in commit 95f8fd50bb

mentioned in commit 95f8fd50bb98449dc49c6b239e40cd9e06c35361
PlasticDigits commented 2026-05-02 04:45:17 +00:00 (Migrated from gitlab.com)

/cc @brouie

Merged on main (merge 95f8fd5) — Terra rate-limit permanent-block now uses payoutAmount > maxPerPeriod only, same pattern as computeEvmExecutionRateLimitStatus (EVM classifier from GL-127). Your root-cause write-up was validated before implementation: max_per_period is expressed in destination base units, so amount > maxPerPeriod mixing raw source wei-scale amounts (e.g. 18→6 decimals) falsely returned kind: permanently-blocked.

What landed

  • packages/frontend/src/services/terraBridgeQueries.ts — dropped || amount > maxPerPeriod; doc clarified INV-UX2-TERRA1 intent.
  • terraBridgeQueries.test.ts — three Vitest cases: GL-130 regression (18d tiny amount, generous 6d cap ⇒ ok), permanent when normalized payout > cap, temporary when payout > remaining (normalized, aligned with evmExecutionRateLimit.test.ts normalization idea).
  • docs/FRONTEND_BRIDGE_INVARIANTS.md, docs/frontend.md, skills/agent-frontend-bridge-chains.md — INV-UX2-TERRA1, cross-links, GL-131 doc merge reconciled upstream.

Leaving the issue open for your sign-off.

Checklist for you to verify

  • Manual: MegaETH → Terra (tdec-like token), 0.001 source amount repro path — Transfer Status reaches COMPLETE without the red “maximum daily rate limit … cannot be executed even after … resets” when the payout is genuinely under the Terra period cap.
  • Edge: Destination temporary rate-limit still surfaces (amber) when normalized payout > remaining_amount but <= max_per_period.
  • Edge: Permanent banner still appears when normalized payout genuinely exceeds max_per_period.
  • CI / local: cd packages/frontend && npm run test:run -- src/services/terraBridgeQueries.test.ts passes (plus full npm run test:unit if you want).

Thanks for the pinpoint note on line 344 — that made this a surgical fix.

/cc @brouie Merged on **main** (merge **`95f8fd5`**) — Terra rate-limit **permanent-block** now uses **`payoutAmount > maxPerPeriod` only**, same pattern as **`computeEvmExecutionRateLimitStatus`** (EVM classifier from GL-127). Your root-cause write-up was validated before implementation: `max_per_period` is expressed in **destination** base units, so **`amount > maxPerPeriod`** mixing **raw source** wei-scale amounts (e.g. 18→6 decimals) falsely returned **`kind: permanently-blocked`**. ### What landed - `packages/frontend/src/services/terraBridgeQueries.ts` — dropped `|| amount > maxPerPeriod`; doc clarified **INV-UX2-TERRA1** intent. - `terraBridgeQueries.test.ts` — three Vitest cases: GL-130 regression (**18d tiny amount**, generous **6d** cap ⇒ **ok**), **permanent** when normalized payout &gt; cap, **temporary** when payout &gt; remaining (normalized, aligned with **`evmExecutionRateLimit.test.ts`** normalization idea). - `docs/FRONTEND_BRIDGE_INVARIANTS.md`, `docs/frontend.md`, **`skills/agent-frontend-bridge-chains.md`** — **INV-UX2-TERRA1**, cross-links, GL-131 doc merge reconciled upstream. Leaving the issue **open** for your sign-off. ### Checklist for you to verify - [ ] **Manual:** MegaETH → Terra (tdec-like token), **`0.001`** source amount repro path — Transfer Status reaches **COMPLETE** without the red **“maximum daily rate limit … cannot be executed even after … resets”** when the payout is genuinely under the Terra period cap. - [ ] **Edge:** Destination **temporary** rate-limit still surfaces (**amber**) when normalized payout &gt; **`remaining_amount`** but &lt;= **`max_per_period`**. - [ ] **Edge:** **Permanent** banner still appears when normalized payout genuinely exceeds **`max_per_period`**. - [ ] **CI / local:** `cd packages/frontend && npm run test:run -- src/services/terraBridgeQueries.test.ts` passes (plus full **`npm run test:unit`** if you want). Thanks for the pinpoint note on line 344 — that made this a surgical fix.
Brouie commented 2026-05-03 22:10:02 +00:00 (Migrated from gitlab.com)

mentioned in issue #127

mentioned in issue #127
Brouie commented 2026-05-03 22:10:04 +00:00 (Migrated from gitlab.com)

Production deploy gap — sign-off blocked

Verified prod bundle on bridge.cl8y.com today: footer reads v0.1.345 · 27a5e42. Repo main HEAD is currently at 95f8fd5, which is the merge commit containing all four fixes (#127 eaa3d0a, #128 b6c5b5a, #130 Terra || clause drop, #131 67a48e4). Prod is therefore still on the pre-fix bundle and none of the four fixes are live yet.

Confirming repro on prod (proves bundle is pre-fix):

  • EVM wallet chip in bridge header shows ETH text fallback while MetaMask is on MegaETH (chain 4326). This is the clean #131-D pre-fix behavior — getChainLogoPath has no branch for 4326, falls through to getGasSymbol default ETH.

Cannot run mainnet sign-off on the verification checklist for #127 / #128 / #130 / #131 until prod cuts to current HEAD (or whichever release commit contains the four fixes).

Will re-run the combined live walk on bridge.cl8y.com once deploy is confirmed:

  • #128 — Solana → MegaETH × 3 (testa / testb / tdec) for fresh-blockhash retry path
  • #127 — deliberate rate-limit row, observe amber "Resets in" countdown banner
  • #130 — MegaETH → Terra tdec 0.001, expect COMPLETE clean
  • #131 — A (stepper stable across poll), B (Switch button visible on wrong chain), C (no refresh stall after switch), D (mega.png header chip)

Plus the 4 #123 blocked rows pick-up that rides on #128 / #130 fixes.

cc @PlasticDigits

## Production deploy gap — sign-off blocked Verified prod bundle on bridge.cl8y.com today: footer reads `v0.1.345 · 27a5e42`. Repo `main` HEAD is currently at `95f8fd5`, which is the merge commit containing all four fixes (#127 `eaa3d0a`, #128 `b6c5b5a`, #130 Terra `||` clause drop, #131 `67a48e4`). Prod is therefore still on the pre-fix bundle and none of the four fixes are live yet. Confirming repro on prod (proves bundle is pre-fix): - EVM wallet chip in bridge header shows `ETH` text fallback while MetaMask is on MegaETH (chain 4326). This is the clean #131-D pre-fix behavior — `getChainLogoPath` has no branch for 4326, falls through to `getGasSymbol` default `ETH`. Cannot run mainnet sign-off on the verification checklist for #127 / #128 / #130 / #131 until prod cuts to current HEAD (or whichever release commit contains the four fixes). Will re-run the combined live walk on bridge.cl8y.com once deploy is confirmed: - #128 — Solana → MegaETH × 3 (testa / testb / tdec) for fresh-blockhash retry path - #127 — deliberate rate-limit row, observe amber "Resets in" countdown banner - #130 — MegaETH → Terra tdec 0.001, expect COMPLETE clean - #131 — A (stepper stable across poll), B (Switch button visible on wrong chain), C (no refresh stall after switch), D (mega.png header chip) Plus the 4 #123 blocked rows pick-up that rides on #128 / #130 fixes. cc @PlasticDigits
Brouie commented 2026-05-03 22:10:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #128

mentioned in issue #128
Brouie commented 2026-05-03 22:10:06 +00:00 (Migrated from gitlab.com)

mentioned in issue #131

mentioned in issue #131
PlasticDigits commented 2026-05-04 03:39:29 +00:00 (Migrated from gitlab.com)

@Brouie bridge.cl8y.com frontend is now deployed at latest commit.

@Brouie bridge.cl8y.com frontend is now deployed at latest commit.
Brouie commented 2026-05-07 05:43:37 +00:00 (Migrated from gitlab.com)

queued in the bundled bridge live-walk along with #128 — see https://gitlab.com/PlasticDigits/cl8y-bridge-monorepo/-/work_items/128#note_3322750554 for timing.

queued in the bundled bridge live-walk along with #128 — see https://gitlab.com/PlasticDigits/cl8y-bridge-monorepo/-/work_items/128#note_3322750554 for timing.
PlasticDigits commented 2026-05-19 08:10:26 +00:00 (Migrated from gitlab.com)

mentioned in issue #132

mentioned in issue #132
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#130
No description provided.