parseZecAmount function
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);
}