Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/simulator/dto/simulation.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
IsArray,
IsEnum,
IsInt,
IsNumber,
IsOptional,
IsPositive,
IsString,
Min,
Max,
} from "class-validator";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { SupportedChain } from "../entities/simulation.entity";

export class CreateSimulationDto {
@ApiProperty({
enum: SupportedChain,
default: SupportedChain.ETHEREUM,
description: "Target chain to fork",
})
@IsEnum(SupportedChain)
chain: SupportedChain;

@ApiProperty({ description: "Block number to fork from (0 = latest)" })
@IsInt()
@Min(0)
forkBlockNumber: number;

@ApiProperty({ description: "Number of blocks to simulate", minimum: 1, maximum: 1000 })
@IsInt()
@IsPositive()
@Max(1000)
blocksToSimulate: number;

@ApiPropertyOptional({
description: "Time-scale multiplier — simulate N blocks per real second (default 1). Higher values fast-forward.",
default: 1,
})
@IsOptional()
@IsNumber()
@IsPositive()
timeScaleFactor?: number;
}

export class RunSimulationDto {
@ApiPropertyOptional({ description: "Agent addresses to track during simulation" })
@IsOptional()
@IsString({ each: true })
agentAddresses?: string[];

@ApiPropertyOptional({ description: "Specific tx hashes to replay from historical chain data" })
@IsOptional()
@IsArray()
@IsString({ each: true })
replayTxHashes?: string[];
}
74 changes: 74 additions & 0 deletions src/simulator/entities/simulation.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";

export enum SimulationStatus {
PENDING = "PENDING",
RUNNING = "RUNNING",
COMPLETED = "COMPLETED",
FAILED = "FAILED",
}

export enum SupportedChain {
ETHEREUM = "ethereum",
POLYGON = "polygon",
ARBITRUM = "arbitrum",
OPTIMISM = "optimism",
}

@Entity("simulations")
export class Simulation {
@PrimaryGeneratedColumn("uuid")
id: string;

@Column()
userId: string;

@Column({ type: "varchar", default: SupportedChain.ETHEREUM })
chain: SupportedChain;

@Column({ type: "bigint" })
forkBlockNumber: number;

@Column({ type: "int", default: 0 })
blocksToSimulate: number;

@Column({ type: "varchar", default: SimulationStatus.PENDING })
status: SimulationStatus;

@Column({ type: "jsonb", nullable: true })
agentActions: Record<string, unknown>[];

@Column({ type: "jsonb", nullable: true })
gasReport: Record<string, unknown>;

@Column({ type: "jsonb", nullable: true })
comparisonReport: Record<string, unknown>;

@Column({ type: "text", nullable: true })
errorMessage: string;

@Column({ type: "int", default: 0 })
blocksProcessed: number;

@Column({ type: "bigint", nullable: true })
durationMs: number;

/** Speed multiplier for time-scaled simulation (e.g. 10 = 10x faster than real-time) */
@Column({ type: "float", default: 1 })
timeScaleFactor: number;

/** Specific transaction hashes to replay during simulation */
@Column({ type: "jsonb", nullable: true })
replayTxHashes: string[];

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
84 changes: 84 additions & 0 deletions src/simulator/simulator.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
Controller,
Post,
Get,
Delete,
Body,
Param,
UseGuards,
Request,
HttpCode,
HttpStatus,
Logger,
ParseUUIDPipe,
} from "@nestjs/common";
import {
ApiTags,
ApiBearerAuth,
ApiOperation,
ApiResponse,
} from "@nestjs/swagger";
import { SimulatorService } from "./simulator.service";
import { CreateSimulationDto, RunSimulationDto } from "./dto/simulation.dto";
import { JwtAuthGuard } from "src/core/auth/jwt.guard";

@ApiTags("simulator")
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller("simulator")
export class SimulatorController {
private readonly logger = new Logger(SimulatorController.name);

constructor(private readonly simulatorService: SimulatorService) {}

@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: "Create a simulation (fork from a block)" })
@ApiResponse({ status: 201, description: "Simulation created" })
create(@Request() req, @Body() dto: CreateSimulationDto) {
return this.simulatorService.createSimulation(req.user.id, dto);
}

@Post(":id/run")
@ApiOperation({ summary: "Run an existing simulation" })
run(
@Request() req,
@Param("id", ParseUUIDPipe) id: string,
@Body() dto: RunSimulationDto,
) {
return this.simulatorService.runSimulation(id, req.user.id, dto);
}

@Get()
@ApiOperation({ summary: "List all simulations for the current user" })
findAll(@Request() req) {
return this.simulatorService.findAll(req.user.id);
}

@Get(":id")
@ApiOperation({ summary: "Get a simulation by ID" })
findOne(@Request() req, @Param("id", ParseUUIDPipe) id: string) {
return this.simulatorService.findOne(id, req.user.id);
}

@Get(":id/report")
@ApiOperation({ summary: "Get simulation report (gas, comparison, actions summary)" })
getReport(@Request() req, @Param("id", ParseUUIDPipe) id: string) {
return this.simulatorService.getReport(id, req.user.id);
}

@Get(":id/export")
@ApiOperation({ summary: "Export full simulation data as JSON report" })
@ApiResponse({ status: 200, description: "Full simulation JSON export" })
exportReport(@Request() req, @Param("id", ParseUUIDPipe) id: string) {
return this.simulatorService.exportReport(id, req.user.id);
}

@Delete(":id")
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: "Delete a simulation" })
@ApiResponse({ status: 204, description: "Deleted" })
remove(@Request() req, @Param("id", ParseUUIDPipe) id: string) {
return this.simulatorService.deleteSimulation(id, req.user.id);
}
}
14 changes: 14 additions & 0 deletions src/simulator/simulator.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { ConfigModule } from "@nestjs/config";
import { SimulatorController } from "./simulator.controller";
import { SimulatorService } from "./simulator.service";
import { Simulation } from "./entities/simulation.entity";

@Module({
imports: [TypeOrmModule.forFeature([Simulation]), ConfigModule],
controllers: [SimulatorController],
providers: [SimulatorService],
exports: [SimulatorService],
})
export class SimulatorModule {}
Loading
Loading