SkillAgentSearch skills...

Bankster

Money as data, done right.

Install / Use

/learn @randomseed-io/Bankster

README

Bankster /ˈbæŋkstə/

Money as data, done right.

A pragmatic, EDN-friendly money & currency toolkit for Clojure: ISO 4217 / crypto / custom currencies, with precision-first arithmetic and an expressive DSL layer.

Bankster on Clojars Bankster on cljdoc CircleCI

This is a Clojure library for operating on monetary units with cryptocurrency and custom currency support.

Who is this for?

Bankster shines when you need money as data — predictable, comparable, composable — without sacrificing ergonomics.

Typical users and use-cases:

  • Fintech / payments / wallets / exchanges: model fiat + crypto under one coherent API (including non-standard codes).
  • Accounting-ish domains: operate on amounts with fixed, explicit scale and controlled rounding.
  • Data pipelines & ETL: parse/emit EDN safely (tagged literals, data readers) and keep currency semantics attached to numbers.
  • Systems integrating many “currency worlds”: keep separate registries per source/environment (dynamic/global/local registries).
  • Apps that want a small “money DSL”: concise literals/macros for currencies and amounts.

If your primary need is exchange-rate discovery, market data, or full-blown accounting/ledger rules — Bankster is intentionally smaller and focuses on representing currency + amount and doing safe operations around that.

Why Bankster (in one breath)

  • Precision-first arithmetic: BigDecimal everywhere, currency scale-aware operations, and tooling for non-terminating expansions.
  • Allocation and distribution: built-in functions for allocating and distributing monetary amounts.
  • One model for ISO + crypto + custom: namespaced identifiers like crypto/ETH, plus conflict-safe code resolution via weights.
  • EDN-native ergonomics: tagged literals (#money, #currency), readers, macros, and parsing that make data pipelines pleasant.
  • Registry concept: treat currencies as a database you can extend, swap, or scope per computation.
  • Polymorphism: protocols (Scalable, Monetary, Accountable) keep it extensible and composition-friendly.

Features

  • Pure Clojure implementation based on Java's BigDecimal.
  • Uses records to organize data: Registry, Currency, Money.
  • Built-in standard currencies database, extendable with EDN file.
  • Ability to create ad hoc currencies (with optional registering).
  • Different sources of currency registries (dynamic, global or local).
  • Registry hierarchies for classification (:domain, :kind, custom)
    and optional per-currency traits.
  • Polymorphic interfaces (Scalable, Monetary, Accountable protocols).
  • Ability to cast and convert monetary amounts.
  • Useful macros to express currencies and monetary amounts with various forms.
  • Namespaced identifiers for non-ISO currencies (e.g. crypto/ETH).
  • Common math operators.
  • Auto-rescaling in math operations to handle non-terminating decimal expansion.
  • Variants of variadic math functions with rescaling after each consecutive operation.
  • Optional rescaling of monetary amounts with keeping track of nominal scales.
  • Functions for rounding to a scale or to the given interval.
  • Functions for allocating and distributing of monetary amounts.
  • Tagged literals for currencies and monetary amounts.
  • Data readers for currencies and monetary amounts expressed with EDN.
  • Customizable currency and money formatting with locale support.
  • JSON and EDN serialization with per-format protocols, minimal/full representations, and Cheshire integration.

Quickstart

Bankster models currencies and monetary amounts. The recommended entrypoint is the Front API (io.randomseed.bankster.api.*) and the default registry.

To start using Bankster add it as a dependency to your project:

;; deps.edn

{io.randomseed/bankster {:mvn/version "2.2.4"}}

;; Leiningen

[io.randomseed/bankster "2.2.4"]

You can also download JAR from Clojars.

Then you can use require to make API namespaces ready to use:

(require
  '[io.randomseed.bankster.api          :as   b]
  '[io.randomseed.bankster.api.money    :as   m]
  '[io.randomseed.bankster.api.currency :as   c]
  '[io.randomseed.bankster.api.ops      :as ops])

;; currency code resolution using the default registry (strict)

(def usd (c/resolve "USD"))
(def pln (c/resolve "PLN"))

;; create Money values (strict)

(def a (m/money 12.34M usd))
(def b (m/money 10M    pln))

;; arithmetic (Money semantics, strict)

(ops/+ a (m/money 1.66M usd))
(ops/- a (m/money 2M    usd))

;; EDN tagged literals (optional convenience, strict)

#money[1.66M :USD]
#currency[PLN]

;; soft resolution (nil on failure)

(c/resolve-try "NOT_A_CURRENCY") ; => nil
  • Strict variants throw on missing currency/match.
  • Soft variants (usually -try) return nil.

Use soft at boundaries (parsing/import), strict in core domain logic. Quickstart uses the default registry; registry scoping options are described in the docs.

Pitfalls

A few things that can bite you in production if you treat money/currency like "just numbers and strings":

  • Don't use clojure.core/= to compare Money.

BigDecimal equality is scale-sensitive (1.0 vs 1.00), so value-equality can surprise you. Use Bankster's dedicated predicates (e.g. money/eq?, money/==) or the operator layer (money.inter-ops, money.api.inter-ops) depending on the semantics you want.

  • Be careful with untrusted input – avoid keyword interning.

Turning arbitrary user data into keywords can leak memory (interning is global). Prefer string IDs/codes and the helper functions intended for untrusted values (e.g. to-id-str, to-code-str), then resolve currencies through the registry.

  • Non-terminating division requires an explicit policy.

BigDecimal division can throw for non-terminating results unless you provide a rounding/rescaling policy. In Bankster, use the rounding/rescaling helpers (or the Front API variants that accept a rounding mode) so the behavior is deterministic and documented.

  • Know whether you want strict or soft behavior at the boundaries.

Bankster provides strict (throwing) and soft (nil-returning) variants (e.g. resolve vs resolve-try, money vs money-try). A good pattern is: soft at system boundaries (parsing/import), strict in the core business logic.

  • Registries are a feature – but choose the scoping deliberately.

Registry can be global, dynamically scoped, or passed explicitly. Pick one policy per subsystem to avoid "it works on my machine" issues (especially in tests and multi-tenant setups).

Warning about literal amounts

Clojure changes number literals into objects of various numeric data types. Some of them will have fixed precision when there is a decimal separator present, yet they will not be big decimals before entering monetary functions of Bankster.

Putting a decimal number having more than 16–17 digits will often result in accidental approximation and casting it to a double value. This value may become the amount of money which probably is not what you want:

1234.5678910111213000001
; => 1234.5678910111212

To work around that you should:

  • Use big decimal literals (e.g. (api-money/of XXX 1234.56789101112M) – note the M).
  • Use strings (e.g. (api-money/of "1234.56789101112 XXX")).
  • Use api-money/of macro or #money tagged literal with amount and currency in joint form (or with the above tactics applied), e.g.:
    • (api-money/of XXX123.45678),
    • #money XXX123.45678,
    • #money "XXX123.45678",
    • #money "123.456789101112 XXX",
    • #money[123.45678M XXX].

As it may not be a problem in case of regular currencies, it may pop-up when using scale-wide cryptocurrencies, like Ether or Ethereum tokens, having 18 decimal places.

Front API

Bankster provides a curated front API under io.randomseed.bankster.api.* (recommended for application code). See API for an overview.

For major-version stability there is a frozen API namespace: io.randomseed.bankster.api.v2 and its sub-namespaces. It mirrors io.randomseed.bankster.api for the Bankster 2.x line. When Bankster 3 appears, the v2 API will remain available for compatibility. In the current release, io.randomseed.bankster.api.v2.* is equivalent to io.randomseed.bankster.api.*.

Contracts

See Bankster Contracts for practical contracts (what is guaranteed, what is "soft" vs "strict", how the default registry is chosen, when exceptions are thrown, how the protocols behave) for Bankster's core axis: Currency, Money, Registry records and the Monetary, Scalable and Accountable protocols.

Specs

Clojure Spec definitions for Bankster's core data structures live in io.randomseed.bankster.spec (aggregator) and in the io.randomseed.bankster.spec.* sub-namespaces:

  • spec.primitives — sentinel values, basic types, and composite specs (e.g. currency scale, numeric ID, domain);
  • spec.scale — auto-scaled sentinel spec;
  • spec.records — record-level specs for Currency, Money, Registry, and CurrencyHierarchies (including ISO/non-ISO Currency variants).

Design

The core design principle: money is a value with a currency attached, and currencie

Related Skills

View on GitHub
GitHub Stars69
CategoryFinance
Updated14d ago
Forks6

Languages

Clojure

Security Score

85/100

Audited on Mar 10, 2026

No findings