686 lines
19 KiB
Markdown
686 lines
19 KiB
Markdown
|
|
# Simulation Engine Specification
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
This document specifies the optional simulation engine for the ISO-20022 Combo Flow system. The simulation engine provides dry-run execution logic, gas estimation, slippage calculation, liquidity checks, failure prediction, and result presentation. It is toggleable for advanced users per requirement 2b.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Simulation Engine Architecture
|
||
|
|
|
||
|
|
### High-Level Design
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────────────────────┐
|
||
|
|
│ Combo Builder UI │
|
||
|
|
│ [Simulation Toggle: ON/OFF] │
|
||
|
|
└────────────────────────┬────────────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────────────────────────┐
|
||
|
|
│ Simulation Engine API │
|
||
|
|
│ POST /api/plans/{planId}/simulate │
|
||
|
|
└──────────────┬──────────────────────────────┬───────────────┘
|
||
|
|
│ │
|
||
|
|
▼ ▼
|
||
|
|
┌──────────────────┐ ┌──────────────────┐
|
||
|
|
│ DLT Simulator │ │ Fiat Simulator │
|
||
|
|
│ │ │ │
|
||
|
|
│ • Gas Estimation│ │ • Bank Routing │
|
||
|
|
│ • Slippage Calc │ │ • Fee Calculation│
|
||
|
|
│ • Liquidity Check│ │ • Settlement Time│
|
||
|
|
└──────────────────┘ └──────────────────┘
|
||
|
|
│ │
|
||
|
|
▼ ▼
|
||
|
|
┌──────────────────┐ ┌──────────────────┐
|
||
|
|
│ Price Oracles │ │ Bank APIs │
|
||
|
|
│ (On-Chain) │ │ (Off-Chain) │
|
||
|
|
└──────────────────┘ └──────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. API Specification
|
||
|
|
|
||
|
|
### Endpoint: `POST /api/plans/{planId}/simulate`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface SimulationRequest {
|
||
|
|
planId: string;
|
||
|
|
options?: {
|
||
|
|
includeGasEstimate?: boolean; // Default: true
|
||
|
|
includeSlippageAnalysis?: boolean; // Default: true
|
||
|
|
includeLiquidityCheck?: boolean; // Default: true
|
||
|
|
includeBankRouting?: boolean; // Default: true (for fiat steps)
|
||
|
|
chainId?: number; // Default: current chain
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
interface SimulationResponse {
|
||
|
|
planId: string;
|
||
|
|
status: 'SUCCESS' | 'FAILURE' | 'PARTIAL';
|
||
|
|
steps: SimulationStepResult[];
|
||
|
|
summary: {
|
||
|
|
gasEstimate: number;
|
||
|
|
estimatedCost: number; // USD
|
||
|
|
totalSlippage: number; // Percentage
|
||
|
|
executionTime: number; // Seconds
|
||
|
|
};
|
||
|
|
slippageAnalysis: SlippageAnalysis;
|
||
|
|
liquidityCheck: LiquidityCheck;
|
||
|
|
warnings: string[];
|
||
|
|
errors: string[];
|
||
|
|
timestamp: string;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Response Structure
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface SimulationStepResult {
|
||
|
|
stepIndex: number;
|
||
|
|
stepType: 'borrow' | 'swap' | 'repay' | 'pay';
|
||
|
|
status: 'SUCCESS' | 'FAILURE' | 'WARNING';
|
||
|
|
message: string;
|
||
|
|
estimatedOutput?: {
|
||
|
|
token: string;
|
||
|
|
amount: number;
|
||
|
|
};
|
||
|
|
gasEstimate?: number;
|
||
|
|
slippage?: number;
|
||
|
|
liquidityStatus?: 'SUFFICIENT' | 'INSUFFICIENT' | 'LOW';
|
||
|
|
bankRouting?: {
|
||
|
|
estimatedTime: number; // Minutes
|
||
|
|
fee: number;
|
||
|
|
currency: string;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
interface SlippageAnalysis {
|
||
|
|
expectedSlippage: number; // Percentage
|
||
|
|
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH';
|
||
|
|
liquidityDepth: number; // Total liquidity in pool
|
||
|
|
priceImpact: number; // Percentage
|
||
|
|
warnings: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
interface LiquidityCheck {
|
||
|
|
sufficient: boolean;
|
||
|
|
poolDepth: number;
|
||
|
|
requiredAmount: number;
|
||
|
|
availableAmount: number;
|
||
|
|
warnings: string[];
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Dry-Run Execution Logic
|
||
|
|
|
||
|
|
### Step-by-Step Simulation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class SimulationEngine {
|
||
|
|
async simulatePlan(plan: Plan, options: SimulationOptions): Promise<SimulationResponse> {
|
||
|
|
const results: SimulationStepResult[] = [];
|
||
|
|
let cumulativeGas = 0;
|
||
|
|
let totalSlippage = 0;
|
||
|
|
const warnings: string[] = [];
|
||
|
|
const errors: string[] = [];
|
||
|
|
|
||
|
|
// Simulate each step sequentially
|
||
|
|
for (let i = 0; i < plan.steps.length; i++) {
|
||
|
|
const step = plan.steps[i];
|
||
|
|
const stepResult = await this.simulateStep(step, i, plan, options);
|
||
|
|
|
||
|
|
results.push(stepResult);
|
||
|
|
|
||
|
|
if (stepResult.status === 'FAILURE') {
|
||
|
|
errors.push(`Step ${i + 1} failed: ${stepResult.message}`);
|
||
|
|
return {
|
||
|
|
status: 'FAILURE',
|
||
|
|
steps: results,
|
||
|
|
errors,
|
||
|
|
warnings
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
if (stepResult.status === 'WARNING') {
|
||
|
|
warnings.push(`Step ${i + 1}: ${stepResult.message}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
cumulativeGas += stepResult.gasEstimate || 0;
|
||
|
|
totalSlippage += stepResult.slippage || 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Aggregate results
|
||
|
|
return {
|
||
|
|
status: 'SUCCESS',
|
||
|
|
steps: results,
|
||
|
|
summary: {
|
||
|
|
gasEstimate: cumulativeGas,
|
||
|
|
estimatedCost: this.calculateCost(cumulativeGas),
|
||
|
|
totalSlippage,
|
||
|
|
executionTime: this.estimateExecutionTime(plan)
|
||
|
|
},
|
||
|
|
slippageAnalysis: this.analyzeSlippage(results),
|
||
|
|
liquidityCheck: this.checkLiquidity(results),
|
||
|
|
warnings,
|
||
|
|
errors: []
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async simulateStep(
|
||
|
|
step: PlanStep,
|
||
|
|
index: number,
|
||
|
|
plan: Plan,
|
||
|
|
options: SimulationOptions
|
||
|
|
): Promise<SimulationStepResult> {
|
||
|
|
switch (step.type) {
|
||
|
|
case 'borrow':
|
||
|
|
return await this.simulateBorrow(step, index);
|
||
|
|
case 'swap':
|
||
|
|
return await this.simulateSwap(step, index, options);
|
||
|
|
case 'repay':
|
||
|
|
return await this.simulateRepay(step, index);
|
||
|
|
case 'pay':
|
||
|
|
return await this.simulatePay(step, index, options);
|
||
|
|
default:
|
||
|
|
return {
|
||
|
|
stepIndex: index,
|
||
|
|
stepType: step.type,
|
||
|
|
status: 'FAILURE',
|
||
|
|
message: 'Unknown step type'
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### DeFi Step Simulation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
async simulateSwap(
|
||
|
|
step: SwapStep,
|
||
|
|
index: number,
|
||
|
|
options: SimulationOptions
|
||
|
|
): Promise<SimulationStepResult> {
|
||
|
|
// 1. Get current price from oracle
|
||
|
|
const currentPrice = await this.priceOracle.getPrice(step.from, step.to);
|
||
|
|
|
||
|
|
// 2. Calculate slippage
|
||
|
|
const slippage = await this.calculateSlippage(step.from, step.to, step.amount);
|
||
|
|
|
||
|
|
// 3. Check liquidity
|
||
|
|
const liquidity = await this.liquidityChecker.check(step.from, step.to, step.amount);
|
||
|
|
|
||
|
|
// 4. Estimate gas
|
||
|
|
const gasEstimate = await this.gasEstimator.estimateSwap(step.from, step.to, step.amount);
|
||
|
|
|
||
|
|
// 5. Calculate expected output
|
||
|
|
const expectedOutput = step.amount * currentPrice * (1 - slippage / 100);
|
||
|
|
|
||
|
|
// 6. Validate minimum receive
|
||
|
|
if (step.minRecv && expectedOutput < step.minRecv) {
|
||
|
|
return {
|
||
|
|
stepIndex: index,
|
||
|
|
stepType: 'swap',
|
||
|
|
status: 'FAILURE',
|
||
|
|
message: `Expected output ${expectedOutput} is below minimum ${step.minRecv}`,
|
||
|
|
estimatedOutput: { token: step.to, amount: expectedOutput },
|
||
|
|
slippage,
|
||
|
|
liquidityStatus: liquidity.status
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
stepIndex: index,
|
||
|
|
stepType: 'swap',
|
||
|
|
status: liquidity.sufficient ? 'SUCCESS' : 'WARNING',
|
||
|
|
message: liquidity.sufficient ? 'Swap would succeed' : 'Low liquidity warning',
|
||
|
|
estimatedOutput: { token: step.to, amount: expectedOutput },
|
||
|
|
gasEstimate,
|
||
|
|
slippage,
|
||
|
|
liquidityStatus: liquidity.status
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Fiat Step Simulation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
async simulatePay(
|
||
|
|
step: PayStep,
|
||
|
|
index: number,
|
||
|
|
options: SimulationOptions
|
||
|
|
): Promise<SimulationStepResult> {
|
||
|
|
// 1. Validate IBAN
|
||
|
|
if (!this.validateIBAN(step.beneficiary.IBAN)) {
|
||
|
|
return {
|
||
|
|
stepIndex: index,
|
||
|
|
stepType: 'pay',
|
||
|
|
status: 'FAILURE',
|
||
|
|
message: 'Invalid IBAN format'
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Get bank routing info
|
||
|
|
const routing = await this.bankRouter.getRouting(step.beneficiary.IBAN, step.asset);
|
||
|
|
|
||
|
|
// 3. Calculate fees
|
||
|
|
const fee = await this.feeCalculator.calculateFiatFee(step.amount, step.asset, routing);
|
||
|
|
|
||
|
|
// 4. Estimate settlement time
|
||
|
|
const settlementTime = await this.settlementEstimator.estimate(step.asset, routing);
|
||
|
|
|
||
|
|
return {
|
||
|
|
stepIndex: index,
|
||
|
|
stepType: 'pay',
|
||
|
|
status: 'SUCCESS',
|
||
|
|
message: 'Payment would be processed',
|
||
|
|
bankRouting: {
|
||
|
|
estimatedTime: settlementTime,
|
||
|
|
fee,
|
||
|
|
currency: step.asset
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Gas Estimation
|
||
|
|
|
||
|
|
### Gas Estimation Strategy
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class GasEstimator {
|
||
|
|
async estimateSwap(tokenIn: string, tokenOut: string, amount: number): Promise<number> {
|
||
|
|
// Base gas for swap
|
||
|
|
const baseGas = 150000;
|
||
|
|
|
||
|
|
// Additional gas for complex routing
|
||
|
|
const routingGas = await this.estimateRoutingGas(tokenIn, tokenOut);
|
||
|
|
|
||
|
|
// Gas for token approvals (if needed)
|
||
|
|
const approvalGas = await this.estimateApprovalGas(tokenIn);
|
||
|
|
|
||
|
|
return baseGas + routingGas + approvalGas;
|
||
|
|
}
|
||
|
|
|
||
|
|
async estimateBorrow(asset: string, amount: number): Promise<number> {
|
||
|
|
// Base gas for borrow
|
||
|
|
const baseGas = 200000;
|
||
|
|
|
||
|
|
// Gas for collateral check
|
||
|
|
const collateralGas = 50000;
|
||
|
|
|
||
|
|
// Gas for LTV calculation
|
||
|
|
const ltvGas = 30000;
|
||
|
|
|
||
|
|
return baseGas + collateralGas + ltvGas;
|
||
|
|
}
|
||
|
|
|
||
|
|
async estimateFullPlan(plan: Plan): Promise<number> {
|
||
|
|
let totalGas = 21000; // Base transaction gas
|
||
|
|
|
||
|
|
for (const step of plan.steps) {
|
||
|
|
switch (step.type) {
|
||
|
|
case 'borrow':
|
||
|
|
totalGas += await this.estimateBorrow(step.asset, step.amount);
|
||
|
|
break;
|
||
|
|
case 'swap':
|
||
|
|
totalGas += await this.estimateSwap(step.from, step.to, step.amount);
|
||
|
|
break;
|
||
|
|
case 'repay':
|
||
|
|
totalGas += 100000; // Standard repay gas
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add handler overhead
|
||
|
|
totalGas += 50000;
|
||
|
|
|
||
|
|
return totalGas;
|
||
|
|
}
|
||
|
|
|
||
|
|
calculateCost(gas: number, gasPrice: number): number {
|
||
|
|
// gasPrice in gwei, convert to ETH then USD
|
||
|
|
const ethCost = (gas * gasPrice * 1e9) / 1e18;
|
||
|
|
const usdCost = ethCost * await this.getETHPrice();
|
||
|
|
return usdCost;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Slippage Calculation
|
||
|
|
|
||
|
|
### Slippage Calculation Logic
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class SlippageCalculator {
|
||
|
|
async calculateSlippage(
|
||
|
|
tokenIn: string,
|
||
|
|
tokenOut: string,
|
||
|
|
amountIn: number
|
||
|
|
): Promise<number> {
|
||
|
|
// Get current pool reserves
|
||
|
|
const reserves = await this.getPoolReserves(tokenIn, tokenOut);
|
||
|
|
|
||
|
|
// Calculate price impact using constant product formula (x * y = k)
|
||
|
|
const priceImpact = this.calculatePriceImpact(
|
||
|
|
reserves.tokenIn,
|
||
|
|
reserves.tokenOut,
|
||
|
|
amountIn
|
||
|
|
);
|
||
|
|
|
||
|
|
// Add fixed fee (e.g., 0.3% for Uniswap)
|
||
|
|
const protocolFee = 0.3;
|
||
|
|
|
||
|
|
// Total slippage = price impact + protocol fee
|
||
|
|
const totalSlippage = priceImpact + protocolFee;
|
||
|
|
|
||
|
|
return totalSlippage;
|
||
|
|
}
|
||
|
|
|
||
|
|
calculatePriceImpact(
|
||
|
|
reserveIn: number,
|
||
|
|
reserveOut: number,
|
||
|
|
amountIn: number
|
||
|
|
): number {
|
||
|
|
// Constant product formula: (x + Δx) * (y - Δy) = x * y
|
||
|
|
// Solving for Δy: Δy = (y * Δx) / (x + Δx)
|
||
|
|
const amountOut = (reserveOut * amountIn) / (reserveIn + amountIn);
|
||
|
|
const priceBefore = reserveOut / reserveIn;
|
||
|
|
const priceAfter = (reserveOut - amountOut) / (reserveIn + amountIn);
|
||
|
|
const priceImpact = ((priceBefore - priceAfter) / priceBefore) * 100;
|
||
|
|
|
||
|
|
return priceImpact;
|
||
|
|
}
|
||
|
|
|
||
|
|
analyzeSlippage(results: SimulationStepResult[]): SlippageAnalysis {
|
||
|
|
const swapSteps = results.filter(r => r.stepType === 'swap');
|
||
|
|
const totalSlippage = swapSteps.reduce((sum, r) => sum + (r.slippage || 0), 0);
|
||
|
|
const avgSlippage = totalSlippage / swapSteps.length;
|
||
|
|
|
||
|
|
let riskLevel: 'LOW' | 'MEDIUM' | 'HIGH';
|
||
|
|
if (avgSlippage < 0.5) {
|
||
|
|
riskLevel = 'LOW';
|
||
|
|
} else if (avgSlippage < 2.0) {
|
||
|
|
riskLevel = 'MEDIUM';
|
||
|
|
} else {
|
||
|
|
riskLevel = 'HIGH';
|
||
|
|
}
|
||
|
|
|
||
|
|
const warnings: string[] = [];
|
||
|
|
if (avgSlippage > 1.0) {
|
||
|
|
warnings.push(`High slippage expected: ${avgSlippage.toFixed(2)}%`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
expectedSlippage: avgSlippage,
|
||
|
|
riskLevel,
|
||
|
|
liquidityDepth: 0, // Aggregate from steps
|
||
|
|
priceImpact: avgSlippage,
|
||
|
|
warnings
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. Liquidity Checks
|
||
|
|
|
||
|
|
### Liquidity Check Logic
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class LiquidityChecker {
|
||
|
|
async check(
|
||
|
|
tokenIn: string,
|
||
|
|
tokenOut: string,
|
||
|
|
amountIn: number
|
||
|
|
): Promise<LiquidityCheck> {
|
||
|
|
// Get pool liquidity
|
||
|
|
const pool = await this.getPool(tokenIn, tokenOut);
|
||
|
|
const availableLiquidity = pool.reserveOut;
|
||
|
|
|
||
|
|
// Calculate required output
|
||
|
|
const price = await this.getPrice(tokenIn, tokenOut);
|
||
|
|
const requiredOutput = amountIn * price;
|
||
|
|
|
||
|
|
// Check if sufficient
|
||
|
|
const sufficient = availableLiquidity >= requiredOutput * 1.1; // 10% buffer
|
||
|
|
|
||
|
|
const warnings: string[] = [];
|
||
|
|
if (!sufficient) {
|
||
|
|
warnings.push(`Insufficient liquidity: need ${requiredOutput}, have ${availableLiquidity}`);
|
||
|
|
} else if (availableLiquidity < requiredOutput * 1.5) {
|
||
|
|
warnings.push(`Low liquidity: ${((availableLiquidity / requiredOutput) * 100).toFixed(1)}% buffer`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
sufficient,
|
||
|
|
poolDepth: availableLiquidity,
|
||
|
|
requiredAmount: requiredOutput,
|
||
|
|
availableAmount: availableLiquidity,
|
||
|
|
warnings
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. Failure Prediction
|
||
|
|
|
||
|
|
### Failure Prediction Logic
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class FailurePredictor {
|
||
|
|
async predictFailures(plan: Plan): Promise<string[]> {
|
||
|
|
const failures: string[] = [];
|
||
|
|
|
||
|
|
// Check step dependencies
|
||
|
|
for (let i = 0; i < plan.steps.length; i++) {
|
||
|
|
const step = plan.steps[i];
|
||
|
|
|
||
|
|
// Check if previous step outputs are sufficient
|
||
|
|
if (i > 0) {
|
||
|
|
const prevStep = plan.steps[i - 1];
|
||
|
|
const prevOutput = await this.getStepOutput(prevStep);
|
||
|
|
|
||
|
|
if (step.type === 'swap' && step.amount > prevOutput.amount) {
|
||
|
|
failures.push(`Step ${i + 1}: Insufficient input from previous step`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check step-specific validations
|
||
|
|
if (step.type === 'borrow') {
|
||
|
|
const canBorrow = await this.checkBorrowCapacity(step.asset, step.amount);
|
||
|
|
if (!canBorrow) {
|
||
|
|
failures.push(`Step ${i + 1}: Cannot borrow ${step.amount} ${step.asset}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (step.type === 'pay') {
|
||
|
|
const isValidIBAN = this.validateIBAN(step.beneficiary.IBAN);
|
||
|
|
if (!isValidIBAN) {
|
||
|
|
failures.push(`Step ${i + 1}: Invalid IBAN`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check recursion depth
|
||
|
|
const borrowCount = plan.steps.filter(s => s.type === 'borrow').length;
|
||
|
|
if (borrowCount - 1 > plan.maxRecursion) {
|
||
|
|
failures.push(`Recursion depth ${borrowCount - 1} exceeds maximum ${plan.maxRecursion}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check LTV
|
||
|
|
const totalBorrowed = plan.steps
|
||
|
|
.filter(s => s.type === 'borrow')
|
||
|
|
.reduce((sum, s) => sum + (s as BorrowStep).amount, 0);
|
||
|
|
const totalCollateral = await this.getTotalCollateral();
|
||
|
|
const ltv = totalBorrowed / totalCollateral;
|
||
|
|
|
||
|
|
if (ltv > plan.maxLTV) {
|
||
|
|
failures.push(`LTV ${ltv} exceeds maximum ${plan.maxLTV}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return failures;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. Result Presentation Format
|
||
|
|
|
||
|
|
### UI Presentation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Simulation Results Component
|
||
|
|
const SimulationResults = ({ results }: { results: SimulationResponse }) => {
|
||
|
|
return (
|
||
|
|
<div className="simulation-results">
|
||
|
|
<h2>Simulation Results</h2>
|
||
|
|
|
||
|
|
{/* Status */}
|
||
|
|
<StatusBadge status={results.status} />
|
||
|
|
|
||
|
|
{/* Summary */}
|
||
|
|
<div className="summary">
|
||
|
|
<div>Gas Estimate: {results.summary.gasEstimate.toLocaleString()}</div>
|
||
|
|
<div>Estimated Cost: ${results.summary.estimatedCost.toFixed(2)}</div>
|
||
|
|
<div>Total Slippage: {results.summary.totalSlippage.toFixed(2)}%</div>
|
||
|
|
<div>Execution Time: ~{results.summary.executionTime}s</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Step-by-Step Results */}
|
||
|
|
<div className="steps">
|
||
|
|
{results.steps.map((step, i) => (
|
||
|
|
<StepResultCard key={i} step={step} />
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Warnings */}
|
||
|
|
{results.warnings.length > 0 && (
|
||
|
|
<WarningPanel warnings={results.warnings} />
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Errors */}
|
||
|
|
{results.errors.length > 0 && (
|
||
|
|
<ErrorPanel errors={results.errors} />
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Actions */}
|
||
|
|
<div className="actions">
|
||
|
|
<Button onClick={onRunAgain}>Run Simulation Again</Button>
|
||
|
|
<Button onClick={onProceed} disabled={results.status === 'FAILURE'}>
|
||
|
|
Proceed to Sign
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. Optional Toggle Implementation
|
||
|
|
|
||
|
|
### Frontend Toggle
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Builder UI with optional simulation toggle
|
||
|
|
const BuilderPage = () => {
|
||
|
|
const [simulationEnabled, setSimulationEnabled] = useState(false);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div>
|
||
|
|
{/* Summary Panel */}
|
||
|
|
<SummaryPanel>
|
||
|
|
<Checkbox
|
||
|
|
checked={simulationEnabled}
|
||
|
|
onChange={(e) => setSimulationEnabled(e.target.checked)}
|
||
|
|
label="Enable Simulation (Advanced)"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{simulationEnabled && (
|
||
|
|
<Button onClick={handleSimulate}>Simulate</Button>
|
||
|
|
)}
|
||
|
|
</SummaryPanel>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### Backend Handling
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Backend respects simulation toggle
|
||
|
|
if (simulationEnabled && user.isAdvanced) {
|
||
|
|
// Show simulation button
|
||
|
|
// Allow simulation requests
|
||
|
|
} else {
|
||
|
|
// Hide simulation button
|
||
|
|
// Simulation still available via API for advanced users
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10. Performance Requirements
|
||
|
|
|
||
|
|
### Response Time
|
||
|
|
- **Simulation Time**: < 5 seconds for typical workflows
|
||
|
|
- **Gas Estimation**: < 1 second per step
|
||
|
|
- **Slippage Calculation**: < 500ms per swap
|
||
|
|
- **Liquidity Check**: < 1 second per check
|
||
|
|
|
||
|
|
### Caching
|
||
|
|
- Cache price oracle data for 30 seconds
|
||
|
|
- Cache liquidity data for 10 seconds
|
||
|
|
- Cache gas estimates for 60 seconds
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11. Testing Requirements
|
||
|
|
|
||
|
|
### Unit Tests
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
describe('SimulationEngine', () => {
|
||
|
|
it('should simulate swap step', async () => {
|
||
|
|
const result = await engine.simulateStep(swapStep, 0);
|
||
|
|
expect(result.status).toBe('SUCCESS');
|
||
|
|
expect(result.slippage).toBeLessThan(1.0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should predict failures', async () => {
|
||
|
|
const failures = await predictor.predictFailures(invalidPlan);
|
||
|
|
expect(failures.length).toBeGreaterThan(0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
describe('Simulation API', () => {
|
||
|
|
it('should return simulation results', async () => {
|
||
|
|
const response = await api.simulatePlan(planId);
|
||
|
|
expect(response.status).toBe('SUCCESS');
|
||
|
|
expect(response.steps.length).toBe(plan.steps.length);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Document Version**: 1.0
|
||
|
|
**Last Updated**: 2025-01-15
|
||
|
|
**Author**: Engineering Team
|
||
|
|
|