Skip to content

Proposal: org.accordproject.money@1.0.0 β€” remove DigitalCurrencyCode, support non-decimal denominations, fixed-point representationΒ #186

@mttrbrts

Description

@mttrbrts

Feature Request πŸ›οΈ

Propose a stable 1.0.0 release of org.accordproject.money, addressing three long-standing concerns: removal of digital currency types, correct handling of non-decimal currency denominations, and an optional precision-safe fixed-point representation.

Opening this for community discussion before any code changes land.

Use Case

The current model (org.accordproject.money@0.3.0) has a few limitations that surface in real contracts:

  1. DigitalMonetaryAmount / DigitalCurrencyCode mix cryptocurrency concerns into the core money model. These belong in a separate, specialised namespace.
  2. Double doubleValue is ambiguous across currencies with different minor-unit conventions β€” JPY (0 decimals), USD (2 decimals), KWD (3 decimals) all share the same field type but require different interpretation.
  3. No precision-safe option exists for contracts that need exact arithmetic (banking, settlement, regulated financial reporting). Floating-point rounding errors are unsuitable for these domains.
  4. CurrencyCode enum should be refreshed against the latest ISO 4217 active list (e.g., VES for the Venezuelan BolΓ­var Soberano).

Possible Solution

A dual-representation v1.0.0 model that:

  • Removes DigitalMonetaryAmount and DigitalCurrencyCode
  • Keeps MonetaryAmount (with doubleValue + currencyCode) backwards-compatible with v0.3.0
  • Adds FixedPointMonetaryAmount for precision-safe arithmetic
  • Annotates each CurrencyCode enum value with a @DecimalPlaces decorator carrying its ISO 4217 minor-unit count
  • Refreshes CurrencyCode against ISO 4217 as of 2026

Draft money@1.0.0.cto

concerto version "^3.0.0"

namespace org.accordproject.money@1.0.0

/**
 * Declares the @DecimalPlaces decorator.
 * Carries the ISO 4217 minor-unit count for each currency.
 */
declaration DecimalPlaces {
  o Integer value range=[0,4]
}

/**
 * ISO 4217 currency codes (active codes as of 2026), each annotated
 * with its standard minor-unit count via @DecimalPlaces.
 *
 *   0 decimals: BIF, CLP, DJF, GNF, ISK, JPY, KMF, KRW, PYG, RWF,
 *               UGX, VND, VUV, XAF, XOF, XPF
 *   2 decimals: most currencies (USD, EUR, GBP, AUD, CAD, …)
 *   3 decimals: BHD, BOV, COU, IQD, JOD, KWD, LYD, OMR, TND, UYI,
 *               XAU, XAG, XPT, XPD, XBA, XBB, XBC, XBD
 */
enum CurrencyCode {
  @DecimalPlaces(2) o AED
  @DecimalPlaces(2) o AFN
  // ... full ISO 4217 list ...
  @DecimalPlaces(3) o BHD
  @DecimalPlaces(0) o BIF
  // ...
  @DecimalPlaces(0) o JPY
  // ...
  @DecimalPlaces(3) o KWD
  // ...
  @DecimalPlaces(2) o USD
  // ...
  @DecimalPlaces(2) o VES   // NEW: Venezuelan BolΓ­var Soberano
  @DecimalPlaces(2) o ZWL
}

/**
 * Abstract base for monetary amounts. All representations share a currency code.
 */
abstract concept Amount {
  o CurrencyCode currencyCode
}

/**
 * Decimal-based monetary amount (v0.3.0 compatible).
 *
 * Use when: legacy contracts, display values, estimates where
 * floating-point precision loss is acceptable.
 *
 * Example: { doubleValue: 10.50, currencyCode: "USD" }
 */
concept MonetaryAmount extends Amount {
  o Double doubleValue
}

/**
 * Fixed-point monetary amount (v1.0 precision-safe).
 *
 * Represents: value = mantissa Γ— 10^(-scale)
 *
 * Use when: financial calculations requiring exact arithmetic,
 * currencies with non-decimal denominations, audit trails,
 * regulated reporting.
 *
 * The scale should match the currency's @DecimalPlaces decorator value.
 *
 * Examples:
 *   { mantissa: 1050,   scale: 2, currencyCode: "USD" } = $10.50
 *   { mantissa: 10050,  scale: 0, currencyCode: "JPY" } = Β₯10,050
 *   { mantissa: 100500, scale: 3, currencyCode: "KWD" } = 100.500 KWD
 */
concept FixedPointMonetaryAmount extends Amount {
  o Long mantissa
  o Integer scale default=2
}

/**
 * Currency exchange rate (decimal).
 */
concept CurrencyConversion {
  o CurrencyCode from
  o CurrencyCode to
  o Double rate
}

Why decorators instead of a CurrencyDecimalPlaces concept

Earlier drafts of this proposal carried a separate CurrencyDecimalPlaces concept as reference data. Decorators are strictly better:

  • Single source of truth β€” metadata lives on the enum value itself; no risk of drift between two collections
  • Idiomatic Concerto β€” decorators are the language's intended mechanism for static metadata
  • Smaller surface β€” eliminates a concept from the model
  • Tooling-friendly β€” code generators, validators, and introspection APIs can access the value via getDecorator('DecimalPlaces')

Tradeoff: decorators are not part of the wire format, so consumers need access to the model (not just the instance) to resolve the scale. This is fine for any caller that already loads the CTO via a ModelManager.

Conversion semantics

The two amount representations are interconvertible by reading the decorator off the enum value:

MonetaryAmount β†’ FixedPointMonetaryAmount
  Input:  { doubleValue: 10.50, currencyCode: "USD" }
  Lookup: CurrencyCode.USD.@DecimalPlaces = 2
  Output: { mantissa: 1050, scale: 2, currencyCode: "USD" }

FixedPointMonetaryAmount β†’ MonetaryAmount
  Input:  { mantissa: 1050, scale: 2, currencyCode: "USD" }
  Output: { doubleValue: 10.50, currencyCode: "USD" }

JavaScript helper sketch:

function getDecimalPlaces(modelManager, currencyCode) {
  const enumDecl = modelManager.getType('org.accordproject.money@1.0.0.CurrencyCode');
  const enumValue = enumDecl.getOwnProperty(currencyCode);
  return enumValue.getDecorator('DecimalPlaces').getArguments()[0];
}

Decimal places reference (ISO 4217)

Decimals Count Currencies
0 16 BIF, CLP, DJF, GNF, ISK, JPY, KMF, KRW, PYG, RWF, UGX, VND, VUV, XAF, XOF, XPF
2 ~137 AED, AUD, CAD, CHF, CNY, EUR, GBP, USD, … (most currencies)
3 18 BHD, BOV, COU, IQD, JOD, KWD, LYD, OMR, TND, UYI, XAU, XAG, XPT, XPD, XBA, XBB, XBC, XBD

Context

This proposal came out of a discussion about modelling monetary amounts in Cicero contracts. The driving questions were:

  • Can a JPY amount be represented unambiguously without out-of-band knowledge?
  • Can a contract that settles money use a representation that doesn't introduce floating-point drift?
  • Is the digital-currency concept still earning its place in the core money model, or should it move out?

The dual-representation approach lets template authors choose: keep MonetaryAmount for human-readable / display / legacy use, opt into FixedPointMonetaryAmount for precision-critical work, and convert between them when integrating systems with different expectations.

Detailed Description

Use case guidance

Scenario Recommended type
Legacy contracts (v0.3.0 migration) MonetaryAmount
Display values, UI bindings MonetaryAmount
Banking, settlement, ledgers FixedPointMonetaryAmount
Tax computations, accruals FixedPointMonetaryAmount
Regulated financial reporting FixedPointMonetaryAmount
Cross-currency arithmetic FixedPointMonetaryAmount
Estimated / approximate values MonetaryAmount

Breaking changes

  • Removal of DigitalMonetaryAmount and DigitalCurrencyCode. Contracts using these would migrate to either:
    • A new dedicated namespace (e.g. org.accordproject.crypto@1.0.0), or
    • Continue using @0.3.0 in parallel (Concerto supports multi-version imports), or
    • Switch to fiat MonetaryAmount where appropriate.

Non-breaking additions

  • FixedPointMonetaryAmount β€” new concept
  • Amount β€” new abstract base (existing MonetaryAmount extends it)
  • @DecimalPlaces decorator on each CurrencyCode value
  • New CurrencyCode enum entries (e.g. VES)

Design alternatives considered

  1. Generic FixedPoint<S> type β€” rejected: Concerto doesn't have generics and adding them for one type is too invasive.
  2. Replace Double doubleValue with Long mantissa outright β€” rejected: breaks every v0.3.0 contract.
  3. Single concept with optional mantissa + scale alongside doubleValue β€” rejected: dual-state in one concept invites consistency bugs; clearer to model as two related concepts sharing Amount.
  4. Separate CurrencyDecimalPlaces reference concept β€” rejected in favour of @DecimalPlaces decorator; metadata co-located with the enum is harder to desynchronise.
  5. String-based decimal representation β€” rejected: loses type information, requires parsing on every operation.

The dual-concept approach with a shared Amount base and decorator-annotated enum felt most idiomatic for Concerto.

Open questions for the community

  1. Is the abstract Amount base useful, or does it add ceremony without payoff?
  2. Are there code-generation targets in concerto-codegen that strip decorators? If so, what's the mitigation for consumers in those languages?
  3. Should we also add FixedPointCurrencyConversion for precision-safe FX rates?
  4. Are there contracts in the wild that still rely on DigitalMonetaryAmount / DigitalCurrencyCode? If so, what's the migration story?
  5. Should we add Concerto validation constraints (e.g., scale range [0, 4]) on FixedPointMonetaryAmount, or leave that to applications?

Happy to iterate on this β€” feedback welcome before any PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions