Documentation

Monetra

Developer Documentation • 6 min read

On This Page

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.23 becomes 123n (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_UP
  • HALF_DOWN
  • HALF_EVEN (banker’s rounding)
  • FLOOR
  • CEIL
  • TRUNCATE

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 Money as 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/subtract money 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.reviver can be used with JSON.parse(..., Money.reviver) to restore values.

This is useful for logs, caches, and APIs that need to preserve exactness.

Practical guidance

  • Prefer Money.fromMinor() and Money.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, and Converter

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.