parseZecAmount function

ZecAmountResult parseZecAmount(
  1. String input
)

Parse input (a user-typed ZEC amount) into zatoshis with integer math only.

Accepts a plain non-negative decimal: optional whole part, an optional single ., an optional fractional part of at most 8 digits (e.g. 1, 0.0001, .5, 21000000). Rejects signs, thousands separators, exponents, a second dot, and anything non-digit. Surrounding whitespace is trimmed.

Round-trips with formatZec: parseZecAmount(formatZec(x)) is ZecAmountValid(x) for every 1 <= x <= maxMoneyZat.

Implementation

ZecAmountResult parseZecAmount(String input) {
  final s = input.trim();
  if (s.isEmpty) return const ZecAmountInvalid(ZecAmountFault.empty);

  // Digits and at most one dot, and at least one digit somewhere (a bare "."
  // matches the shape but is not a number). No sign, no separators, no exponent.
  if (!RegExp(r'^[0-9]*\.?[0-9]*$').hasMatch(s) ||
      !RegExp(r'[0-9]').hasMatch(s)) {
    return const ZecAmountInvalid(ZecAmountFault.notANumber);
  }

  final dot = s.indexOf('.');
  final wholePart = dot < 0 ? s : s.substring(0, dot);
  final fracPart = dot < 0 ? '' : s.substring(dot + 1);

  if (fracPart.length > 8) {
    return const ZecAmountInvalid(ZecAmountFault.tooManyDecimals);
  }
  // Length guard BEFORE int.parse so an arbitrarily long paste can't overflow the
  // parse itself (≤18 digits ⇒ ≤~1e18, inside int64).
  if (wholePart.length > _maxWholeDigits) {
    return const ZecAmountInvalid(ZecAmountFault.outOfRange);
  }

  // wholePart may be empty (".5"); fracPart is right-padded to 8 digits so its
  // place value is fixed (".5" ⇒ 50000000 zat, not 5).
  final whole = wholePart.isEmpty ? 0 : int.parse(wholePart);
  // The SUPPLY ceiling on the whole part, applied BEFORE the multiply — so
  // `whole * zatPerZec` can NEVER overflow int64 (max supply is 21_000_000 ZEC,
  // so any larger whole part is already out of range). This is the real guard a
  // digit count can't be: it also tolerates leading zeros ("00021000000" ⇒ 21e6).
  if (whole > maxMoneyZat ~/ zatPerZec) {
    return const ZecAmountInvalid(ZecAmountFault.outOfRange);
  }
  final frac = fracPart.isEmpty ? 0 : int.parse(fracPart.padRight(8, '0'));
  final zat = whole * zatPerZec + frac;

  if (zat <= 0) return const ZecAmountInvalid(ZecAmountFault.notPositive);
  if (zat > maxMoneyZat) {
    return const ZecAmountInvalid(ZecAmountFault.outOfRange);
  }
  return ZecAmountValid(zat);
}