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.prisma file 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.

results matching ""

    No results matching ""