Database & Storage
The backend uses PostgreSQL 16 as its primary database, accessed through SeaORM — an async ORM for Rust.
Database Overview
| Property | Value |
|---|---|
| Engine | PostgreSQL 16 |
| ORM | SeaORM 1.1.17 |
| Connection pool | Max 30, Min 10, 30s timeout |
| Entities | 43 table definitions |
| Migrations | SQL-based, in migrations/pg/ |
Key Tables
The database is organized around several domains:
Users & Wallets
| Table | Purpose |
|---|---|
user |
Platform user accounts |
wallet |
Connected wallet addresses (EVM and Solana) |
user_address |
Additional user addresses |
goal |
User investment goals |
Smart Wallets (Safes)
| Table | Purpose |
|---|---|
safe_account |
Gnosis Safe instances |
safe_delegate |
Delegated operators |
safe_module |
Enabled modules per Safe |
safe_module_permission |
Granular permissions |
safe_discount |
Subscription discounts |
DeFi Positions
| Table | Purpose |
|---|---|
nmt_position |
Active and closed positions |
nmt_position_valuation |
Historical USD value snapshots |
nmt_position_flow |
Deposits, withdrawals, fee collections |
nmt_pool |
Indexed pool data (APY, TVL, tokens) |
Protocol-Specific
| Table | Purpose |
|---|---|
uniswap_v3_pool |
Uniswap V3 pool details |
uniswap_v2_pair |
Uniswap V2 pair details |
yearn_vault |
Yearn vault metadata |
morpho_vault |
Morpho vault metadata |
Pricing & Analytics
| Table | Purpose |
|---|---|
price |
Token price history |
coin |
Token metadata |
coin_data |
Extended token data |
apy_history |
Historical APY records |
token_daily_values_mv |
Materialized view for daily aggregates |
Transactions
| Table | Purpose |
|---|---|
eth_transaction |
EVM transaction records |
balance_change |
Token balance changes |
sol_balance_change |
Solana balance changes |
subscription_transaction |
Subscription payments |
swaps |
Token swap records |
Communication
| Table | Purpose |
|---|---|
chats |
Chat rooms |
messages |
Chat messages |
Repository Pattern
Business logic doesn't query the database directly. Instead, it goes through repositories — Rust traits that define the available operations:
// Example: PositionRepository
trait PositionRepository {
async fn find_by_user(&self, user_id: Uuid) -> Result<Vec<NmtPosition>>;
async fn create(&self, position: CreatePosition) -> Result<NmtPosition>;
async fn update_valuation(&self, id: Uuid, value: Decimal) -> Result<()>;
}
This pattern separates business logic from database queries, making the code easier to test and maintain.
Entity Location
All SeaORM entities are in backend/crates/storage/src/entities/. Each file corresponds to one database table.
Migrations
Database schema changes are managed through SQL migration files in backend/migrations/pg/. Migrations run automatically on application startup.
Shared Database
Both the backend (SeaORM) and the frontend (Prisma) connect to the same PostgreSQL database. The backend writes data (from indexing), and the frontend reads it directly via Prisma in Next.js server components and API routes.
There is no REST API layer between the frontend and the database — the frontend queries Prisma directly, enabling faster page loads via server components.
Schema Coordination — Known Issue
Two independent migration systems manage the same database:
| System | Location | ORM | Migrations |
|---|---|---|---|
| Backend | backend/migrations/pg/ |
SeaORM | SQL-based, run on app startup |
| Frontend | frontend-app/prisma/migrations/ |
Prisma | 129+ migrations, run via prisma db push |
There is no formal coordination process between these two migration systems. In practice, the backend owns the DeFi-related tables (nmt_position, nmt_pool, price, etc.) and the frontend owns user-facing tables (user, safe_account, wallet, etc.). Both sides read from tables owned by the other.
Risks:
- A backend migration that renames or drops a column could break Prisma queries (and vice versa)
- The Prisma
introspected.prismafile is auto-generated from the existing DB and may drift from reality - There is no CI check that validates both ORMs against the same schema version
This is a known architectural issue. Schema changes that affect cross-ORM tables require manual coordination between backend and frontend developers.