Bug: BSC deposit button blocked at exact minimum amount (off-by-one) #101

Closed
opened 2026-04-09 03:20:44 +00:00 by Brouie · 10 comments
Brouie commented 2026-04-09 03:20:44 +00:00 (Migrated from gitlab.com)

Mainnet v0.1.82 (b9c6d54)

Token: testa (tokena-cb) on BSC
MIN displayed: 1.005
Amount entered: 1.005025
Bridge fee: 0.5%
YOU WILL RECEIVE: 1.00 TESTA

Current behavior: Bridge button is blocked/disabled when entering the minimum amount (1.005025). Cannot submit deposit.

Works when: Amount is increased above the minimum. Transfers complete normally at higher amounts.

Expected behavior: Bridge button should be enabled at the displayed minimum amount. If 1.005 is the minimum, entering 1.005 or 1.005025 should be accepted.

Likely cause: Division or off-by-one bug in the minimum amount calculation. The post-fee amount may be compared against the minimum instead of the pre-fee amount, or rounding causes the check to fail at the boundary.

Acceptance criteria:

  • Bridge button enabled at exact MIN amount
  • MIN display matches actual enforceable minimum
  • No off-by-one at boundary
**Mainnet v0.1.82 (b9c6d54)** **Token:** testa (tokena-cb) on BSC **MIN displayed:** 1.005 **Amount entered:** 1.005025 **Bridge fee:** 0.5% **YOU WILL RECEIVE:** 1.00 TESTA **Current behavior:** Bridge button is blocked/disabled when entering the minimum amount (1.005025). Cannot submit deposit. **Works when:** Amount is increased above the minimum. Transfers complete normally at higher amounts. **Expected behavior:** Bridge button should be enabled at the displayed minimum amount. If 1.005 is the minimum, entering 1.005 or 1.005025 should be accepted. **Likely cause:** Division or off-by-one bug in the minimum amount calculation. The post-fee amount may be compared against the minimum instead of the pre-fee amount, or rounding causes the check to fail at the boundary. **Acceptance criteria:** - [ ] Bridge button enabled at exact MIN amount - [ ] MIN display matches actual enforceable minimum - [ ] No off-by-one at boundary
Brouie commented 2026-04-20 04:21:49 +00:00 (Migrated from gitlab.com)

@PlasticDigits Still reproducing on build v0.1.82 - f227b6f (frontend bundle main-CvcBOh67), 4/20 mainnet.

Reproducing scenario (MIN off-by-one on BSC -> Terra):

  • From: BNB Chain
  • To: Terra Classic
  • Token: testa (tokena-cb)
  • MIN displayed: 1.005
  • Clicked MIN preset -> fills 1.005025
  • YOU WILL RECEIVE: 1.00 TESTA
  • BRIDGE FROM EVM button: BLOCKED with 🚫 icon, not clickable

Comparison (BSC -> opBNB works):

  • Same BSC source, same testa, MIN 0.1005
  • Clicked MIN preset -> fills 0.100503
  • YOU WILL RECEIVE: 0.10 TOKENA-CB
  • BRIDGE EVM TO EVM button: ACTIVE, clickable

The MIN preset math is the same pattern (gross = min + fee buffer). BSC -> opBNB passes, BSC -> Terra blocks. Looks like the off-by-one is Terra-destination specific, possibly related to Terra's 6-decimal rounding vs 18-decimal source. The 1.00 net might be evaluated as exactly equal to min rather than >= min after decimal normalization.

Recent transfers today show the BSC -> Terra path itself works fine at amounts above MIN (see nonce 48: 1.99 testa BSC -> Terra, just completed). Issue is strictly at the MIN boundary on Terra destinations.

Leaving open.

@PlasticDigits Still reproducing on build v0.1.82 - f227b6f (frontend bundle main-CvcBOh67), 4/20 mainnet. **Reproducing scenario (MIN off-by-one on BSC -> Terra):** - From: BNB Chain - To: Terra Classic - Token: testa (tokena-cb) - MIN displayed: 1.005 - Clicked MIN preset -> fills 1.005025 - YOU WILL RECEIVE: 1.00 TESTA - BRIDGE FROM EVM button: **BLOCKED with 🚫 icon, not clickable** **Comparison (BSC -> opBNB works):** - Same BSC source, same testa, MIN 0.1005 - Clicked MIN preset -> fills 0.100503 - YOU WILL RECEIVE: 0.10 TOKENA-CB - BRIDGE EVM TO EVM button: ACTIVE, clickable The MIN preset math is the same pattern (gross = min + fee buffer). BSC -> opBNB passes, BSC -> Terra blocks. Looks like the off-by-one is Terra-destination specific, possibly related to Terra's 6-decimal rounding vs 18-decimal source. The 1.00 net might be evaluated as exactly equal to min rather than >= min after decimal normalization. Recent transfers today show the BSC -> Terra path itself works fine at amounts above MIN (see nonce 48: 1.99 testa BSC -> Terra, just completed). Issue is strictly at the MIN boundary on Terra destinations. Leaving open.
Brouie commented 2026-04-20 05:35:47 +00:00 (Migrated from gitlab.com)

mentioned in issue #111

mentioned in issue #111
PlasticDigits commented 2026-04-20 05:59:32 +00:00 (Migrated from gitlab.com)

mentioned in commit dae31d3fc3

mentioned in commit dae31d3fc309d05450642672edd4500ec1293314
PlasticDigits commented 2026-04-20 05:59:52 +00:00 (Migrated from gitlab.com)

Fixed on main in dae31d3: MIN gross now targets the same destination minimum as validation (removed +1 base-unit skew). MIN label uses full token precision (formatAmountForNumberInput) so compact rounding cannot suggest an amount that still fails isBelowMin. Regression test added for 50 bps / 1e6 min net (1.005025 gross).

Fixed on main in dae31d3: MIN gross now targets the same destination minimum as validation (removed +1 base-unit skew). MIN label uses full token precision (formatAmountForNumberInput) so compact rounding cannot suggest an amount that still fails isBelowMin. Regression test added for 50 bps / 1e6 min net (1.005025 gross).
PlasticDigits commented 2026-04-20 06:00:14 +00:00 (Migrated from gitlab.com)

What we changed

The bridge form had two boundary bugs:

  1. Skewed minimum gross — MIN was computed with effectiveMinInSrc + 1n, so the UI required slightly more net than isBelowMin (which only checks net >= effectiveMinInSrc). That disagreed with the on-chain minimum and the “exact MIN” expectation.

  2. Misleading MIN label — The label used formatCompact (4 significant figures), so e.g. 1.005025 appeared as 1.005. Typing what the label showed could yield net below the real minimum, so the deposit button stayed disabled.

Fix (merged to main, commit dae31d3):

  • MIN gross = minGrossForMinNet(effectiveMinInSrc, bridgeFeeBps) — aligned with validation.
  • MIN label = formatAmountForNumberInput(..., amountDecimals, amountDecimals) — full token precision so the shown MIN matches what you can enter.
  • Regression test for the 50 bps / 1e6 min-net case (gross 1_005_025).

This applies to all routes that use TransferForm (EVM / Terra / Solana as source), not only BSC → Terra Classic.


@brouie Could you verify on the next deploy (or locally against main) that the bridge button enables at the displayed MIN and that entering that amount still shows the expected “you will receive” at the destination minimum? We are leaving this issue open until you confirm. Thanks!

## What we changed The bridge form had two boundary bugs: 1. **Skewed minimum gross** — MIN was computed with `effectiveMinInSrc + 1n`, so the UI required slightly more net than `isBelowMin` (which only checks `net >= effectiveMinInSrc`). That disagreed with the on-chain minimum and the “exact MIN” expectation. 2. **Misleading MIN label** — The label used `formatCompact` (4 significant figures), so e.g. **1.005025** appeared as **1.005**. Typing what the label showed could yield net **below** the real minimum, so the deposit button stayed disabled. **Fix (merged to `main`, commit `dae31d3`):** - MIN gross = `minGrossForMinNet(effectiveMinInSrc, bridgeFeeBps)` — aligned with validation. - MIN label = `formatAmountForNumberInput(..., amountDecimals, amountDecimals)` — full token precision so the shown MIN matches what you can enter. - Regression test for the 50 bps / 1e6 min-net case (gross `1_005_025`). This applies to all routes that use `TransferForm` (EVM / Terra / Solana as source), not only BSC → Terra Classic. --- @brouie Could you verify on the next deploy (or locally against `main`) that the bridge button enables at the displayed MIN and that entering that amount still shows the expected “you will receive” at the destination minimum? We are leaving this issue **open** until you confirm. Thanks!
Brouie commented 2026-04-20 06:14:51 +00:00 (Migrated from gitlab.com)

@PlasticDigits Partial fix on build v0.1.82 - dae31d3 (frontend bundle main-CSMgjCa4), 4/20 mainnet.

What's fixed:

  • MIN label now shows full precision: 1.005025125628140703 (previously displayed as 1.005)
  • Typing the full-precision value manually into the amount field unblocks the button -- BSC->Terra testa 1.005025125628140703 allows the button to activate

What's still broken:

  • Clicking the MIN preset fills only 1.005025 (6-decimal truncation) which is LESS than the full-precision MIN 1.005025125628140703
  • With 1.005025 in the field (from MIN preset), button is still BLOCKED with 🚫 icon
  • User must manually copy the label value to unblock

Expected:
MIN preset should fill the exact full-precision value from the label so clicking MIN gives an immediately valid amount.

Leaving open. The label fix is good; preset fill just needs to match label precision.

@PlasticDigits Partial fix on build v0.1.82 - dae31d3 (frontend bundle main-CSMgjCa4), 4/20 mainnet. **What's fixed:** - MIN label now shows full precision: 1.005025125628140703 (previously displayed as 1.005) - Typing the full-precision value manually into the amount field unblocks the button -- BSC->Terra testa 1.005025125628140703 allows the button to activate **What's still broken:** - Clicking the MIN preset fills only 1.005025 (6-decimal truncation) which is LESS than the full-precision MIN 1.005025125628140703 - With 1.005025 in the field (from MIN preset), button is still BLOCKED with 🚫 icon - User must manually copy the label value to unblock **Expected:** MIN preset should fill the exact full-precision value from the label so clicking MIN gives an immediately valid amount. Leaving open. The label fix is good; preset fill just needs to match label precision.
PlasticDigits commented 2026-04-20 06:20:16 +00:00 (Migrated from gitlab.com)

mentioned in commit 3cd46b3b71

mentioned in commit 3cd46b3b712720cc3cf92adc7687d38f0f3955e1
PlasticDigits commented 2026-04-20 06:20:21 +00:00 (Migrated from gitlab.com)

Follow-up: MIN preset vs full-precision label (partial fix after dae31d3)

Cause: formatAmountForNumberInput defaults displayDecimals to min(tokenDecimals, 6) when the third argument is omitted. The MIN label already passed (amountDecimals, amountDecimals) for full precision, but the MIN button called formatAmountForNumberInput(minSendGrossInSrc, amountDecimals) with only two arguments—so the field was filled with at most 6 decimal places (e.g. 1.005025) while the enforceable gross requires full source precision (e.g. 1.005025125628140703 on 18 dp).

Fix: handleMin now passes the third argument: formatAmountForNumberInput(minSendGrossInSrc, amountDecimals, amountDecimals), matching displayMinLabel.

Commit: 3cd46b3 on main

Test: Regression in format.test.ts documents default 6-dp cap vs explicit full decimals.

Leaving this issue open for verification on deploy (MIN click should enable the bridge button at the same value as the MIN label).

## Follow-up: MIN preset vs full-precision label (partial fix after dae31d3) **Cause:** `formatAmountForNumberInput` defaults `displayDecimals` to `min(tokenDecimals, 6)` when the third argument is omitted. The MIN **label** already passed `(amountDecimals, amountDecimals)` for full precision, but the MIN **button** called `formatAmountForNumberInput(minSendGrossInSrc, amountDecimals)` with only two arguments—so the field was filled with at most 6 decimal places (e.g. `1.005025`) while the enforceable gross requires full source precision (e.g. `1.005025125628140703` on 18 dp). **Fix:** `handleMin` now passes the third argument: `formatAmountForNumberInput(minSendGrossInSrc, amountDecimals, amountDecimals)`, matching `displayMinLabel`. **Commit:** `3cd46b3` on `main` **Test:** Regression in `format.test.ts` documents default 6-dp cap vs explicit full decimals. Leaving this issue **open** for verification on deploy (MIN click should enable the bridge button at the same value as the MIN label).
PlasticDigits commented 2026-04-20 06:23:10 +00:00 (Migrated from gitlab.com)

Update (follow-up commits on main)

MIN preset vs label (3cd46b3)
The MIN button now calls formatAmountForNumberInput(minSendGrossInSrc, amountDecimals, amountDecimals) so the filled amount matches the full-precision MIN label. Previously the third argument was omitted, so formatAmountForNumberInput capped display at 6 decimals while high-decimal source tokens need the full string to pass validation.

Solana dest limits (2a127e6)
Related frontend hardening: Solana destination routes now resolve the TokenMapping PDA for useTokenDetails (EVM→Solana, Terra→Solana) and surface mapping destTokenDecimals so min/max and unit conversion use the correct SPL decimals when the destination is Solana.

Repo hygiene
Branches were already only main; no extra branches to remove locally or on origin.

Leaving this issue open until you can confirm on a deploy that MIN enables the bridge at the displayed minimum without manual entry.

## Update (follow-up commits on `main`) **MIN preset vs label (`3cd46b3`)** The MIN button now calls `formatAmountForNumberInput(minSendGrossInSrc, amountDecimals, amountDecimals)` so the filled amount matches the full-precision MIN label. Previously the third argument was omitted, so `formatAmountForNumberInput` capped display at 6 decimals while high-decimal source tokens need the full string to pass validation. **Solana dest limits (`2a127e6`)** Related frontend hardening: Solana destination routes now resolve the TokenMapping PDA for `useTokenDetails` (EVM→Solana, Terra→Solana) and surface mapping `destTokenDecimals` so min/max and unit conversion use the correct SPL decimals when the destination is Solana. **Repo hygiene** Branches were already only `main`; no extra branches to remove locally or on `origin`. Leaving this issue **open** until you can confirm on a deploy that **MIN** enables the bridge at the displayed minimum without manual entry.
Brouie commented 2026-04-20 09:14:42 +00:00 (Migrated from gitlab.com)

@PlasticDigits Fix verified on v0.1.82 - 2a127e6 (mainnet).

Route: opBNB -> BSC, token testa (18 decimals)

Behavior confirmed:

  • MIN label displays full precision: 0.10050251256281407
  • MIN preset button fills input with matching full-precision value: 0.10050251256281407 (third formatAmountForNumberInput argument now passes amountDecimals)
  • 'BRIDGE EVM TO EVM' primary CTA is enabled immediately at MIN click (no manual entry required)
  • No 🚫 block icon, no 'Below minimum' state

formatAmountForNumberInput fix in 3cd46b3 resolves the truncation that was capping display at 6 decimals. Closing.

@PlasticDigits Fix verified on v0.1.82 - 2a127e6 (mainnet). **Route:** opBNB -> BSC, token testa (18 decimals) **Behavior confirmed:** - MIN label displays full precision: 0.10050251256281407 - MIN preset button fills input with matching full-precision value: 0.10050251256281407 (third formatAmountForNumberInput argument now passes amountDecimals) - 'BRIDGE EVM TO EVM' primary CTA is **enabled** immediately at MIN click (no manual entry required) - No 🚫 block icon, no 'Below minimum' state formatAmountForNumberInput fix in 3cd46b3 resolves the truncation that was capping display at 6 decimals. Closing.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-20 09:14:48 +00:00
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#101
No description provided.