This document provides in-depth technical information about the SolFlow application architecture, implementation details, and codebase organization. It serves as a reference for developers working on the project.
The SolFlow codebase follows a client-server architecture with clear separation of concerns:
├── client/ # Frontend React application
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── lib/ # Utility functions and services
│ │ ├── pages/ # Page components
│ │ ├── services/ # API service functions
│ │ ├── types/ # TypeScript type definitions
│ │ ├── App.tsx # Main application component
│ │ └── main.tsx # Application entry point
│ └── index.html # HTML template
├── server/ # Backend Express application
│ ├── routes/ # API route handlers
│ ├── services/ # Business logic services
│ ├── db.ts # Database connection setup
│ ├── index.ts # Server entry point
│ ├── routes.ts # Main route registration
│ ├── storage.ts # Data storage interface
│ ├── solana.ts # Solana blockchain connection
│ └── vite.ts # Vite server integration
├── shared/ # Shared code between client and server
│ └── schema.ts # Database schema and types
└── drizzle.config.ts # Drizzle ORM configuration
SolFlow implements a modern web application architecture with these key components:
The frontend follows a component-based architecture using React. It's organized into:
- Presentation Components: UI components that render data and handle user interactions
- Container Components: Connect presentation components to data sources and services
- Custom Hooks: Encapsulate and share stateful logic between components
- Services: Handle API communication and data transformation
Key design patterns used:
- Container/Presentational Pattern: Separation of data and presentation concerns
- Custom Hook Pattern: Reusable stateful logic
- Context API: Global state management for authentication and theme
- Render Props: Component composition for complex UI elements
The backend follows a route-controller-service architecture:
- Routes: Define API endpoints and handle HTTP requests
- Services: Contain business logic and data manipulation
- Storage Interface: Abstracts database operations
- Database Layer: Handles data persistence
Key design patterns used:
- Repository Pattern: Abstraction for data access
- Dependency Injection: Services receive dependencies through constructors
- Middleware Pattern: Request processing pipeline
- Adapter Pattern: Integration with external systems (Solana RPC)
- User interacts with the UI
- React components update state and trigger hooks
- Hooks make API calls through services
- Server routes receive requests
- Server services process requests
- Storage interface interacts with the database
- Response flows back to the client
- React updates the UI
The database schema consists of several core entities:
// Users table
export const users = pgTable("users", {
id: serial("id").primaryKey(),
username: text("username").notNull().unique(),
email: text("email").unique(),
passwordHash: text("password_hash"),
createdAt: timestamp("created_at").defaultNow(),
lastLogin: timestamp("last_login"),
});
// User sessions
export const sessions = pgTable("sessions", {
sid: text("sid").primaryKey(),
sess: jsonb("sess").notNull(),
expire: timestamp("expire").notNull(),
});// Wallets table
export const wallets = pgTable("wallets", {
id: serial("id").primaryKey(),
address: text("address").notNull().unique(),
label: text("label"),
type: text("type").default("wallet"),
firstSeen: timestamp("first_seen").defaultNow(),
lastActivity: timestamp("last_activity"),
lastFetched: timestamp("last_fetched"),
riskScore: integer("risk_score"),
isMonitored: boolean("is_monitored").default(false),
userId: integer("user_id").references(() => users.id),
});
// Transactions table
export const transactions = pgTable("transactions", {
id: serial("id").primaryKey(),
signature: text("signature").notNull().unique(),
fromAddress: text("from_address").notNull(),
toAddress: text("to_address").notNull(),
amount: numeric("amount"),
timestamp: timestamp("timestamp").notNull(),
transactionType: text("transaction_type"),
program: text("program"),
status: text("status").default("confirmed"),
blockNumber: bigint("block_number", { mode: "number" }),
slot: bigint("slot", { mode: "number" }),
memo: text("memo"),
raw: jsonb("raw"),
});// Visualizations table
export const visualizations = pgTable("visualizations", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
walletAddress: text("wallet_address").notNull(),
config: jsonb("config").notNull(),
description: text("description"),
userId: integer("user_id").references(() => users.id),
dateRange: jsonb("date_range"),
amountRange: jsonb("amount_range"),
transactionTypes: jsonb("transaction_types"),
programs: jsonb("programs"),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
isPublic: boolean("is_public").default(false),
shareToken: text("share_token").unique(),
lastViewed: timestamp("last_viewed"),
});
// Teams table
export const teams = pgTable("teams", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
description: text("description"),
createdById: integer("created_by_id").references(() => users.id),
createdAt: timestamp("created_at").defaultNow(),
});
// Team members
export const teamMembers = pgTable("team_members", {
id: serial("id").primaryKey(),
teamId: integer("team_id").references(() => teams.id).notNull(),
userId: integer("user_id").references(() => users.id).notNull(),
role: text("role").default("member").notNull(),
addedAt: timestamp("added_at").defaultNow(),
lastActive: timestamp("last_active"),
});
// Team visualizations
export const teamVisualizations = pgTable("team_visualizations", {
id: serial("id").primaryKey(),
teamId: integer("team_id").references(() => teams.id).notNull(),
visualizationId: integer("visualization_id").references(() => visualizations.id).notNull(),
addedAt: timestamp("added_at").defaultNow(),
});
// Comments/Annotations
export const comments = pgTable("comments", {
id: serial("id").primaryKey(),
visualizationId: integer("visualization_id").references(() => visualizations.id).notNull(),
userId: integer("user_id").references(() => users.id).notNull(),
content: text("content").notNull(),
referencedNodeAddress: text("referenced_node_address"),
referencedTransactionSignature: text("referenced_transaction_signature"),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at"),
parentId: integer("parent_id").references(() => comments.id),
});// Entity labels
export const entities = pgTable("entities", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
type: text("type").notNull(),
description: text("description"),
logo: text("logo"),
website: text("website"),
userId: integer("user_id").references(() => users.id),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at"),
isVerified: boolean("is_verified").default(false),
});
// Wallet-entity relationships
export const walletEntityRelations = pgTable("wallet_entity_relations", {
id: serial("id").primaryKey(),
walletId: integer("wallet_id").references(() => wallets.id).notNull(),
entityId: integer("entity_id").references(() => entities.id).notNull(),
confidence: numeric("confidence"),
source: text("source"),
addedAt: timestamp("added_at").defaultNow(),
addedById: integer("added_by_id").references(() => users.id),
});
// Activity patterns
export const activityPatterns = pgTable("activity_patterns", {
id: serial("id").primaryKey(),
walletId: integer("wallet_id").references(() => wallets.id).notNull(),
patternType: text("pattern_type").notNull(),
description: text("description"),
confidence: numeric("confidence"),
detectedAt: timestamp("detected_at").defaultNow(),
lastSeen: timestamp("last_seen"),
occurrences: integer("occurrences").default(1),
metadata: jsonb("metadata"),
});The visualization system centers around a few core components:
- FlowVisualization: Main graph visualization component using D3.js
- useVisualization: Custom hook that encapsulates D3 logic for the visualization
- FilterSidebar: Controls for filtering the visualization
- TransactionTimeline: Chronological view of transactions
- EntityClustering: Visual representation of related wallet clusters
// Core visualization component props
interface FlowVisualizationProps {
graph: VisualizationGraph;
onNodeClick?: (node: WalletNode) => void;
onEdgeClick?: (edge: TransactionEdge) => void;
selectedNode?: WalletNode | null;
selectedEdge?: TransactionEdge | null;
filteredWalletAddress?: string | null;
svgRef?: RefObject<SVGSVGElement>;
}The FlowVisualization component renders an SVG container and delegates the D3 rendering logic to the useVisualization hook. It handles:
- Layout selection (force-directed, radial, hierarchical)
- Zoom and pan controls
- Node and edge selection
- Export and sharing functionality
This custom hook handles the D3.js integration:
- Creates and updates the force-directed graph
- Implements different layout algorithms
- Handles zoom and pan behavior
- Manages node and edge styling
- Implements interactive behaviors (click, double-click)
The collaboration system consists of:
- TeamCollaboration: Team management interface
- VisualizationAnnotation: Annotation and commenting system
- WebSocket Integration: Real-time updates and presence tracking
The WebSocket system enables real-time collaboration through:
- Server-side WebSocket Hub: Manages connections and broadcasts updates
- Client-side WebSocket Consumer: Connects to the server and handles updates
- Visualization-specific Channels: Group users by visualization ID
Key WebSocket message types:
join-visualization: User joins a visualization sessionleave-visualization: User leaves a visualization sessioncursor-update: User cursor position updatesactive-users: List of active users in a visualizationnew-annotation: Notification of new annotationannotation-update: Notification of updated annotation
The application uses TanStack Query (React Query) for data fetching and caching:
- QueryClient Configuration: Set up in
client/src/lib/queryClient.ts - API Request Utility: Standardized fetch wrapper with error handling
- Custom Query Hooks: Component-specific data fetching hooks
Example query hook:
function useWallet({ address }: { address: string }) {
const { data, isLoading, error } = useQuery({
queryKey: ['/api/wallets', address],
enabled: !!address,
});
return {
wallet: data?.wallet,
transactions: data?.transactions,
isLoading,
error,
isValidAddress: !!data
};
}The application uses a combination of state management approaches:
- Local Component State: For UI-specific state
- TanStack Query Cache: For server data
- Context API: For application-wide state (authentication, theme)
- URL State: For shareable application state (current wallet, filters)
-
Login Process:
- Client submits credentials to
/api/auth/login - Server validates credentials with Passport.js
- On success, user session is established
- User data is returned to client
- Client submits credentials to
-
Session Management:
- Express-session with PostgreSQL session store
- Session data stored in database
- Session cookie sent with each request
-
Authorization Checks:
- Route middleware validates user authentication
- Role-based checks for team access
- Ownership validation for resources
// Authentication middleware
const isAuthenticated = (req: Request, res: Response, next: Function) => {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ message: "Not authenticated" });
};
// Resource ownership middleware
const isVisualizationOwner = async (req: Request, res: Response, next: Function) => {
const visualizationId = parseInt(req.params.id);
const visualization = await storage.getVisualization(visualizationId);
if (!visualization) {
return res.status(404).json({ message: "Visualization not found" });
}
if (visualization.userId !== req.user.id) {
return res.status(403).json({ message: "Not authorized" });
}
next();
};The application integrates with the Solana blockchain through:
-
Connection Setup:
import { Connection } from '@solana/web3.js'; export function getSolanaConnection(): Connection { const endpoint = process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com"; return new Connection(endpoint); }
-
Transaction Fetching:
async function fetchTransactionsForWallet(address: string): Promise<Transaction[]> { const connection = getSolanaConnection(); const publicKey = new PublicKey(address); // Fetch signatures const signatures = await connection.getSignaturesForAddress( publicKey, { limit: 100 } ); // Fetch transaction details const transactionDetails = await connection.getParsedTransactions( signatures.map(sig => sig.signature) ); // Transform and store transactions // ... }
-
Data Processing:
- Parse raw transaction data
- Extract sender, receiver, amounts
- Identify transaction types
- Calculate relationships between wallets
The application implements several performance optimizations:
- Data Pagination: Limit transaction fetching to manageable chunks
- Lazy Loading: Defer loading of visualization data until needed
- Memoization: Cache expensive computations using useMemo and useCallback
- Virtualization: Render only visible elements in large lists
- Code Splitting: Dynamically import components when needed
- Progressive Rendering: Show core UI quickly and load details progressively
The application implements a comprehensive error handling strategy:
- Client-side Error Boundaries: Catch and display React rendering errors
- API Error Handling: Standardized error responses with appropriate status codes
- Form Validation: Client-side validation using Zod schemas
- Graceful Degradation: Fallback UI when components fail
- Error Logging: Track and report errors for debugging
The application follows a multi-layered testing approach:
- Unit Tests: Test individual functions and hooks
- Component Tests: Test UI components in isolation
- Integration Tests: Test components working together
- API Tests: Test backend endpoints
- End-to-End Tests: Test complete application flows
The application is configured for deployment on Replit:
-
Build Process:
- Vite builds optimized client assets
- Server compiled with TypeScript
- Static assets served from server/public directory
-
Environment Configuration:
- Environment variables for sensitive configuration
- Database connection parameters
- Solana RPC endpoint configuration
-
Database Migrations:
- Schema changes managed through Drizzle ORM
- Push-based migration strategy
- Clone the repository
- Install dependencies:
npm install - Set up environment variables:
DATABASE_URL=postgresql://user:password@host:port/database SOLANA_RPC_URL=https://your-solana-rpc-endpoint - Start the development server:
npm run dev
- Type Safety: Use TypeScript for all code
- Code Formatting: Follow consistent formatting with Prettier
- Linting: Enforce code quality with ESLint
- Commit Conventions: Use conventional commits
- Pull Request Process: Code reviews for all changes
The API provides these main endpoint groups:
-
Authentication:
POST /api/auth/login: Log in a userPOST /api/auth/logout: Log out the current userGET /api/auth/user: Get the current user
-
Wallets:
GET /api/wallets/:address: Get wallet detailsGET /api/wallets/:address/transactions: Get wallet transactions
-
Visualizations:
GET /api/visualizations: List user visualizationsGET /api/visualizations/:id: Get visualization detailsPOST /api/visualizations: Create a visualizationPATCH /api/visualizations/:id: Update a visualizationDELETE /api/visualizations/:id: Delete a visualization
-
Teams:
GET /api/teams: List user teamsPOST /api/teams: Create a teamGET /api/teams/:id: Get team detailsPATCH /api/teams/:id: Update a teamDELETE /api/teams/:id: Delete a teamGET /api/teams/:id/members: List team membersPOST /api/teams/:id/members/invite: Invite a team memberPATCH /api/teams/:id/members/:memberId: Update member roleDELETE /api/teams/:id/members/:memberId: Remove a member
-
Annotations:
GET /api/visualizations/:id/annotations: List annotationsPOST /api/visualizations/:id/annotations: Create an annotationPATCH /api/annotations/:id: Update an annotationDELETE /api/annotations/:id: Delete an annotationPOST /api/annotations/:id/replies: Reply to an annotation
The application processes Solana transactions in several steps:
-
Fetch Transaction Signatures: Get recent transaction signatures for a wallet
-
Fetch Transaction Details: Get parsed transaction data for each signature
-
Extract Relevant Information:
- Sender and receiver addresses
- Transaction amount and token type
- Program ID to determine transaction type
- Timestamp and block information
-
Process Instruction Data: Different instructions require specific parsing:
- System Program transfers
- Token Program transfers
- Swap operations
- NFT transactions
- DeFi interactions
-
Build Transaction Graph:
- Create nodes for wallets
- Create edges for transactions
- Calculate edge weights based on amounts
- Apply layout algorithms
The WebSocket implementation uses a simple message protocol:
interface WebSocketMessage {
type: string;
[key: string]: any;
}
// Example message types
interface JoinVisualizationMessage extends WebSocketMessage {
type: 'join-visualization';
visualizationId: number;
userId: number;
username: string;
}
interface CursorUpdateMessage extends WebSocketMessage {
type: 'cursor-update';
visualizationId: number;
userId: number;
position: { x: number; y: number };
}
interface NewAnnotationMessage extends WebSocketMessage {
type: 'new-annotation';
visualizationId: number;
annotationId: number;
userId: number;
username: string;
}These messages are serialized as JSON for transmission over the WebSocket connection.