# 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 { 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 { 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 { // 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 { // 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 { // 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 { // 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 { 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 { // 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 { // 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 { 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 (

Simulation Results

{/* Status */} {/* Summary */}
Gas Estimate: {results.summary.gasEstimate.toLocaleString()}
Estimated Cost: ${results.summary.estimatedCost.toFixed(2)}
Total Slippage: {results.summary.totalSlippage.toFixed(2)}%
Execution Time: ~{results.summary.executionTime}s
{/* Step-by-Step Results */}
{results.steps.map((step, i) => ( ))}
{/* Warnings */} {results.warnings.length > 0 && ( )} {/* Errors */} {results.errors.length > 0 && ( )} {/* Actions */}
); }; ``` --- ## 9. Optional Toggle Implementation ### Frontend Toggle ```typescript // Builder UI with optional simulation toggle const BuilderPage = () => { const [simulationEnabled, setSimulationEnabled] = useState(false); return (
{/* Summary Panel */} setSimulationEnabled(e.target.checked)} label="Enable Simulation (Advanced)" /> {simulationEnabled && ( )}
); }; ``` ### 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