OS Trading Engine
Technical Documentation
Database
Schema & ERD

Schema & ERD

Nexgent uses PostgreSQL with Prisma ORM for type-safe database access. The schema is designed around trading agents, their wallets, positions, and transaction history.

Schema Overview

EntityPurpose
UserAuthentication and account management
AgentTrading agents with configuration
AgentWalletWallet assignments (simulation/live)
AgentBalanceToken balances per wallet
AgentPositionOpen positions with stop loss state
AgentTransactionAll transaction records
AgentHistoricalSwapCompleted trade history with P/L
AgentBalanceSnapshotHourly portfolio snapshots
TradingSignalTrading signals from sources
SignalExecutionSignal processing status per agent
ApiKeyAPI keys for programmatic access

Entity Relationship Diagram

The ERD below shows all database entities organized into logical layers, with relationships clearly marked. Key relationships use cardinality notation (1:N, N:1, 1:1) to indicate one-to-many, many-to-one, and one-to-one relationships.

Diagram Legend

  • PK = Primary Key
  • FK = Foreign Key (relationship to another table)
  • 1:N = One-to-Many (one parent, many children)
  • N:1 = Many-to-One (many children, one parent)
  • 1:1 = One-to-One (unique relationship)

Relationship Summary

RelationshipTypeDescription
User → ApiKey1:NUser can have multiple API keys
User → Agent1:NUser can create multiple agents
Agent → AgentWallet1:NAgent has wallets (simulation/live)
Agent → SignalExecution1:NAgent processes multiple signals
TradingSignal → SignalExecution1:NSignal can be processed by multiple agents
AgentWallet → AgentBalance1:NWallet holds multiple token balances
AgentWallet → AgentPosition1:NWallet can have multiple open positions
AgentWallet → AgentTransaction1:NWallet has transaction history
AgentWallet → BalanceSnapshot1:NWallet has hourly snapshots
AgentPosition → AgentTransaction1:1Position linked to purchase transaction
AgentTransaction → AgentHistoricalSwap1:NTransaction can be purchase or sale in swaps
TradingSignal → AgentTransaction1:NSignal can trigger multiple transactions
TradingSignal → AgentHistoricalSwap1:NSignal can result in multiple completed trades
AgentBalance → BalanceSnapshot1:NBalance contributes to snapshots

Note: The diagram shows all direct relationships. Some indirect relationships exist through foreign keys (e.g., AgentHistoricalSwap references AgentTransaction via purchaseTransactionId and saleTransactionId).


Core Relationships

User → Agents (1:N)

Users can create multiple agents. Each agent belongs to one user.

model User {
  id     String  @id @default(uuid())
  agents Agent[]
}
 
model Agent {
  id     String @id @default(uuid())
  userId String @map("user_id")
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
}

Agent → Wallets (1:N)

Each agent can have multiple wallets (one per trading mode).

model Agent {
  wallets AgentWallet[]
}
 
model AgentWallet {
  walletAddress String @id
  agentId       String
  walletType    String // "simulation" | "live"
  agent         Agent  @relation(fields: [agentId], references: [id], onDelete: Cascade)
  
  @@unique([agentId, walletType])  // One wallet per mode per agent
}

Wallet → Positions/Transactions (1:N)

Wallets own positions and transactions. Deleting a wallet cascades to its data.

model AgentWallet {
  positions    AgentPosition[]
  transactions AgentTransaction[]
  balances     AgentBalance[]
}

Signal → Executions (1:N)

Each signal can be processed by multiple agents, tracked via SignalExecution.

model TradingSignal {
  id               Int               @id @default(autoincrement())
  signalExecutions SignalExecution[]
}
 
model SignalExecution {
  id       String        @id @default(uuid())
  signalId Int
  agentId  String
  status   String        // "success" | "failed" | "skipped"
  signal   TradingSignal @relation(fields: [signalId], references: [id])
  
  @@unique([signalId, agentId])  // One execution per agent per signal
}

Key Design Decisions

1. UUID Primary Keys

Most entities use UUIDs for distributed-friendly IDs:

id String @id @default(uuid()) @db.Uuid

Exception: TradingSignal uses auto-increment for simpler signal references.

2. Wallet as Natural Key

AgentWallet uses wallet address as primary key since it's already unique:

model AgentWallet {
  walletAddress String @id @db.VarChar(44)
}

3. Trading Config as JSON

Agent trading configuration is stored as JSON for flexibility:

model Agent {
  tradingConfig Json?  // AgentTradingConfig type
}

JSON storage allows schema evolution without migrations. The application layer validates against TypeScript types.

4. Cascade Deletes

Parent deletions cascade to children:

agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)

5. Decimal Precision

Financial values use high-precision decimals:

PrecisionUse Case
Decimal(30, 18)Token prices, amounts (can be very small)
Decimal(20, 8)USD/SOL values
Decimal(10, 4)Percentages
Decimal(5, 2)Stop loss percentages

Schema Location

packages/backend/src/infrastructure/database/
├── schema.prisma      # Schema definition
├── client.ts          # Prisma client singleton
├── migrations/        # Migration history
└── repositories/      # Repository implementations