OS Trading Engine
Contributing
Code Style Guide

Code Style Guide

This guide covers coding conventions used in Nexgent. Following these guidelines ensures consistency across the codebase.

General Principles

  1. Readability - Code should be self-documenting
  2. Consistency - Follow existing patterns
  3. Simplicity - Prefer simple solutions
  4. Type Safety - Use TypeScript strictly

TypeScript

Strict Mode

TypeScript strict mode is enabled:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

Type Annotations

// Good: Explicit types for function signatures
function calculateStopLoss(price: number, percentage: number): number {
  return price * (1 + percentage / 100);
}
 
// Good: Interface for objects
interface Agent {
  id: string;
  name: string;
  tradingMode: 'live' | 'simulation';
}
 
// Avoid: any type
function processData(data: any) { }  // ❌
function processData(data: unknown) { }  // ✓

Type vs Interface

// Use interface for object shapes
interface Agent {
  id: string;
  name: string;
}
 
// Use type for unions, primitives, tuples
type TradingMode = 'live' | 'simulation';
type Result<T> = { success: true; data: T } | { success: false; error: string };

Naming Conventions

Variables and Functions

// camelCase for variables and functions
const agentBalance = 10.5;
function getAgentById(id: string) { }
 
// Descriptive names
const a = 10;  // ❌ unclear
const solBalance = 10;  // ✓ clear

Classes and Interfaces

// PascalCase for classes and interfaces
class AgentService { }
interface TradingConfig { }
 
// Prefix interfaces with 'I' for repository contracts
interface IAgentRepository { }

Constants

// SCREAMING_SNAKE_CASE for constants
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_TIMEOUT_MS = 5000;
 
// Group related constants in objects
const REDIS_KEYS = {
  AGENT_CONFIG: (id: string) => `agent:${id}:config`,
  BALANCE: (wallet: string) => `balance:${wallet}`,
};

Files and Directories

// kebab-case for files
agent-service.ts
trading-config.ts
price-update-manager.ts

// Feature-based directory names
domain/
  agents/
    agent-service.ts
    agent.repository.ts
  trading/
    position-service.ts

Code Organization

File Structure

// 1. Imports (external, then internal, then relative)
import express from 'express';
import { prisma } from '@/infrastructure/database/client.js';
import { calculateStopLoss } from './utils.js';
 
// 2. Types and interfaces
interface ServiceConfig {
  timeout: number;
}
 
// 3. Constants
const DEFAULT_TIMEOUT = 5000;
 
// 4. Main class/function
export class MyService {
  // ...
}
 
// 5. Helper functions (private)
function helperFunction() {
  // ...
}

Import Order

// 1. Node built-ins
import { randomUUID } from 'crypto';
 
// 2. External packages
import express from 'express';
import { z } from 'zod';
 
// 3. Internal packages
import { DEFAULT_TRADING_CONFIG } from 'nexgent-open-source-trading-engine/shared';
 
// 4. Absolute imports (path aliases)
import { prisma } from '@/infrastructure/database/client.js';
import { AgentService } from '@/domain/agents/agent-service.js';
 
// 5. Relative imports
import { helper } from './utils.js';

Functions

Function Length

Keep functions short and focused:

// Good: Single responsibility
async function getAgent(id: string): Promise<Agent | null> {
  return prisma.agent.findUnique({ where: { id } });
}
 
async function validateAgentOwnership(agentId: string, userId: string): Promise<boolean> {
  const agent = await getAgent(agentId);
  return agent?.userId === userId;
}
 
// Avoid: Functions that do too much
async function getAndValidateAndUpdateAgent() { }  // ❌

Parameter Count

Limit parameters; use objects for many options:

// Good: Object parameter for many options
interface CreateAgentOptions {
  name: string;
  userId: string;
  tradingMode: TradingMode;
  walletAddress?: string;
}
 
async function createAgent(options: CreateAgentOptions): Promise<Agent> {
  // ...
}
 
// Avoid: Many positional parameters
async function createAgent(
  name: string,
  userId: string,
  mode: string,
  wallet: string,
  config: object
) { }  // ❌

Return Early

// Good: Early returns reduce nesting
function validateAgent(agent: Agent | null): Agent {
  if (!agent) {
    throw new Error('Agent not found');
  }
  
  if (agent.status !== 'active') {
    throw new Error('Agent not active');
  }
  
  return agent;
}
 
// Avoid: Deep nesting
function validateAgent(agent: Agent | null): Agent {
  if (agent) {
    if (agent.status === 'active') {
      return agent;
    } else {
      throw new Error('Agent not active');
    }
  } else {
    throw new Error('Agent not found');
  }
}  // ❌

Error Handling

Custom Error Classes

// Domain-specific errors
export class AgentServiceError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly details?: Record<string, unknown>
  ) {
    super(message);
    this.name = 'AgentServiceError';
  }
}
 
// Usage
throw new AgentServiceError(
  'Agent not found',
  'AGENT_NOT_FOUND',
  { agentId }
);

Try-Catch Patterns

// Good: Specific error handling
try {
  await executeSwap(params);
} catch (error) {
  if (error instanceof SwapServiceError) {
    // Handle swap-specific errors
    logger.error({ error }, 'Swap failed');
    throw error;
  }
  
  // Re-throw unexpected errors
  throw new Error(`Unexpected error: ${error}`);
}

Async/Await

Always Use Async/Await

// Good: async/await
async function fetchData(): Promise<Data> {
  const response = await fetch(url);
  const data = await response.json();
  return data;
}
 
// Avoid: Promise chains (unless necessary)
function fetchData(): Promise<Data> {
  return fetch(url)
    .then(res => res.json())
    .then(data => data);
}  // ❌ harder to read

Parallel Execution

// Good: Run independent operations in parallel
const [agent, balance, positions] = await Promise.all([
  getAgent(agentId),
  getBalance(walletAddress),
  getPositions(agentId),
]);
 
// Avoid: Sequential when parallel is possible
const agent = await getAgent(agentId);
const balance = await getBalance(walletAddress);
const positions = await getPositions(agentId);  // ❌ slower

Comments

When to Comment

// Good: Explain WHY, not WHAT
// Use fixed stepper mode because it's more predictable for most users
const stopLossMode = 'fixed';
 
// Good: Document complex algorithms
/**
 * Calculate stop loss percentage using exponential decay.
 * 
 * Formula: keepPercent = 100 - (baseDropPercent * e^(-k * change))
 * This creates a curve that's loose at low gains and tighter at high gains.
 */
function calculateExponentialStopLoss(change: number): number {
  // ...
}
 
// Avoid: Obvious comments
// Get the agent by ID
const agent = await getAgentById(id);  // ❌ obvious

JSDoc for Public APIs

/**
 * Create a new trading agent
 * 
 * @param options - Agent creation options
 * @returns The created agent
 * @throws AgentServiceError if validation fails
 * 
 * @example
 * const agent = await agentService.create({
 *   name: 'My Agent',
 *   userId: 'user-123',
 *   tradingMode: 'simulation',
 * });
 */
async function create(options: CreateAgentOptions): Promise<Agent> {
  // ...
}

Frontend Specific

React Components

// Good: Functional components with TypeScript
interface AgentCardProps {
  agent: Agent;
  onSelect: (id: string) => void;
}
 
export function AgentCard({ agent, onSelect }: AgentCardProps) {
  return (
    <div onClick={() => onSelect(agent.id)}>
      {agent.name}
    </div>
  );
}

Hooks

// Custom hooks prefixed with 'use'
function useAgents() {
  return useQuery({
    queryKey: ['agents'],
    queryFn: agentsService.list,
  });
}
 
function useAgentSelection() {
  const [selectedId, setSelectedId] = useState<string | null>(null);
  // ...
}

Styling

// Use Tailwind CSS classes
<div className="flex items-center gap-4 p-4 bg-card rounded-lg">
  
// Use cn() for conditional classes
<button className={cn(
  "px-4 py-2 rounded",
  isActive && "bg-primary text-white",
  isDisabled && "opacity-50 cursor-not-allowed"
)}>