Skip to content

fix(display): negative token amounts render as "-1.-5 ETH" due to BigInt modulo sign propagation #66

Description

@Sertug17

Bug Report

While testing the vibenet explorer and TIPS transaction list with edge-case token amounts, we noticed that negative values display incorrectly in the UI.

Affected files

  • app/vibenet/library/explorer.tsfmtTokenAmount
  • app/tips/library/explorer-format.tsformatUnits

Reproduction

fmtTokenAmount(-1_500_000_000_000_000_000n, 18)
// Expected: "-1.5"
// Actual:   "-1.-5"

fmtTokenAmount(-500_000_000_000_000_000n, 18)
// Expected: "-0.5"
// Actual:   "0.-5"

Root cause

JavaScript's BigInt % operator preserves the sign of the dividend, not the divisor. So when raw is negative, frac = raw % divisor is also negative and frac.toString() produces a string starting with "-", which gets concatenated directly into the fractional part:

const frac = raw % divisor;          // -500_000_000_000_000_000n (negative!)
const fracStr = frac.toString()       // "-5"
  .padStart(decimals, '0')
  .replace(/0+$/, '');
return `${whole.toLocaleString()}.${fracStr}`; // "0.-5"

Fix

Take the absolute value of frac before formatting:

const whole = raw / divisor;
const frac = (raw % divisor + divisor) % divisor; // always positive

Or more explicitly:

const frac = raw < 0n ? -(raw % divisor) : raw % divisor;

Same fix applies to formatUnits in explorer-format.ts.

Notes

  • All 131 existing vitest tests pass negative BigInt inputs are not currently covered by any test
  • Positive amounts and zero are unaffected
  • fmtHexInt has a separate but related issue: it uses Number.parseInt(hex, 16) which silently loses precision for values above 2^53. Should use BigInt(hex) instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions