Monetra is a TypeScript money framework built around a few non-negotiables:
- Store values in integer minor units (no floating point drift)
- Make currency explicit
- Require an explicit policy for any ambiguous math (especially rounding)
- Provide audit primitives that make “what happened?” defensible later
Repository: https://github.com/zugobite/monetra
Architecture
Monetra is intentionally split into layers so you can adopt it incrementally:
- Core money layer:
Money, currency metadata, arithmetic, rounding, formatting/parsing. - Financial layer: higher-level financial math helpers.
- Audit layer: an append-only ledger with a hash chain for tamper-evident verification.
The package is published with multiple entry points (for example, ./ledger and ./financial) so apps can import only what they need.
Core mental model
Minor units + BigInt
Every Money instance stores its amount in the currency’s minor unit (e.g. cents) as a bigint.
That means:
$1.23becomes123n(minor units) for a 2-decimal currency.- Math is exact because it is integer arithmetic.
- Precision is a property of the currency definition, not the number type.
Immutability
Money is immutable: operations return a new value instead of mutating the original.
This matters for correctness (no “who changed this?” surprises) and for auditability (values used to compute an entry can be kept as-is).
Creating money
Monetra provides a small convenience helper plus explicit constructors.
money(amount, currency)
The money() helper intentionally distinguishes between major and minor units:
- Passing a string is treated as major units (e.g.
'12.34'). - Passing a number or bigint is treated as minor units (e.g.
1234n).
import { money } from 'monetra'
const a = money('12.34', 'USD') // major units
const b = money(1234n, 'USD') // minor units
This design avoids the most common production bug: accidentally treating “cents” as “dollars” (or vice versa).
Explicit constructors
Use explicit constructors when you want zero ambiguity:
Money.fromMinor(minor, currency)Money.fromMajor(majorString, currency)
There are also helpers for decimal-style inputs. Money.fromFloat() exists for convenience, but should be treated as a last resort because floats are not exact.
Arithmetic and rounding
Safe arithmetic (no silent rounding)
Addition/subtraction are straightforward as long as currencies match.
Multiplication/division are where financial bugs usually begin: if an operation produces a fractional minor-unit result, Monetra requires a rounding policy.
If you don’t supply a rounding policy, Monetra throws RoundingRequiredError rather than silently choosing for you.
Rounding modes
Monetra exposes a small set of rounding modes:
HALF_UPHALF_DOWNHALF_EVEN(banker’s rounding)FLOORCEILTRUNCATE
HALF_EVEN is commonly used for financial calculations because it reduces aggregate rounding bias.
import { Money, RoundingMode } from 'monetra'
const price = Money.fromMajor('10.00', 'USD')
const perItem = price.divide(3, { rounding: RoundingMode.HALF_EVEN })
Why Monetra forces explicit rounding
Rounding is a business rule.
Two “correct” systems can legitimately choose different rounding strategies (invoice-level vs line-item rounding, regulatory requirements, customer fairness policies). Monetra makes that choice visible in code reviews instead of hiding it inside helper functions.
Currency and precision
Monetra includes an ISO 4217-backed currency registry and supports custom tokens.
The currency definition drives:
- The number of decimals (minor unit exponent)
- Formatting behavior
- Conversion behavior (when used with a converter)
This is why Money is always “amount + currency”, never just a number.
Formatting and parsing
Formatting is built on Intl.NumberFormat, so it’s locale-aware.
Typical flow:
- Store and compute as
Money - Format for UI at the boundary
- Parse user input back into
Moneyas early as possible
const total = Money.fromMinor(123456n, 'USD')
total.format({ locale: 'en-US' }) // "$1,234.56" (example)
Parsing turns a user-facing string into minor units using the currency’s decimal rules.
Multi-currency: MoneyBag
MoneyBag is a “wallet” for holding multiple currencies without forcing conversion.
Key behaviors:
- You can
add/subtractmoney into the bag. get(currency)returns a zero amount if that currency isn’t present.- You can compute a cross-currency total only by providing a converter.
import { Money, MoneyBag } from 'monetra'
const bag = new MoneyBag()
.add(Money.fromMajor('10.00', 'USD'))
.add(Money.fromMajor('5.00', 'EUR'))
const usd = bag.get('USD') // returns $10.00
Conversion: Converter
Conversion is intentionally explicit:
- You provide a base currency and rate table.
- You choose (or accept defaults for) rounding.
This keeps FX as a boundary where product/business rules can live.
Audit: Ledger + hash chain verification
Monetra’s ledger is designed for tamper-evident audit trails.
Conceptually:
- Each entry includes a
previousHash. - A hash is computed over the entry content and links entries into a chain.
verify()recomputes the chain and confirms that history was not rewritten.
The implementation supports both sync and async flows.
Hashing runtime notes
The ledger hashing code uses the best available crypto implementation for the runtime (Node.js crypto when available, otherwise WebCrypto/SubtleCrypto). This is one reason Monetra targets modern runtimes.
Serialization
Monetra is built to serialize safely:
Money.toJSON()emits a structured representation (not a float).Money.revivercan be used withJSON.parse(..., Money.reviver)to restore values.
This is useful for logs, caches, and APIs that need to preserve exactness.
Practical guidance
- Prefer
Money.fromMinor()andMoney.fromMajor()over floats. - Treat rounding strategy selection as part of product requirements.
- Keep conversion at explicit boundaries (don’t “auto total” mixed currencies).
- Use the ledger for audit narratives, not just storage.
Next additions
This page is intentionally developer-first. If you want, I can expand it with:
- A “rounding cookbook” (tax, discounts, proration, split allocation)
- A ledger walkthrough with snapshots + verification
- API reference tables for
Money,MoneyBag, andConverter
It is intended to document the architecture, mental models, and usage patterns behind Monetra - with a strong focus on making financial logic explicit, verifiable, and difficult to misuse.
The documentation is currently being prepared and will be published incrementally as the project continues to mature.
What This Documentation Will Cover
Once complete, this section will include:
-
Core mental models
How Monetra represents money values, currencies, precision, and immutability - and why these choices matter. -
Rounding and allocation policies
Explicit strategies for rounding, splitting, and allocating values safely in financial systems. -
Multi-currency handling
The MoneyBag pattern for aggregating and reasoning about values across currencies without losing correctness. -
Ledger and audit primitives
Building blocks for traceability, reconciliation, and tamper-evident verification. -
Practical usage guidance
Real-world examples and patterns for integrating Monetra into production systems.
Where to Start Today
While this documentation is still in progress, you can explore Monetra through:
- Project overview:
/projects/monetra - Source repository: https://github.com/zugobite/monetra
These provide the most up-to-date view of the project’s current state.
Status
This documentation is being written iteratively alongside the public API.
As interfaces stabilize, corresponding sections will be published here. If there is a particular concept or use case you would like documented first, feel free to open an issue on the repository and it will be prioritized.
This page will evolve into the canonical reference as Monetra reaches maturity.