Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Standard library

Standard library

Money and payments

Use Money for an amount of money and Currency for its currency. Both are standard types. An amount is held as a whole number of the currency's minor units, such as pence or cents, together with its Currency. It is never a decimal of pounds. Each currency's exponent, the number of digits after its decimal point, comes from ISO 4217: jpy 0, gbp 2, bhd 3.

Money is a type built into the language, as Duration is, not an object: an amount has no fields, and money.minor and money.currency read its two parts. Like Int, it is a representation, so a signature names a type built on it (Signatures use named types). A type built on Money takes these settings:

  • currency:, a Currency variant, fixes the currency;
  • rounding:, a Rounding variant, says how * and / round to a minor unit;
  • min: and max: are whole numbers of minor units.

An amount holds at most 2^53 - 1 minor units either side of zero, JavaScript's largest safe integer, so the amount sent on the wire is always a JSON number a client reads exactly. Making or working out an amount past that is overflow, a defect, as it is for + on Int.

Currency is an enum of the active ISO 4217 codes, written Currency.gbp, and each release ships the list. It is ISO 4217's list less the codes with no minor unit (N.A.: the precious metals, the SDR, the bond-market units, XSU, XUA, XTS and XXX): 164 codes, each with its exponent. The codes ISO has withdrawn since 2021 (ANG, BGN, CUC, HRK, SLL, ZWL) are retired variants: your code can read them but not write them (type.variant_retired). Albania's lek is Currency.all_, because .all lists an enum's variants; its code is still ALL.

A Currency is spelt by its code, GBP, on the wire, in settings, in OpenAPI and in its column, as every standard enum is, so the database reads as the API does. text.format gives its CLDR display name, such as "Japanese Yen".

A bakery prices its cakes in pounds, takes orders from abroad in the customer's currency, and shares a bill between friends:

example
type CakePrice = Money {    currency: Currency.gbp  // fixed: adding another currency doesn't compile    min: 0  // in minor units: pence    rounding: Rounding.half_up  // how `*` and `/` round to a penny}type Payment = Money {}  // any currency, known only while runningtype Pounds = Decimal {    scale: 2}type Guests = Int {    min: 1    max: 12}type AmountTooPrecise = Failure {    kind: FailureKind.invalid_value}type MixedCurrencies = Failure {    kind: FailureKind.invalid_value}/// A cake's price, written in pence.function list_price() -> CakePrice {    return CakePrice(money.from_minor(1050, Currency.gbp))  // £10.50}/// An amount a customer typed, in major units of their currency.function entered(amount: Pounds, currency: Currency) fails AmountTooPrecise -> Payment {    var entered_amount: Money = attempt money.of(amount, currency) {        too_many_places: AmountTooPrecise  // 10.50 in yen    }    return Payment(entered_amount)}/// A cake delivered: one currency, so a plain `+`.function delivered(cake: CakePrice, delivery: CakePrice) -> CakePrice {    return CakePrice(cake + delivery)}/// The VAT in a price at 20%, rounded half up to a penny.function vat_in(total: CakePrice) -> CakePrice {    return CakePrice(total * 0.2)}/// A price before VAT: dividing by a rate.function before_vat(gross: CakePrice) -> CakePrice {    return CakePrice(gross / 1.20)}/// A bill shared between friends, every penny to someone.function bill_shares(total: CakePrice, guests: Guests) -> List<Payment> {    return list.map(money.split(total, guests), share -> Payment(share))}/// Payments added up, in whatever currency they share.function received(first: Payment, second: Payment) fails MixedCurrencies -> Payment {    var total: Money = attempt first + second {        currency_mismatch: MixedCurrencies  // a pound and a euro    }    return Payment(total)}/// A price for people in the reader's locale.function price_label(cake: CakePrice, customer: Locale) -> PresentationText {    return text.format(cake, locale: customer)}
example
test "amounts add, round and share in minor units" {    var delivery: CakePrice = CakePrice(money.from_minor(250, Currency.gbp))    var gross: CakePrice = CakePrice(money.from_minor(1200, Currency.gbp))    var bill: CakePrice = CakePrice(money.from_minor(1000, Currency.gbp))    assert list_price() == money.from_minor(1050, Currency.gbp)    assert delivered(list_price(), delivery) == money.from_minor(1300, Currency.gbp)    assert vat_in(list_price()) == money.from_minor(210, Currency.gbp)    assert before_vat(gross) == money.from_minor(1000, Currency.gbp)    var pence: List<Int> = list.map(bill_shares(bill, 3), share -> money.minor(share))    assert pence == [334, 333, 333]}test "a currency known only while running is checked as it runs" {    var in_yen: Payment = attempt entered(500.00, Currency.jpy)    assert in_yen == money.from_minor(500, Currency.jpy)    assert entered(10.50, Currency.jpy) fails AmountTooPrecise    var euros: Payment = Payment(money.from_minor(500, Currency.eur))    var pounds: Payment = Payment(money.from_minor(250, Currency.gbp))    var both: Payment = attempt received(euros, euros)    assert both == money.from_minor(1000, Currency.eur)    assert received(euros, pounds) fails MixedCurrencies}test "an amount is shown, written and read one way" {    var large: CakePrice = CakePrice(money.from_minor(123450, Currency.gbp))    var abroad: Payment = Payment(money.from_minor(123450, Currency.eur))    assert price_label(large, Locale.en_gb) == "£1,234.50"    assert text.format(abroad, locale: Locale.en_us) == "€1,234.50"    assert text.format(list_price(), locale: Locale.en_gb, precision: MoneyPrecision.whole) == "£11"    assert text.from(list_price()) == "GBP 10.50"    assert text.format(Currency.jpy, locale: Locale.en_gb) == "Japanese Yen"}

Inside if (money.currency(a) == money.currency(b)), the checker knows the two currencies match, so + needs no attempt. Comparing a currency with one written out, as in money.currency(a) == Currency.gbp, proves that currency, so the amount builds a type that fixes it with no arm for currency:

example
type Payment = Money {}type SterlingPayment = Money {    currency: Currency.gbp}/// Two payments added when they share a currency, the first otherwise.function sum_if_shared(a: Payment, b: Payment) -> Payment {    if (money.currency(a) == money.currency(b)) {        return Payment(a + b)    }    return a}/// A payment in pounds, when it is one.function sterling(paid: Payment) -> SterlingPayment? {    if (money.currency(paid) == Currency.gbp) {        return SterlingPayment(paid)    }    return none}

Markdown for agents: /docs/money-and-payments.md.Built from Jadpo main at a707efb.