auradefi 0.1.1
PyPI GitHub

PyBook 02: Money & Quantity

Two non-negotiable rules, each with a named casualty:

Rule #1, "Money is a tagged decimal string." Plaid's quantity/amount/price are format: double, ~15–17 significant digits against 18-decimal tokens. "Silently corrupts exactly the largest balances."

Rule #2, "Never emit a JSON integer for a raw amount." Allium ships raw_balance (integer): "Any wei-scale value is past Number.MAX_SAFE_INTEGER before it reaches a client."

auradefi.money makes both rules structural: Quantity is an arbitrary-precision int count of base units plus its scale, Money is an exact Decimal plus a currency tag. Nothing here may round.

from decimal import Decimal

from auradefi.money.quantity import Quantity

one_eth_ish = Quantity(raw=1234567890123456789, decimals=18)
assert str(one_eth_ish) == "1.234567890123456789"
assert one_eth_ish.as_decimal() == Decimal("1.234567890123456789")

# Equality is strict on (raw, decimals): equal numeric value at a
# different scale is a DIFFERENT quantity. Scale is part of identity.
assert Quantity(10, 1) != Quantity(100, 2)
assert str(Quantity(10, 1)) == str(Quantity(100, 2)) == "1"
print(one_eth_ish)
1.234567890123456789

Arbitrary precision: the 10^77 demo

raw is a Python int: "arbitrary precision, no ceiling". A 78-digit balance, near the uint256 maximum a malicious or broken token contract can actually mint, survives construction, arithmetic, and rendering exactly.

whale = Quantity(raw=10**77 + 1, decimals=18)

rendered = str(whale)
assert "E" not in rendered and "e" not in rendered   # never scientific
assert rendered == "1" + "0" * 59 + "." + "0" * 17 + "1"
# Exact Decimal at 78 digits. String construction is context-free, so
# this equality is exact, not rounded to the default 28-digit context.
assert whale.as_decimal() == Decimal(f"{10**77 + 1}E-18")

# Exact arithmetic at that magnitude: add one base unit (1e-18).
bumped = whale + Quantity(1, 18)
assert bumped.raw == 10**77 + 2
print(f"78-digit raw renders exactly: {rendered[:12]}{rendered[-12:]} ({len(rendered)} chars)")
78-digit raw renders exactly: 100000000000…000000000001 (79 chars)

Exact string rendering: never scientific notation

__str__ is a wire-format contract: exact at any magnitude, trailing fractional zeros trimmed, and 'E' never appears, not for huge values, not for dust.

dust = Quantity(raw=1, decimals=77)
assert str(dust) == "0." + "0" * 76 + "1"
assert "E" not in str(dust)
# For contrast: naive Decimal printing goes scientific on exactly this value.
assert str(dust.as_decimal()) == "1E-77"

assert str(Quantity(15 * 10**17, 18)) == "1.5"        # trailing zeros trimmed
assert str(Quantity(-15 * 10**17, 18)) == "-1.5"      # sign preserved
assert str(Quantity(0, 18)) == "0"
print("dust:", str(dust)[:8] + "…" + str(dust)[-3:], "| naive Decimal:", dust.as_decimal())
dust: 0.000000…001 | naive Decimal: 1E-77

The four-field wire form

The pinned shape: "raw for arithmetic, float for display, exact string for correctness, decimals inline so no second lookup":

"quantity": { "raw": "1234567890123456789", "decimals": 18,
              "numeric": "1.234567890123456789", "float": 1.2345678901234568 }

"float is display-only and documented as lossy. raw is a string in JSON, an int in Python."

import json

from auradefi.money.decimal_json import quantity_from_wire, quantity_to_wire

wire = quantity_to_wire(one_eth_ish)
print(json.dumps(wire, indent=2))

# The exact wire dict, asserted field by field:
assert wire == {
    "raw": "1234567890123456789",       # a STRING: rule #2
    "decimals": 18,
    "numeric": "1.234567890123456789",  # exact, never scientific
    "float": 1.2345678901234567,        # lossy, display-only
}
assert isinstance(wire["raw"], str) and isinstance(wire["decimals"], int)
{
  "raw": "1234567890123456789",
  "decimals": 18,
  "numeric": "1.234567890123456789",
  "float": 1.2345678901234567
}
# float is LOSSY: the double dropped real precision...
assert Decimal(wire["float"]) != one_eth_ish.as_decimal()

# ...which is why reads reconstruct from raw + decimals ONLY. Corrupt
# display fields cannot corrupt the value:
tampered = {**wire, "numeric": "999", "float": 999.0}
assert quantity_from_wire(tampered) == one_eth_ish

# And the 78-digit whale round-trips exactly through the wire.
assert quantity_from_wire(quantity_to_wire(whale)) == whale
print("reads use raw + decimals only; float is never trusted")
reads use raw + decimals only; float is never trusted

Strict wire grammar

Python's int() and Decimal() accept far more than a wire amount should: underscores, whitespace, '+', scientific notation, even non-ASCII digits. The wire grammar is pinned to -?[0-9]+ (and -?[0-9]+(\.[0-9]+)? for money). Everything else is a ValidationError, including a JSON integer in raw (rule #2, rejected on read).

from auradefi.errors import ValidationError

# Python's parsers are lax: exactly the laxness the wire must not inherit:
assert int("1_000") == 1000        # underscores
assert int(" +5\n") == 5           # whitespace and '+'
assert int("٥٥") == 55             # Arabic-Indic digits!
assert Decimal("1e5") == 100000    # scientific notation

# None of that is a wire amount. Every one is rejected on read:
for bad_raw in ["1_000", "1e5", " 5", "+5", "5\n", "٥٥"]:
    try:
        quantity_from_wire({"raw": bad_raw, "decimals": 18})
    except ValidationError as exc:
        print("rejected:", repr(bad_raw), "→", type(exc).__name__)
    else:
        raise AssertionError(f"{bad_raw!r} must not parse")

# A JSON integer raw is a rule #2 violation: rejected outright.
try:
    quantity_from_wire({"raw": 1234567890123456789, "decimals": 18})
except ValidationError as exc:
    print("rejected: JSON-integer raw →", exc)
else:
    raise AssertionError("integer raw must not parse")
rejected: '1_000' → ValidationError
rejected: '1e5' → ValidationError
rejected: ' 5' → ValidationError
rejected: '+5' → ValidationError
rejected: '5\n' → ValidationError
rejected: '٥٥' → ValidationError
rejected: JSON-integer raw → wire 'raw' must be a string, got int (rule #2)

Money: a tagged decimal string

Rule #1's shape, verbatim: {"amount": "-741.027368947745798389", "currency": "USD"}. amount is a Decimal in Python and an exact string on the wire; a float amount is rejected on read, as are the non-finite specials ("NaN", "Infinity") and scientific notation.

from auradefi.money.decimal_json import money_from_wire, money_to_wire
from auradefi.money.fiat import Money

# The exact value from rule #1: 21 significant digits, no double.
spec_example = Money(Decimal("-741.027368947745798389"), "USD")
wire_money = money_to_wire(spec_example)
assert wire_money == {"amount": "-741.027368947745798389", "currency": "USD"}
assert money_from_wire(wire_money) == spec_example
print(json.dumps(wire_money))

for bad in [
    {"amount": 4321.55, "currency": "USD"},        # a float: rule #1
    {"amount": "NaN", "currency": "USD"},          # not an exact amount
    {"amount": "1e5", "currency": "USD"},          # no scientific notation
    {"amount": "1_000", "currency": "USD"},        # no int()-isms
]:
    try:
        money_from_wire(bad)
    except ValidationError as exc:
        print("rejected:", repr(bad["amount"]), "→", type(exc).__name__)
    else:
        raise AssertionError(f"{bad!r} must not parse")
{"amount": "-741.027368947745798389", "currency": "USD"}
rejected: 4321.55 → ValidationError
rejected: 'NaN' → ValidationError
rejected: '1e5' → ValidationError
rejected: '1_000' → ValidationError

Mismatches raise: never coerce

Arithmetic is defined only between quantities of equal decimals and money of equal currency. Mixing scales or currencies is always a caller bug, so it raises from the one taxonomy (DecimalsMismatchError, CurrencyMismatchError) instead of silently rescaling or converting.

from auradefi.errors import CurrencyMismatchError, DecimalsMismatchError

try:
    Quantity(1, 18) + Quantity(1, 6)   # ETH-scale + USDC-scale
except DecimalsMismatchError as exc:
    print("quantity:", exc)
else:
    raise AssertionError("expected DecimalsMismatchError")

try:
    Money(Decimal("1"), "USD") + Money(Decimal("1"), "EUR")
except CurrencyMismatchError as exc:
    print("money:   ", exc)
else:
    raise AssertionError("expected CurrencyMismatchError")

# Same scale / same currency: exact, context-free arithmetic.
assert Quantity(1, 6) + Quantity(2, 6) == Quantity(3, 6)
assert (Money(Decimal("0.1"), "USD") + Money(Decimal("0.2"), "USD")).amount == Decimal("0.3")
print("\nmoney walkthrough complete: every assertion held")
quantity: decimals mismatch: 18 vs 6
money:    currency mismatch: 'USD' vs 'EUR'

money walkthrough complete: every assertion held