Bankster
Money as data, done right.
Install / Use
/learn @randomseed-io/BanksterREADME
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.
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:
BigDecimaleverywhere, 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,Accountableprotocols). - 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) returnnil.
Use soft at boundaries (parsing/import), strict in core domain logic. Quickstart uses the default registry; registry scoping options are described in the docs.
- For demonstrative snippets see Sneak Peeks.
- For more complete, runnable examples see the
examples/directory in the source repository.
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 theM). - Use strings (e.g.
(api-money/of "1234.56789101112 XXX")). - Use
api-money/ofmacro or#moneytagged 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 forCurrency,Money,Registry, andCurrencyHierarchies(including ISO/non-ISOCurrencyvariants).
Design
The core design principle: money is a value with a currency attached, and currencie
Related Skills
beanquery-mcp
41Beancount MCP Server is an experimental implementation that utilizes the Model Context Protocol (MCP) to enable AI assistants to query and analyze Beancount ledger files using Beancount Query Language (BQL) and the beanquery tool.
valuecell
9.7kValueCell is a community-driven, multi-agent platform for financial applications.
REFERENCE
An intelligent middleware layer between crypto wallets and traditional payment systems.
mcp-yfinance-server
49Real-time stock API with Python, MCP server example, yfinance stock analysis dashboard
