crystalbank
crystalbank
CrystalBank is an open source, event-sourced multi-purpose ledger system built in Crystal. It is designed to serve as a solid foundation for financial applications — demonstrating how core banking concepts can be implemented cleanly on a minimal, self-contained infrastructure.
What it does
At its core, CrystalBank is an event-sourced ledger (powered by crystal-es) with first-class support for accounts, transfers, and customers. Every state change is recorded as an immutable event, making the full history of the system auditable and replayable.
The system is multi-tenant ready through an embedded roles and permissions model that provides fine-grained data scoping and ownership. Access rights and data visibility are controlled at the data level — not just at the API boundary.
Multi-layer approval workflows allow financial operations to be reviewed and approved across multiple levels before they take effect, making the system suitable for regulated environments where maker-checker controls are required.
The entire stack runs on PostgreSQL — the same database instance handles the event store, projections, and the outbox queue. This keeps the infrastructure footprint minimal while preserving flexibility across different deployment contexts.
A Svelte-based frontend dashboard provides a visual interface to the system, making it accessible to non-engineers and easier to demonstrate.
Capabilities
| Domain | Status | Description |
|---|---|---|
| Accounts | ✅ | Full account lifecycle management including opening, blocking, and unblocking with stackable cause tracking |
| Payments | ✅ | SEPA credit transfer processing with approval workflow integration and automated ledger posting |
| Ledger | ✅ | Double-entry bookkeeping engine recording all money movements as immutable debit/credit entries |
| Customers | ✅ | Customer onboarding and profile management for natural persons and organisations |
| Users | ✅ | User management with role assignment and scope-based access control |
| API Keys | ✅ | Generation, rotation, and revocation of API keys for programmatic access |
| Authentication | ✅ | API key and credential-based authentication for both human users and machine clients |
| Approval Workflows | ✅ | Multi-layer maker-checker workflows allowing sensitive operations to be reviewed and approved across multiple levels |
| Roles & Permissions | ✅ | Fine-grained permission model with role grouping for controlling what each user can see and do |
| Scopes | ✅ | Hierarchical tenant and business unit isolation — controls data ownership and visibility across the ledger |
| Events | ✅ | Queryable audit trail exposing the full stream of domain events to authorised users |
Roadmap
- More refined and optimized frontend
- Dedicated project website
- Self sign-up to the platform
- Full event replay to re-project state at any point in time (via crystal-es)
- ISO 20022 connectors
- ISO 8583 connectors
Project Structure
crystalbank/
├── app/
│ ├── frontend/ # Svelte-based SPA dashboard
│ │ ├── src/ # Svelte components and application logic
│ │ ├── package.json
│ │ └── vite.config.js
│ ├── public/ # Built frontend assets (generated by build-frontend)
│ ├── src/ # Crystal backend source
│ └── spec/ # Crystal specs
├── docker-compose.yml
└── Makefile
Setup
Prerequisites
- Docker and Docker Compose
Start the environment
$ make dev
This builds the Docker image (if not already present), starts the database, API server, and API documentation services, and opens a console in the running container.
All available make targets can be listed with:
$ make help
Seed the environment
In order to access the API, the initial API credentials need to be seeded. Run this inside the console:
$ crystal app/src/server/start.cr --seed
This will output credentials for two admin users — a Super Admin (initiator) and an Approver:
-----------------------------------------------------
--- Seed credentials Super Admin
client_id: '0193cc51-cc9b-7955-82ca-7a6482587201'
client_secret: 'secret'
-----------------------------------------------------
-----------------------------------------------------
--- Seed credentials Approver
client_id: '0193cc51-dd2e-8866-93db-8b7593698312'
client_secret: 'secret'
-----------------------------------------------------
Start the API server
$ crystal src/server/start.cr
The API is exposed on http://localhost:4000.
Build the Svelte frontend
The frontend is a Svelte SPA located in app/frontend/. It is built with Vite and outputs static assets into app/public/, which is served by the Crystal backend.
To build the frontend, run from the project root:
$ make build-frontend
This runs npm install && npm run build inside a node:22-alpine Docker container — no local Node.js installation required. The build produces:
app/public/app.js— bundled JavaScriptapp/public/style.css— bundled CSS
To iterate on the frontend locally outside of Docker, use the Vite dev server directly:
$ cd app/frontend
$ npm install
$ npm run dev
Generate OpenAPI specs
To regenerate the API specification:
$ crystal src/server/start.cr -d -f openapi.json
The updated spec is automatically picked up by the running API documentation services.
View API documentation
- ReDoc: http://localhost:4002
- Swagger UI: http://localhost:4003
Access the PostgreSQL database
postgres://<user>:<password>/eventstore@localhost:4010
Contributing
- Fork it (https://github.com/your-github-user/crystalbank/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Tristan Holl - creator and maintainer
crystalbank
- 8
- 1
- 5
- 0
- 0
- 5 days ago
- October 23, 2024
MIT License
Mon, 13 Apr 2026 20:13:25 GMT