System Architecture
Dualis Finance is built as a modular monorepo that bridges traditional financial infrastructure with decentralized smart contracts on the Canton Network. This page describes the system topology, technology stack, and data flow for core operations.
High-Level Overview
The platform follows a hybrid on-chain/off-chain architecture. User-facing operations originate in the frontend, pass through a centralized API layer for validation and orchestration, and ultimately settle on Canton via DAML smart contracts. Off-chain components handle rate computation, indexing, and caching, while Canton provides atomic settlement, privacy-preserving execution, and an immutable audit trail.
Technology Stack
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Frontend | Next.js / React | 14.2 / 18 | Server-rendered UI with client-side state management |
| State Management | Zustand | 4.x | Lightweight global state for wallet, positions, pools |
| Styling | Tailwind CSS / Radix UI | 3.x / 1.x | Utility-first styling with accessible primitives |
| API Server | Fastify | 5.7 | High-performance HTTP + WebSocket API |
| ORM | Drizzle ORM | 0.3x | Type-safe SQL with zero runtime overhead |
| Database | PostgreSQL | 16 | 63 tables covering positions, pools, users, credit, governance |
| Cache / Queue | Redis / BullMQ | 7.x | Rate caching, session store, and background job processing |
| Smart Contracts | DAML (Canton) | 3.4 / LF 2.1 | 38 templates for lending, collateral, liquidation, governance |
| Testing | Vitest | 1.x | Unit and integration tests (230+ shared package tests) |
Monorepo Structure
The codebase is organized as a pnpm workspace managed by Turborepo. Each package has a clear boundary and well-defined dependency graph:
| Package | Path | Description |
|---|---|---|
| frontend | packages/frontend | Next.js 14 application with App Router, Zustand stores, and Tailwind |
| api | packages/api | Fastify server with Drizzle ORM, BullMQ jobs, Canton client |
| shared | packages/shared | Financial math engine, type definitions, formatters, constants |
| canton | packages/canton | DAML source (25 modules), DAR build scripts, deployment tooling |
| config | packages/config | Shared ESLint, TypeScript, and Tailwind configuration |
The shared package must build first because both api and frontend depend on its exported types and utility functions. Turborepo handles this ordering automatically via the dependency graph.
Data Flow: Supply Operation
To illustrate how the layers interact, here is the step-by-step data flow when a user supplies USDC to a lending pool:
- Frontend: The user connects their Canton wallet via PartyLayer and selects an amount to supply. The frontend validates the input and sends a
POST /api/v1/pools/:id/supplyrequest. - API — Validation: Fastify middleware authenticates the session, checks KYC status, and validates the amount against pool limits and the user's wallet balance.
- API — Rate Computation: The shared math engine calculates the current supply index using the jump rate model. The accrued index is persisted to PostgreSQL and cached in Redis for subsequent reads.
- Canton — Settlement: The API submits a DAML command to exercise the
Supplychoice on theLendingPoolcontract. Canton atomically mints pool tokens (dTokens) and transfers the underlying asset. - API — Indexing: A BullMQ worker listens for the Canton transaction result, updates the position in PostgreSQL, and pushes a WebSocket event to the frontend.
- Frontend — Update: The Zustand store receives the WebSocket event, updates the user's position, and re-renders the portfolio view with the new supply balance and projected APY.
Infrastructure
In the current devnet deployment, all services run on a single host behind an Nginx reverse proxy with Let's Encrypt TLS. The Canton validator node (v0.5.12) operates within a Docker network, with the JSON API accessible on port 7575 and gRPC on port 5001. PostgreSQL, Redis, the API server, and the frontend each run in dedicated containers orchestrated by Docker Compose.
For mainnet, the architecture is designed to scale horizontally: the API layer is stateless (session state lives in Redis), BullMQ workers can be replicated across nodes, and PostgreSQL supports read replicas for query-heavy paths such as portfolio analytics and pool listings.