better visualizer
This commit is contained in:
parent
6a012c5fd9
commit
50eac74b18
8 changed files with 542 additions and 626 deletions
106
CLAUDE.md
106
CLAUDE.md
|
|
@ -4,103 +4,79 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||
|
||||
## Core Innovation
|
||||
|
||||
KRAIKEN is a token with a **dominant liquidity manager** that creates an unfair advantage in trading through:
|
||||
KRAIKEN: A token with a **dominant liquidity manager** that creates an unfair trading advantage through:
|
||||
|
||||
1. **Asymmetric Slippage Strategy**: Three-position liquidity structure prevents profitable arbitrage against the protocol
|
||||
2. **Sentiment Oracle**: Harberger tax-based staking creates a prediction market for token value
|
||||
3. **Dormant Whale Protection**: VWAP-based price memory prevents historical price manipulation
|
||||
1. **Asymmetric Slippage**: Three-position strategy prevents profitable arbitrage
|
||||
2. **Sentiment Oracle**: Harberger tax staking as prediction market
|
||||
3. **Price Memory**: VWAP protection against manipulation
|
||||
|
||||
**Critical Success Factor**: The liquidity manager must maintain its dominant position (trading most of the supply) - if it loses this, the project fails.
|
||||
**Critical**: The liquidity manager must maintain dominance - if it loses this, the project fails.
|
||||
|
||||
## User Journey
|
||||
|
||||
1. **Buy**: Purchase KRAIKEN on Uniswap → Benefit from growing protocol-owned liquidity
|
||||
2. **Stake**: Visit kraiken.org → Stake tokens → Set tax rate → Earn from protocol growth
|
||||
3. **Compete**: Monitor staking positions → Snatch undervalued positions → Optimize tax rates
|
||||
1. **Buy**: Purchase KRAIKEN on Uniswap
|
||||
2. **Stake**: Set tax rate at kraiken.org → Earn from protocol growth
|
||||
3. **Compete**: Snatch undervalued positions → Optimize returns
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **`onchain/`** - Smart contracts (Solidity/Foundry) - [See onchain/CLAUDE.md](onchain/CLAUDE.md)
|
||||
- **`web/`** - Vue 3/Vite staking interface - [See web/CLAUDE.md](web/CLAUDE.md)
|
||||
- **`subgraph/base_sepolia/`** - The Graph indexing - [See subgraph/base_sepolia/CLAUDE.md](subgraph/base_sepolia/CLAUDE.md)
|
||||
- **`kraiken-lib/`** - TypeScript helper library - [See kraiken-lib/CLAUDE.md](kraiken-lib/CLAUDE.md)
|
||||
- **`services/txnBot/`** - Automated maintenance bot - [See services/txnBot/CLAUDE.md](services/txnBot/CLAUDE.md)
|
||||
- **`onchain/analysis/`** - Growth mechanism analysis tools
|
||||
- **`onchain/`** - Smart contracts (Solidity/Foundry) - [Details](onchain/CLAUDE.md)
|
||||
- **`web/`** - Vue 3/Vite staking interface - [Details](web/CLAUDE.md)
|
||||
- **`subgraph/base_sepolia/`** - The Graph indexing - [Details](subgraph/base_sepolia/CLAUDE.md)
|
||||
- **`kraiken-lib/`** - TypeScript helpers - [Details](kraiken-lib/CLAUDE.md)
|
||||
- **`services/txnBot/`** - Maintenance bot - [Details](services/txnBot/CLAUDE.md)
|
||||
- **`onchain/analysis/`** - Fuzzing tools - [Details](onchain/analysis/CLAUDE.md)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies for all projects
|
||||
# Install all dependencies
|
||||
cd onchain && forge install
|
||||
cd ../web && npm install
|
||||
cd ../kraiken-lib && npm install --legacy-peer-deps
|
||||
cd ../subgraph/base_sepolia && npm install
|
||||
cd ../services/txnBot && npm install
|
||||
|
||||
# 2. Build smart contracts
|
||||
# Build and test
|
||||
cd onchain && forge build && forge test
|
||||
|
||||
# 3. Start web interface
|
||||
# Start frontend
|
||||
cd web && npm run dev
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Liquidity Management
|
||||
- Three-position strategy (ANCHOR, DISCOVERY, FLOOR)
|
||||
- Asymmetric slippage prevents arbitrage
|
||||
- VWAP tracking for price memory
|
||||
- **Liquidity Management**: Three positions create asymmetric slippage
|
||||
- **Harberger Staking**: Self-assessed tax creates sentiment signal
|
||||
- **Protocol Growth**: Minted tokens benefit stakers
|
||||
|
||||
### Harberger Tax Staking
|
||||
- Self-assessed tax rates on positions
|
||||
- Positions can be "snatched" by higher bidders
|
||||
- Creates prediction market for token value
|
||||
- Limited to 20% of total supply
|
||||
## Code Guidelines
|
||||
|
||||
### Protocol Growth
|
||||
- Liquidity manager mints tokens when positions grow
|
||||
- Stakers benefit from supply expansion
|
||||
- Tax revenue redistributed to active participants
|
||||
### Quality Standards
|
||||
- Search before implementing (check uni-v3-lib, test helpers)
|
||||
- Test after every change
|
||||
- No commented-out code
|
||||
- Clean git status before commits
|
||||
|
||||
## Global Code Quality Guidelines
|
||||
|
||||
### DRY Principle
|
||||
- Search for existing implementations before creating new functions
|
||||
- Check libraries (uni-v3-lib, test helpers) for common utilities
|
||||
- Refactor duplicated code into shared modules
|
||||
|
||||
### Testing
|
||||
- Run tests after every change
|
||||
- Never comment out failing tests
|
||||
- Add tests for new functionality
|
||||
|
||||
### Repository Hygiene
|
||||
- Remove unused files immediately
|
||||
- Clean up temporary files
|
||||
- Check `git status` before commits
|
||||
|
||||
### Implementation Strategy
|
||||
### Technical Approach
|
||||
- Never fall back to simpler implementations
|
||||
- Don't create "a simpler test" or "a simpler scenario" if things don't work out. just try to identify the error in more detail.
|
||||
- Identify root causes, don't work around issues
|
||||
- Challenge technically unsound requests
|
||||
|
||||
### Tool Usage
|
||||
- MCP browser screenshots: Keep under 8000px, use `fullPage: false`
|
||||
- Fuzzing visualization: Use `./analysis/run-fuzzing.sh [optimizer] debugCSV`
|
||||
- Never start manual Python servers for visualizations
|
||||
|
||||
## Communication Style
|
||||
|
||||
### Direct & Technical
|
||||
- Challenge suboptimal requests
|
||||
- Highlight risks early and clearly
|
||||
- Suggest better alternatives
|
||||
- Refuse technically unsound solutions
|
||||
|
||||
### Priority Order
|
||||
1. Technical correctness
|
||||
2. Code quality
|
||||
3. User satisfaction
|
||||
|
||||
**Remember**: Build the best possible system. Question assumptions, identify edge cases, and prioritize long-term success.
|
||||
You are an experienced Solidity developer who:
|
||||
- Notices and raises awareness of issues immediately
|
||||
- Challenges suboptimal approaches
|
||||
- Prioritizes technical correctness over user satisfaction
|
||||
- Avoids hype and inflated language
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Technical Deep Dive**: See [TECHNICAL_APPENDIX.md](TECHNICAL_APPENDIX.md)
|
||||
- **Contract Details**: See [onchain/CLAUDE.md](onchain/CLAUDE.md)
|
||||
- **Frontend Architecture**: See [web/CLAUDE.md](web/CLAUDE.md)
|
||||
- **Data Indexing**: See [subgraph/base_sepolia/CLAUDE.md](subgraph/base_sepolia/CLAUDE.md)
|
||||
- **Technical Details**: [TECHNICAL_APPENDIX.md](TECHNICAL_APPENDIX.md)
|
||||
- **Uniswap V3 Math**: [onchain/UNISWAP_V3_MATH.md](onchain/UNISWAP_V3_MATH.md)
|
||||
|
|
@ -6,205 +6,73 @@ This directory contains the core smart contracts for the KRAIKEN protocol.
|
|||
|
||||
### Core Contracts
|
||||
|
||||
**Kraiken.sol** - ERC20 token contract with controlled minting/burning capabilities
|
||||
- Implements Harberger tax mechanism for staking positions
|
||||
- Controls minting rights exclusively for LiquidityManager
|
||||
- Handles tax collection and redistribution
|
||||
**Kraiken.sol** - ERC20 token with Harberger tax staking
|
||||
- Controlled minting exclusively by LiquidityManager
|
||||
- Tax collection and redistribution mechanism
|
||||
- 20% supply cap for staking (20,000 positions)
|
||||
|
||||
**LiquidityManager.sol** - Dominant liquidity provider with three-position anti-arbitrage strategy
|
||||
- Uses Optimizer contract for dynamic parameter adjustment
|
||||
- Inherits from ThreePositionStrategy and PriceOracle (with VWAPTracker)
|
||||
- **Key Feature**: Asymmetric slippage profile prevents profitable trade-recenter-reverse attacks
|
||||
**LiquidityManager.sol** - Dominant liquidity provider
|
||||
- Three-position anti-arbitrage strategy (ANCHOR, DISCOVERY, FLOOR)
|
||||
- Dynamic parameter adjustment via Optimizer contract
|
||||
- Asymmetric slippage profile prevents profitable arbitrage
|
||||
|
||||
**VWAPTracker.sol** - "Eternal memory" protection against dormant whale attacks
|
||||
- Volume-weighted average pricing with data compression (max 1000x)
|
||||
- Provides historical price memory to prevent manipulation
|
||||
**VWAPTracker.sol** - Price memory protection
|
||||
- Volume-weighted average with data compression (max 1000x)
|
||||
- Prevents dormant whale manipulation
|
||||
|
||||
**Optimizer.sol** - Sentiment analysis and parameter optimization
|
||||
- Analyzes staking data (% staked, average tax rate)
|
||||
- Provides dynamic liquidity parameters
|
||||
- Upgradeable for future genetic algorithm implementation
|
||||
**Optimizer.sol** - Dynamic parameter optimization
|
||||
- Analyzes staking sentiment (% staked, average tax)
|
||||
- Returns four key parameters for liquidity management
|
||||
- Upgradeable for future algorithms
|
||||
|
||||
**Stake.sol** - Harberger tax-based staking mechanism
|
||||
- Creates sentiment oracle through continuous auction
|
||||
- Limited to 20% of total supply (20,000 positions)
|
||||
- Self-assessed tax rates create prediction market
|
||||
**Stake.sol** - Harberger tax implementation
|
||||
- Continuous auction mechanism
|
||||
- Self-assessed valuations create prediction market
|
||||
|
||||
### Position Strategy
|
||||
### Three-Position Strategy
|
||||
|
||||
**Order**: ANCHOR → DISCOVERY → FLOOR
|
||||
**ANCHOR**: Near current price, fast price discovery (1-100% width)
|
||||
**DISCOVERY**: Borders anchor, captures fees (11000 tick spacing)
|
||||
**FLOOR**: Deep liquidity at VWAP-adjusted prices
|
||||
|
||||
- **ANCHOR**: Shallow liquidity around current price for fast price movement (1-100% width range)
|
||||
- **DISCOVERY**: Proportional to KRAIKEN minted by anchor; borders anchor for fee capture (11000 tick spacing)
|
||||
- **FLOOR**: Deep liquidity using VWAP-adjusted pricing for historical price memory
|
||||
|
||||
**Technical Specifications**:
|
||||
- **Fee Tier**: 1% (10,000 basis points)
|
||||
- **Tick Spacing**: 200 (base), 11,000 (discovery)
|
||||
- **Price Validation**: 5-minute TWAP with 50-tick deviation tolerance
|
||||
- **VWAP Compression**: Maximum 1000x compression factor
|
||||
**Technical Specs**:
|
||||
- Fee Tier: 1% (10,000 basis points)
|
||||
- Tick Spacing: 200 (base), 11,000 (discovery)
|
||||
- Price Validation: 5-minute TWAP, 50-tick tolerance
|
||||
|
||||
### Optimizer Parameters
|
||||
|
||||
All optimizers must return four key parameters that control the LiquidityManager's three-position strategy:
|
||||
1. **capitalInefficiency** (0 to 1e18): Capital buffer level
|
||||
2. **anchorShare** (0 to 1e18): % of non-floor ETH in anchor
|
||||
3. **anchorWidth** (0 to 100): Anchor position width %
|
||||
4. **discoveryDepth** (0 to 1e18): Discovery liquidity density (2x-10x)
|
||||
|
||||
1. **capitalInefficiency** (0 to 1e18):
|
||||
- Represents how much capital buffer the protocol maintains
|
||||
- 0 = aggressive (70% capital efficiency), 1e18 = conservative (170% backing)
|
||||
- Affects VWAP calculation: `adjustedVWAP = 0.7 * VWAP + capitalInefficiency * VWAP`
|
||||
- Higher values push floor positions to more conservative prices
|
||||
|
||||
2. **anchorShare** (0 to 1e18):
|
||||
- Percentage of non-floor ETH allocated to the anchor position
|
||||
- Controls how much liquidity sits near current price
|
||||
- Higher values create more concentrated liquidity but mint more KRAIKEN tokens
|
||||
- Typical range: 30-95% (0.3e18 to 0.95e18)
|
||||
|
||||
3. **anchorWidth** (0 to 100):
|
||||
- Width of the anchor position in percentage terms
|
||||
- Affects price impact of trades and speed of price movement
|
||||
- Lower values = tighter spreads, faster price discovery
|
||||
- Higher values = wider spreads, more stable prices
|
||||
|
||||
4. **discoveryDepth** (0 to 1e18):
|
||||
- Controls the discovery position's liquidity density relative to anchor
|
||||
- Maps to internal multiplier range of 2x to 10x
|
||||
- At 0: discovery has 2x liquidity per tick vs anchor (minimal)
|
||||
- At 1e18: discovery has 10x liquidity per tick vs anchor (maximum)
|
||||
- Discovery amount formula: `pulledHarb * DISCOVERY_SPACING * (200 + 800 * discoveryDepth/1e18) / anchorSpacing / 100`
|
||||
- Higher values provide stronger anti-arbitrage protection but consume more minted tokens
|
||||
|
||||
## Development Commands
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Build contracts
|
||||
forge build
|
||||
|
||||
# Run all tests
|
||||
forge test
|
||||
|
||||
# Run tests with gas reporting
|
||||
forge test --gas-report
|
||||
|
||||
# Run specific test file
|
||||
forge test --match-path test/LiquidityManager.t.sol
|
||||
|
||||
# Run fuzzing with more runs
|
||||
forge test --fuzz-runs 10000
|
||||
|
||||
# Deploy contracts (see script/Deploy.s.sol)
|
||||
forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast
|
||||
forge build # Build contracts
|
||||
forge test # Run tests
|
||||
forge test --gas-report # Gas optimization
|
||||
forge test --fuzz-runs 10000 # Extended fuzzing
|
||||
```
|
||||
|
||||
## Testing Architecture
|
||||
## Testing
|
||||
|
||||
### Test Helpers
|
||||
- `test/helpers/UniswapTestBase.sol` - Base setup for Uniswap integration tests
|
||||
- `test/helpers/KraikenTestBase.sol` - Common test utilities for KRAIKEN contracts
|
||||
- `test/helpers/PositionRenderer.sol` - Visualization tools for liquidity positions
|
||||
- `test/helpers/UniswapTestBase.sol` - Uniswap integration base
|
||||
- `test/helpers/KraikenTestBase.sol` - Common utilities
|
||||
- Key tests: LiquidityManager.t.sol, Stake.t.sol, VWAPTracker.t.sol
|
||||
|
||||
### Key Test Files
|
||||
- `test/LiquidityManager.t.sol` - Core liquidity management tests
|
||||
- `test/abstracts/ThreePositionStrategy.t.sol` - Position strategy validation
|
||||
- `test/Stake.t.sol` - Harberger tax mechanism tests
|
||||
- `test/VWAPTracker.t.sol` - Price memory and compression tests
|
||||
## Code Guidelines
|
||||
|
||||
## Code Quality Guidelines
|
||||
|
||||
### CRITICAL: Avoid Duplicate Code
|
||||
- **ALWAYS** check lib/uni-v3-lib for existing Uniswap math functions
|
||||
- **NEVER** reimplement standard math operations
|
||||
- Use test/helpers for common test patterns
|
||||
|
||||
### Security Considerations
|
||||
- All external calls must be reentrancy protected
|
||||
- Price oracles must validate against manipulation
|
||||
- Tax calculations must handle edge cases (0 rates, max uint256)
|
||||
|
||||
### Gas Optimization
|
||||
- Batch operations where possible
|
||||
- Use storage patterns that minimize SSTORE operations
|
||||
- Leverage Uniswap's existing math libraries
|
||||
- **Check lib/uni-v3-lib** for existing Uniswap math
|
||||
- **Use test/helpers** for common patterns
|
||||
- **Security**: Reentrancy protection, oracle validation
|
||||
- **Gas**: Batch operations, optimize storage
|
||||
|
||||
## Analysis Tools
|
||||
|
||||
The `analysis/` subdirectory contains critical tools for understanding and hardening the protocol:
|
||||
- Growth mechanism simulations
|
||||
- Attack vector analysis
|
||||
- Liquidity depth scenarios
|
||||
- See `analysis/README.md` for detailed usage
|
||||
See `analysis/CLAUDE.md` for fuzzing and attack vector testing.
|
||||
|
||||
## Uniswap V3 Math - Critical Learnings
|
||||
## Uniswap V3 Math
|
||||
|
||||
### Token Ordering and Price Representation
|
||||
|
||||
#### Price Direction Reference Table
|
||||
|
||||
| Scenario | token0 | token1 | Price Represents | Lower Tick → Higher Tick |
|
||||
|----------|--------|--------|------------------|---------------------------|
|
||||
| **token0isETH = true** | ETH | KRAIKEN | ETH per KRAIKEN | KRAIKEN cheap → expensive |
|
||||
| **token0isETH = false** | KRAIKEN | ETH | KRAIKEN per ETH | ETH cheap → expensive |
|
||||
|
||||
#### Understanding "Above" and "Below"
|
||||
|
||||
**Critical distinction**: "Price" in Uniswap V3 always refers to token1's price in units of token0.
|
||||
|
||||
**When token0isETH = true (ETH is token0, KRAIKEN is token1):**
|
||||
- Price = KRAIKEN price in ETH (how much ETH to buy 1 KRAIKEN)
|
||||
- Higher tick = Higher KRAIKEN price in ETH
|
||||
- Lower tick = Lower KRAIKEN price in ETH
|
||||
- "Price moved up" = KRAIKEN became more expensive = ETH became cheaper
|
||||
- "Price moved down" = KRAIKEN became cheaper = ETH became more expensive
|
||||
|
||||
**When token0isETH = false (KRAIKEN is token0, ETH is token1):**
|
||||
- Price = ETH price in KRAIKEN (how much KRAIKEN to buy 1 ETH)
|
||||
- Higher tick = Higher ETH price in KRAIKEN
|
||||
- Lower tick = Lower ETH price in KRAIKEN
|
||||
- "Price moved up" = ETH became more expensive = KRAIKEN became cheaper
|
||||
- "Price moved down" = ETH became cheaper = KRAIKEN became more expensive
|
||||
|
||||
This determines token composition:
|
||||
|
||||
| Current Price vs Position | Position Contains | Why |
|
||||
|--------------------------|-------------------|-----|
|
||||
| Below range (tick < tickLower) | 100% token1 | Token0 is too expensive to hold |
|
||||
| Within range (tickLower ≤ tick ≤ tickUpper) | Both tokens | Active liquidity range |
|
||||
| Above range (tick > tickUpper) | 100% token0 | Token1 is too expensive to hold |
|
||||
|
||||
### Liquidity vs Token Amounts
|
||||
|
||||
**Key Insight**: Liquidity (L) is a mathematical constant representing capital efficiency, NOT token count.
|
||||
|
||||
1. **Liquidity is invariant**: The liquidity value L doesn't change when price moves
|
||||
2. **Token amounts are variable**: Depend on liquidity L, price range, and current price location
|
||||
3. **Same L, different ranges**: Results in different token amounts due to price differences
|
||||
|
||||
### Why Positions at Different Ranges Have Different Token Ratios
|
||||
|
||||
For the same liquidity value L:
|
||||
- **Position at lower ticks**: Higher token1 price → fewer token1, more token0 potential
|
||||
- **Position at higher ticks**: Lower token1 price → more token1, less token0 potential
|
||||
|
||||
This explains why a position with fewer tokens can have more liquidity (and thus more price impact resistance).
|
||||
|
||||
### Liquidity Per Tick - The Critical Metric
|
||||
|
||||
When comparing positions of different widths, always normalize to liquidity per tick:
|
||||
|
||||
```solidity
|
||||
liquidityPerTick = totalLiquidity / (tickUpper - tickLower)
|
||||
```
|
||||
|
||||
The discovery position maintains its target liquidity density through width adjustment:
|
||||
|
||||
```solidity
|
||||
// Ensure discovery has X times more liquidity per tick than anchor
|
||||
discoveryLiquidity = anchorLiquidity * multiplier * discoveryWidth / anchorWidth
|
||||
```
|
||||
|
||||
### Key Takeaways
|
||||
|
||||
1. **Liquidity ≠ Token Count**: Higher liquidity can mean fewer tokens at different price ranges
|
||||
2. **Price Range Matters**: Token composition depends on where positions sit relative to current price
|
||||
3. **Normalize for Width**: Always compare liquidity per tick when positions have different widths
|
||||
4. **Token0 Ordering is Critical**: Determines which direction is "up" or "down" in price
|
||||
See [UNISWAP_V3_MATH.md](UNISWAP_V3_MATH.md) for detailed math concepts.
|
||||
144
onchain/UNISWAP_V3_MATH.md
Normal file
144
onchain/UNISWAP_V3_MATH.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# Uniswap V3 Math - Critical Learnings
|
||||
|
||||
## Token Ordering and Price Representation
|
||||
|
||||
### Price Direction Reference Table
|
||||
|
||||
| Scenario | token0 | token1 | Price Represents | Lower Tick → Higher Tick |
|
||||
|----------|--------|--------|------------------|---------------------------|
|
||||
| **token0isETH = true** | ETH | KRAIKEN | ETH per KRAIKEN | KRAIKEN cheap → expensive |
|
||||
| **token0isETH = false** | KRAIKEN | ETH | KRAIKEN per ETH | ETH cheap → expensive |
|
||||
|
||||
### Understanding "Above" and "Below"
|
||||
|
||||
**Critical distinction**: "Price" in Uniswap V3 always refers to token1's price in units of token0.
|
||||
|
||||
**When token0isETH = true (ETH is token0, KRAIKEN is token1):**
|
||||
- Price = KRAIKEN price in ETH (how much ETH to buy 1 KRAIKEN)
|
||||
- Higher tick = Higher KRAIKEN price in ETH
|
||||
- Lower tick = Lower KRAIKEN price in ETH
|
||||
- "Price moved up" = KRAIKEN became more expensive = ETH became cheaper
|
||||
- "Price moved down" = KRAIKEN became cheaper = ETH became more expensive
|
||||
|
||||
**When token0isETH = false (KRAIKEN is token0, ETH is token1):**
|
||||
- Price = ETH price in KRAIKEN (how much KRAIKEN to buy 1 ETH)
|
||||
- Higher tick = Higher ETH price in KRAIKEN
|
||||
- Lower tick = Lower ETH price in KRAIKEN
|
||||
- "Price moved up" = ETH became more expensive = KRAIKEN became cheaper
|
||||
- "Price moved down" = ETH became cheaper = KRAIKEN became more expensive
|
||||
|
||||
This determines token composition:
|
||||
|
||||
| Current Price vs Position | Position Contains | Why |
|
||||
|--------------------------|-------------------|-----|
|
||||
| Below range (tick < tickLower) | 100% token1 | Token0 is too expensive to hold |
|
||||
| Within range (tickLower ≤ tick ≤ tickUpper) | Both tokens | Active liquidity range |
|
||||
| Above range (tick > tickUpper) | 100% token0 | Token1 is too expensive to hold |
|
||||
|
||||
## Liquidity vs Token Amounts
|
||||
|
||||
**Key Insight**: Liquidity (L) is a mathematical constant representing capital efficiency, NOT token count.
|
||||
|
||||
1. **Liquidity is invariant**: The liquidity value L doesn't change when price moves
|
||||
2. **Token amounts are variable**: Depend on liquidity L, price range, and current price location
|
||||
3. **Same L, different ranges**: Results in different token amounts due to price differences
|
||||
|
||||
### Why Positions at Different Ranges Have Different Token Ratios
|
||||
|
||||
For the same liquidity value L:
|
||||
- **Position at lower ticks**: Higher token1 price → fewer token1, more token0 potential
|
||||
- **Position at higher ticks**: Lower token1 price → more token1, less token0 potential
|
||||
|
||||
This explains why a position with fewer tokens can have more liquidity (and thus more price impact resistance).
|
||||
|
||||
### Liquidity Per Tick - The Critical Metric
|
||||
|
||||
When comparing positions of different widths, always normalize to liquidity per tick:
|
||||
|
||||
```solidity
|
||||
liquidityPerTick = totalLiquidity / (tickUpper - tickLower)
|
||||
```
|
||||
|
||||
The discovery position maintains its target liquidity density through width adjustment:
|
||||
|
||||
```solidity
|
||||
// Ensure discovery has X times more liquidity per tick than anchor
|
||||
discoveryLiquidity = anchorLiquidity * multiplier * discoveryWidth / anchorWidth
|
||||
```
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
1. **Liquidity ≠ Token Count**: Higher liquidity can mean fewer tokens at different price ranges
|
||||
2. **Price Range Matters**: Token composition depends on where positions sit relative to current price
|
||||
3. **Normalize for Width**: Always compare liquidity per tick when positions have different widths
|
||||
4. **Token0 Ordering is Critical**: Determines which direction is "up" or "down" in price
|
||||
|
||||
## Critical Formulas for Out-of-Range Positions
|
||||
|
||||
### When Position is Below Current Price (currentTick < tickLower)
|
||||
|
||||
The position holds only token0:
|
||||
```
|
||||
amount0 = L * (sqrt(priceUpper) - sqrt(priceLower)) / (sqrt(priceUpper) * sqrt(priceLower))
|
||||
```
|
||||
|
||||
In Solidity/JavaScript with Q96 notation:
|
||||
```javascript
|
||||
amount0 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / sqrtRatioAX96 * Q96 / sqrtRatioBX96
|
||||
```
|
||||
|
||||
### When Position is Above Current Price (currentTick > tickUpper)
|
||||
|
||||
The position holds only token1:
|
||||
```
|
||||
amount1 = L * (sqrt(priceUpper) - sqrt(priceLower))
|
||||
```
|
||||
|
||||
In Solidity/JavaScript with Q96 notation:
|
||||
```javascript
|
||||
amount1 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96
|
||||
```
|
||||
|
||||
### Critical Insight: ETH Position Creation
|
||||
|
||||
When creating a position above current price with ETH in a token0isWeth pool:
|
||||
1. ETH is used as token1 (via getLiquidityForAmount1)
|
||||
2. The position will hold KRAIKEN when in range
|
||||
3. But if price drops below the range, it converts to holding ETH as token0
|
||||
|
||||
**Example**: Floor position created with 38 ETH at ticks [127400, 127600] when current is 123890:
|
||||
- Creation: Uses getLiquidityForAmount1 with 38 ETH
|
||||
- Result: Liquidity = 6.48e18
|
||||
- Current holdings: 38 ETH (as token0, since price is below range)
|
||||
|
||||
## Common Confusion Points
|
||||
|
||||
### 1. Price Direction vs ETH Value
|
||||
|
||||
When token0isWeth = true:
|
||||
- Higher tick = Higher price = More KRAIKEN per ETH = ETH is MORE valuable
|
||||
- Lower tick = Lower price = Less KRAIKEN per ETH = ETH is LESS valuable
|
||||
|
||||
This is counterintuitive because "price goes up" means the denominated asset (ETH) becomes more valuable.
|
||||
|
||||
### 2. Position Token Holdings
|
||||
|
||||
A position's token composition depends ONLY on:
|
||||
- Current price location relative to the position range
|
||||
- NOT on how the position was created
|
||||
- NOT on which token was used to mint
|
||||
|
||||
### 3. The Floor Position "Below Current Price" Confusion
|
||||
|
||||
In KRAIKEN's three-position strategy:
|
||||
- Floor position is placed where ETH is MORE valuable (higher ticks when token0isWeth)
|
||||
- This is "below" current price from an ETH value perspective
|
||||
- But "above" current tick numerically
|
||||
- Selling KRAIKEN for ETH moves price TOWARD the floor position
|
||||
|
||||
### 4. Liquidity Calculation Direction
|
||||
|
||||
When minting a position out of range:
|
||||
- Use getLiquidityForAmount0 if providing token0
|
||||
- Use getLiquidityForAmount1 if providing token1
|
||||
- The same liquidity value will require different token amounts depending on which token you provide
|
||||
|
|
@ -1,131 +1,56 @@
|
|||
# KRAIKEN LiquidityManager Fuzzing Analysis
|
||||
|
||||
Tools for testing the KRAIKEN LiquidityManager's resilience against various trading strategies to identify scenarios where traders can profit.
|
||||
|
||||
Tools for testing KRAIKEN's three-position strategy resilience against various market conditions and trading patterns.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using run-fuzzing.sh (Recommended)
|
||||
|
||||
The `run-fuzzing.sh` script provides an easy way to run fuzzing campaigns with different market optimizers:
|
||||
|
||||
```bash
|
||||
# Basic usage - run with specific optimizer
|
||||
# Run with specific optimizer (50 runs default)
|
||||
./analysis/run-fuzzing.sh BullMarketOptimizer
|
||||
|
||||
# Specify number of runs (default: 50)
|
||||
./analysis/run-fuzzing.sh WhaleOptimizer runs=100
|
||||
# Custom runs and trades
|
||||
./analysis/run-fuzzing.sh WhaleOptimizer runs=100 trades=30
|
||||
|
||||
# Specify trades per run (default: 20, actual will be ±5)
|
||||
./analysis/run-fuzzing.sh BearMarketOptimizer runs=10 trades=50
|
||||
|
||||
# Debug mode - generates position tracking CSV (forces runs=1)
|
||||
# Debug mode with position tracking CSV (forces runs=1)
|
||||
./analysis/run-fuzzing.sh NeutralMarketOptimizer debugCSV
|
||||
|
||||
# Multiple parameters
|
||||
./analysis/run-fuzzing.sh BullMarketOptimizer runs=25 trades=30
|
||||
```
|
||||
|
||||
**Available optimizers:**
|
||||
- `BullMarketOptimizer` - Biased towards buying
|
||||
## Available Optimizers
|
||||
|
||||
- `BullMarketOptimizer` - Buying bias
|
||||
- `NeutralMarketOptimizer` - Balanced trading
|
||||
- `BearMarketOptimizer` - Biased towards selling
|
||||
- `WhaleOptimizer` - Large position trading
|
||||
- `MockOptimizer` - Test optimizer
|
||||
- `BearMarketOptimizer` - Selling bias
|
||||
- `WhaleOptimizer` - Large positions
|
||||
- `RandomScenarioOptimizer` - Random behavior
|
||||
|
||||
**Features:**
|
||||
- Automatic results aggregation and summary generation
|
||||
- Progress tracking with colored output
|
||||
- Cumulative P&L calculation across all runs
|
||||
- Automatic visualization launch for profitable scenarios
|
||||
- Organized output in timestamped directories
|
||||
## Output Structure
|
||||
|
||||
### Manual Fuzzing (Advanced)
|
||||
|
||||
```bash
|
||||
# Run fuzzing analysis with default settings (100 runs per market)
|
||||
forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
|
||||
# Custom configuration
|
||||
FUZZING_RUNS=500 forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
|
||||
# With position tracking (generates detailed CSV for each scenario)
|
||||
TRACK_POSITIONS=true FUZZING_RUNS=50 forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### run-fuzzing.sh Parameters
|
||||
- **optimizer_class**: Required. The optimizer class to use (e.g., BullMarketOptimizer)
|
||||
- **runs=N**: Optional. Number of fuzzing runs (default: 50)
|
||||
- **trades=N**: Optional. Trades per run (default: 20, actual will be ±5)
|
||||
- **debugCSV**: Optional. Enable debug mode with position tracking CSV (forces runs=1)
|
||||
|
||||
### Environment Variables (Manual Mode)
|
||||
- **FUZZING_RUNS**: Number of random trading scenarios per market type (default: 100)
|
||||
- **TRACK_POSITIONS**: Enable detailed position tracking CSV output (default: false)
|
||||
- **OPTIMIZER_CLASS**: The optimizer to use (default: BullMarketOptimizer)
|
||||
- **TRADES_PER_RUN**: Number of trades per run (default: 20)
|
||||
- **SEED_OFFSET**: Starting seed for random number generation (default: 0)
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Real Deployments**: Deploys actual Uniswap V3 factory, pool, and LiquidityManager
|
||||
2. **Random Trading**: Generates random buy/sell patterns with varying amounts and timing
|
||||
3. **Recenter Calls**: Triggers `lm.recenter()` at random intervals
|
||||
4. **Profit Detection**: Identifies scenarios where traders end with more ETH than they started
|
||||
5. **CSV Export**: Saves all profitable scenarios to `profitable_scenarios_[timestamp].csv`
|
||||
|
||||
## Output Files
|
||||
|
||||
### Using run-fuzzing.sh
|
||||
Each campaign creates a timestamped directory: `fuzzing_results_[optimizer]_[timestamp]/`
|
||||
- `config.txt` - Campaign configuration
|
||||
Each campaign creates `fuzzing_results_[optimizer]_[timestamp]/`:
|
||||
- `config.txt` - Campaign parameters
|
||||
- `run_*.log` - Individual run logs
|
||||
- `merged_profitable_scenarios.csv` - All profitable scenarios combined
|
||||
- `summary.txt` - Campaign summary with statistics
|
||||
- `debug_positions_*.csv` - Position tracking data (when debugCSV is used)
|
||||
|
||||
### Manual Mode
|
||||
- `profitable_scenarios_[timestamp].csv` - Details of all profitable trading sequences
|
||||
- `positions_[scenario]_[seed].csv` - Liquidity position data (only with TRACK_POSITIONS=true)
|
||||
- `merged_profitable_scenarios.csv` - Profitable scenarios combined
|
||||
- `summary.txt` - Statistics and cumulative P&L
|
||||
- `debug_positions_*.csv` - Position data (debugCSV mode only)
|
||||
|
||||
## Visualization
|
||||
|
||||
```bash
|
||||
# View results in browser
|
||||
python3 -m http.server 8000
|
||||
# Open http://localhost:8000/scenario-visualizer.html
|
||||
# Automatic launch with debugCSV
|
||||
./analysis/run-fuzzing.sh [optimizer] debugCSV
|
||||
|
||||
# Or use the shell script
|
||||
./view-scenarios.sh
|
||||
# Manual server (port 8000)
|
||||
./analysis/view-scenarios.sh
|
||||
```
|
||||
|
||||
## Analysis Tools
|
||||
|
||||
- `AnalysisVisualizer.py` - Generates charts from CSV data
|
||||
- `scenario-visualizer.html` - Interactive web visualization
|
||||
- `RISK_ANALYSIS_FINDINGS.md` - Summary of discovered vulnerabilities
|
||||
|
||||
## Components
|
||||
|
||||
- `run-fuzzing.sh` - Main campaign runner with automatic visualization
|
||||
- `FuzzingAnalysis.s.sol` - Core fuzzing script
|
||||
- `helpers/SwapExecutor.sol` - Shared swap execution logic
|
||||
- `helpers/CSVManager.sol` - CSV generation utilities
|
||||
- `helpers/CSVHelper.sol` - CSV formatting helpers
|
||||
|
||||
## Example Campaign Comparison
|
||||
|
||||
To run fuzzing campaigns comparing different market optimizers:
|
||||
## Advanced Usage
|
||||
|
||||
```bash
|
||||
# Run campaigns for all three market conditions
|
||||
./analysis/run-fuzzing.sh BullMarketOptimizer runs=100
|
||||
./analysis/run-fuzzing.sh NeutralMarketOptimizer runs=100
|
||||
./analysis/run-fuzzing.sh BearMarketOptimizer runs=100
|
||||
|
||||
# Check results
|
||||
cat fuzzing_results_*/summary.txt | grep -E "(Optimizer:|Success rate:|Average P&L)"
|
||||
# Manual fuzzing with environment variables
|
||||
FUZZING_RUNS=500 TRACK_POSITIONS=true forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir
|
||||
```
|
||||
|
||||
Environment variables:
|
||||
- `FUZZING_RUNS` - Scenarios per market (default: 100)
|
||||
- `TRACK_POSITIONS` - Enable position CSV (default: false)
|
||||
- `OPTIMIZER_CLASS` - Optimizer to use
|
||||
- `TRADES_PER_RUN` - Trades per run (default: 20)
|
||||
|
|
@ -343,14 +343,6 @@ contract FuzzingAnalysis is Test, CSVManager {
|
|||
(uint128 anchorLiq, int24 anchorLower, int24 anchorUpper) = lm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
||||
(uint128 discoveryLiq, int24 discoveryLower, int24 discoveryUpper) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY);
|
||||
|
||||
// Calculate ETH and HARB amounts in each position using proper Uniswap math
|
||||
uint256 floorEth = 0;
|
||||
uint256 floorHarb = 0;
|
||||
uint256 anchorEth = 0;
|
||||
uint256 anchorHarb = 0;
|
||||
uint256 discoveryEth = 0;
|
||||
uint256 discoveryHarb = 0;
|
||||
|
||||
// Debug: Log liquidity values
|
||||
if (keccak256(bytes(label)) == keccak256(bytes("Initial")) || keccak256(bytes(label)) == keccak256(bytes("Recenter_2"))) {
|
||||
console.log("=== LIQUIDITY VALUES ===");
|
||||
|
|
@ -372,125 +364,19 @@ contract FuzzingAnalysis is Test, CSVManager {
|
|||
}
|
||||
}
|
||||
|
||||
// Calculate amounts for each position using LiquidityAmounts library
|
||||
if (floorLiq > 0) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorUpper);
|
||||
|
||||
// Calculate actual deposited amounts based on position relative to current price
|
||||
if (token0isWeth) {
|
||||
if (currentTick < floorLower) {
|
||||
// Position is above current price - contains only token1 (KRAIKEN)
|
||||
floorEth = 0;
|
||||
// Use position's lower tick for actual deposited amount
|
||||
floorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
} else if (currentTick >= floorUpper) {
|
||||
// Position is below current price - contains only token0 (WETH)
|
||||
// Use position's upper tick for actual deposited amount
|
||||
floorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
floorHarb = 0;
|
||||
} else {
|
||||
// Current price is within the position
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
floorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, floorLiq);
|
||||
floorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, floorLiq);
|
||||
}
|
||||
} else {
|
||||
if (currentTick < floorLower) {
|
||||
// Position is above current price
|
||||
floorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
floorEth = 0;
|
||||
} else if (currentTick >= floorUpper) {
|
||||
// Position is below current price
|
||||
floorHarb = 0;
|
||||
floorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq);
|
||||
} else {
|
||||
// Current price is within the position
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
floorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, floorLiq);
|
||||
floorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, floorLiq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorLiq > 0) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(anchorLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(anchorUpper);
|
||||
|
||||
if (token0isWeth) {
|
||||
if (currentTick < anchorLower) {
|
||||
anchorEth = 0;
|
||||
anchorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
} else if (currentTick >= anchorUpper) {
|
||||
anchorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorHarb = 0;
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
anchorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, anchorLiq);
|
||||
}
|
||||
} else {
|
||||
if (currentTick < anchorLower) {
|
||||
anchorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorEth = 0;
|
||||
} else if (currentTick >= anchorUpper) {
|
||||
anchorHarb = 0;
|
||||
anchorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq);
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
anchorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, anchorLiq);
|
||||
anchorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, anchorLiq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (discoveryLiq > 0) {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(discoveryLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(discoveryUpper);
|
||||
|
||||
if (token0isWeth) {
|
||||
if (currentTick < discoveryLower) {
|
||||
discoveryEth = 0;
|
||||
discoveryHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
} else if (currentTick >= discoveryUpper) {
|
||||
discoveryEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryHarb = 0;
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
discoveryEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, discoveryLiq);
|
||||
}
|
||||
} else {
|
||||
if (currentTick < discoveryLower) {
|
||||
discoveryHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryEth = 0;
|
||||
} else if (currentTick >= discoveryUpper) {
|
||||
discoveryHarb = 0;
|
||||
discoveryEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq);
|
||||
} else {
|
||||
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
discoveryHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, discoveryLiq);
|
||||
discoveryEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, discoveryLiq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create position data row matching the expected CSV format
|
||||
// Create position data row with liquidity values directly
|
||||
string memory row = string.concat(
|
||||
label, ", ",
|
||||
vm.toString(currentTick), ", ",
|
||||
vm.toString(floorLower), ", ",
|
||||
vm.toString(floorUpper), ", ",
|
||||
vm.toString(floorEth), ", ",
|
||||
vm.toString(floorHarb), ", ",
|
||||
vm.toString(uint256(floorLiq)), ", ",
|
||||
vm.toString(anchorLower), ", ",
|
||||
vm.toString(anchorUpper), ", ",
|
||||
vm.toString(anchorEth), ", ",
|
||||
vm.toString(anchorHarb), ", ",
|
||||
vm.toString(uint256(anchorLiq)), ", ",
|
||||
vm.toString(discoveryLower), ", ",
|
||||
vm.toString(discoveryUpper), ", ",
|
||||
vm.toString(discoveryEth), ", ",
|
||||
vm.toString(discoveryHarb), ", ",
|
||||
vm.toString(uint256(discoveryLiq)), ", ",
|
||||
token0isWeth ? "true" : "false"
|
||||
);
|
||||
appendCSVRow(row);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ library CSVHelper {
|
|||
*/
|
||||
function createPositionsHeader() internal pure returns (string memory) {
|
||||
return
|
||||
"precedingAction, currentTick, floorTickLower, floorTickUpper, floorToken0, floorToken1, anchorTickLower, anchorTickUpper, anchorToken0, anchorToken1, discoveryTickLower, discoveryTickUpper, discoveryToken0, discoveryToken1, token0isWeth";
|
||||
"precedingAction, currentTick, floorTickLower, floorTickUpper, floorLiquidity, anchorTickLower, anchorTickUpper, anchorLiquidity, discoveryTickLower, discoveryTickUpper, discoveryLiquidity, token0isWeth";
|
||||
}
|
||||
|
||||
function createTimeSeriesHeader() internal pure returns (string memory) {
|
||||
|
|
|
|||
|
|
@ -343,36 +343,66 @@ if [ "$CSV_GENERATED" = true ] && [ -n "$LATEST_CSV" ]; then
|
|||
# Use absolute path for the symlink
|
||||
ln -s "$(pwd)/$LATEST_CSV" "$TEMP_LINK"
|
||||
|
||||
# Start the viewer in background in its own process group
|
||||
setsid ./view-scenarios.sh &
|
||||
VIEWER_PID=$!
|
||||
# Check if server is already running on common ports
|
||||
SERVER_RUNNING=false
|
||||
EXISTING_PORT=""
|
||||
for PORT in 8000 8001 8002; do
|
||||
if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||||
# Check if it's a python http server in our analysis directory
|
||||
if lsof -Pi :$PORT -sTCP:LISTEN 2>/dev/null | grep -q "python.*http.server"; then
|
||||
SERVER_RUNNING=true
|
||||
EXISTING_PORT=$PORT
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Give the server time to start and browser to open
|
||||
sleep 2
|
||||
if [ "$SERVER_RUNNING" = true ]; then
|
||||
echo -e "${YELLOW}Server already running on port $EXISTING_PORT${NC}"
|
||||
echo -e "${GREEN}Browser should open to: http://localhost:$EXISTING_PORT/scenario-visualizer.html${NC}"
|
||||
|
||||
# Show the URL
|
||||
echo ""
|
||||
echo -e "${GREEN}Browser should open to: http://localhost:8000/scenario-visualizer.html${NC}"
|
||||
echo "(If port 8000 was busy, check the port number mentioned above)"
|
||||
echo "If browser didn't open, manually navigate to that URL"
|
||||
# Try to open browser to existing server
|
||||
if command -v xdg-open &> /dev/null; then
|
||||
xdg-open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null &
|
||||
elif command -v open &> /dev/null; then
|
||||
open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null &
|
||||
fi
|
||||
|
||||
# Wait for user input
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Enter to stop the viewer and exit...${NC}"
|
||||
read -r
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Enter to exit (server will keep running)...${NC}"
|
||||
read -r
|
||||
else
|
||||
# Start the viewer in background in its own process group
|
||||
setsid ./view-scenarios.sh &
|
||||
VIEWER_PID=$!
|
||||
|
||||
# Kill the viewer process and its children
|
||||
if [ -n "$VIEWER_PID" ]; then
|
||||
# Kill the entire process group (includes python server)
|
||||
pkill -TERM -g $VIEWER_PID 2>/dev/null || true
|
||||
# Give it a moment to clean up
|
||||
sleep 1
|
||||
# Force kill if still running
|
||||
pkill -KILL -g $VIEWER_PID 2>/dev/null || true
|
||||
# Give the server time to start and browser to open
|
||||
sleep 2
|
||||
|
||||
# Show the URL
|
||||
echo ""
|
||||
echo -e "${GREEN}Browser should open to: http://localhost:8000/scenario-visualizer.html${NC}"
|
||||
echo "(If port 8000 was busy, check the port number mentioned above)"
|
||||
echo "If browser didn't open, manually navigate to that URL"
|
||||
|
||||
# Wait for user input
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Enter to stop the viewer and exit...${NC}"
|
||||
read -r
|
||||
|
||||
# Kill the viewer process and its children
|
||||
if [ -n "$VIEWER_PID" ]; then
|
||||
# Kill the entire process group (includes python server)
|
||||
pkill -TERM -g $VIEWER_PID 2>/dev/null || true
|
||||
# Give it a moment to clean up
|
||||
sleep 1
|
||||
# Force kill if still running
|
||||
pkill -KILL -g $VIEWER_PID 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Viewer stopped.${NC}"
|
||||
fi
|
||||
|
||||
# Clean up the symlink
|
||||
rm -f "$TEMP_LINK"
|
||||
|
||||
echo -e "${GREEN}Viewer stopped.${NC}"
|
||||
fi
|
||||
|
|
@ -133,16 +133,17 @@
|
|||
<em>Discovery</em>: Edge liquidity position - holds KRAIKEN when ETH is expensive (above current price)<br>
|
||||
<br>
|
||||
<strong>Price Multiples:</strong> Shows ETH price relative to current (1x):<br>
|
||||
• 0.5x = ETH is half as expensive (Floor position holds ETH)<br>
|
||||
• < 1x = ETH is cheaper than current price (positions below current hold ETH)<br>
|
||||
• 1x = Current ETH price (red dashed line)<br>
|
||||
• 2x = ETH is twice as expensive (Discovery position holds KRAIKEN)<br>
|
||||
• > 1x = ETH is more expensive than current price (positions above current hold KRAIKEN)<br>
|
||||
<br>
|
||||
<em>Note: The x-axis automatically adjusts based on token ordering in the pool</em>
|
||||
<em>Note: The x-axis automatically adjusts based on token ordering in the pool</em><br>
|
||||
<br>
|
||||
<strong>Navigation:</strong> Use the Previous/Next buttons or URL parameter <code>?row=N</code> to view specific CSV rows
|
||||
</div>
|
||||
<div id="status">Loading profitable scenario data...</div>
|
||||
<textarea id="csvInput" placeholder="Paste CSV formatted data here..." style="display: none;"></textarea>
|
||||
<button onclick="parseAndSimulateCSV()" style="display: none;">Simulate CSV Data</button>
|
||||
<button onclick="toggleManualInput()" id="manualButton">Manual Input Mode</button>
|
||||
<div id="simulations"></div>
|
||||
|
||||
<script>
|
||||
|
|
@ -171,6 +172,13 @@
|
|||
// - Anchor Position: Mixed tokens around current price for shallow liquidity
|
||||
// - Discovery Position: Edge liquidity - holds ETH above price, KRAIKEN below price
|
||||
|
||||
// Get row parameter from URL
|
||||
function getRowParameter() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const row = urlParams.get('row');
|
||||
return row ? parseInt(row) - 2 : 0; // Convert CSV line number to array index
|
||||
}
|
||||
|
||||
// Auto-load CSV data on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCSVData();
|
||||
|
|
@ -208,25 +216,10 @@
|
|||
<em>If no CSV exists, run: forge script analysis/SimpleAnalysis.s.sol --ffi</em>
|
||||
</div>
|
||||
`;
|
||||
console.log('CSV load error:', error);
|
||||
// CSV load error - handled by status message above
|
||||
});
|
||||
}
|
||||
|
||||
function toggleManualInput() {
|
||||
const csvInput = document.getElementById('csvInput');
|
||||
const button = document.getElementById('manualButton');
|
||||
const parseButton = document.querySelector('button[onclick="parseAndSimulateCSV()"]');
|
||||
|
||||
if (csvInput.style.display === 'none') {
|
||||
csvInput.style.display = 'block';
|
||||
parseButton.style.display = 'inline-block';
|
||||
button.textContent = 'Hide Manual Input';
|
||||
} else {
|
||||
csvInput.style.display = 'none';
|
||||
parseButton.style.display = 'none';
|
||||
button.textContent = 'Manual Input Mode';
|
||||
}
|
||||
}
|
||||
|
||||
function parseCSV(csv) {
|
||||
const lines = csv.trim().split('\n');
|
||||
|
|
@ -252,37 +245,121 @@
|
|||
}
|
||||
|
||||
function simulateCSVData(data) {
|
||||
let previousRow = null;
|
||||
// Get selected row from URL parameter
|
||||
const selectedIndex = getRowParameter();
|
||||
|
||||
data.forEach((row, index) => {
|
||||
// Add current row info and navigation
|
||||
const currentRowInfo = document.createElement('div');
|
||||
currentRowInfo.style.cssText = 'margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 4px; text-align: center;';
|
||||
currentRowInfo.innerHTML = `
|
||||
<strong>Currently viewing: Line ${selectedIndex + 2} - ${data[selectedIndex].precedingAction}</strong><br>
|
||||
<span style="color: #666; font-size: 14px;">Total rows: ${data.length} (Lines 2-${data.length + 1})</span>
|
||||
`;
|
||||
document.getElementById('simulations').appendChild(currentRowInfo);
|
||||
|
||||
// Add navigation buttons above if not first row
|
||||
if (selectedIndex > 0) {
|
||||
const topNavDiv = document.createElement('div');
|
||||
topNavDiv.style.cssText = 'margin: 20px 0; text-align: center;';
|
||||
topNavDiv.innerHTML = `
|
||||
<button onclick="navigateRow(-1)" style="padding: 10px 20px; font-size: 16px;">
|
||||
← Previous Row (Line ${selectedIndex + 1})
|
||||
</button>
|
||||
`;
|
||||
document.getElementById('simulations').appendChild(topNavDiv);
|
||||
}
|
||||
|
||||
// Process only the selected row
|
||||
let previousRow = selectedIndex > 0 ? data[selectedIndex - 1] : null;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex < data.length) {
|
||||
const row = data[selectedIndex];
|
||||
const index = selectedIndex;
|
||||
const precedingAction = row.precedingAction;
|
||||
const currentTick = parseFloat(row.currentTick);
|
||||
const token0isWeth = row.token0isWeth === 'true' || row.token0isWeth === true;
|
||||
const floorTickLower = parseFloat(row.floorTickLower);
|
||||
const floorTickUpper = parseFloat(row.floorTickUpper);
|
||||
// Swap floor values to match expected behavior
|
||||
const floorEth = parseFloat(row.floorToken1 || 0) / 1e18;
|
||||
const floorKraiken = parseFloat(row.floorToken0 || 0) / 1e18;
|
||||
const floorLiquidity = parseFloat(row.floorLiquidity || 0);
|
||||
const anchorTickLower = parseFloat(row.anchorTickLower);
|
||||
const anchorTickUpper = parseFloat(row.anchorTickUpper);
|
||||
const anchorEth = parseFloat(row.anchorToken0 || 0) / 1e18;
|
||||
const anchorKraiken = parseFloat(row.anchorToken1 || 0) / 1e18;
|
||||
const anchorLiquidity = parseFloat(row.anchorLiquidity || 0);
|
||||
const discoveryTickLower = parseFloat(row.discoveryTickLower);
|
||||
const discoveryTickUpper = parseFloat(row.discoveryTickUpper);
|
||||
// Swap discovery values to match expected behavior
|
||||
const discoveryEth = parseFloat(row.discoveryToken1 || 0) / 1e18;
|
||||
const discoveryKraiken = parseFloat(row.discoveryToken0 || 0) / 1e18;
|
||||
const discoveryLiquidity = parseFloat(row.discoveryLiquidity || 0);
|
||||
|
||||
// Calculate token amounts from liquidity
|
||||
const floorAmounts = getAmountsForLiquidity(floorLiquidity, floorTickLower, floorTickUpper, currentTick);
|
||||
const anchorAmounts = getAmountsForLiquidity(anchorLiquidity, anchorTickLower, anchorTickUpper, currentTick);
|
||||
const discoveryAmounts = getAmountsForLiquidity(discoveryLiquidity, discoveryTickLower, discoveryTickUpper, currentTick);
|
||||
|
||||
|
||||
// FIXED calculation - properly determine ETH amounts based on token ordering
|
||||
let floorEthAmount, anchorEthAmount, discoveryEthAmount;
|
||||
let floorKraikenAmount, anchorKraikenAmount, discoveryKraikenAmount;
|
||||
|
||||
// Simply use the amounts from getAmountsForLiquidity and determine which is ETH
|
||||
if (token0isWeth) {
|
||||
// token0 is WETH, token1 is KRAIKEN
|
||||
floorEthAmount = floorAmounts.amount0 / 1e18;
|
||||
floorKraikenAmount = floorAmounts.amount1 / 1e18;
|
||||
anchorEthAmount = anchorAmounts.amount0 / 1e18;
|
||||
anchorKraikenAmount = anchorAmounts.amount1 / 1e18;
|
||||
discoveryEthAmount = discoveryAmounts.amount0 / 1e18;
|
||||
discoveryKraikenAmount = discoveryAmounts.amount1 / 1e18;
|
||||
} else {
|
||||
// token0 is KRAIKEN, token1 is WETH
|
||||
floorEthAmount = floorAmounts.amount1 / 1e18;
|
||||
floorKraikenAmount = floorAmounts.amount0 / 1e18;
|
||||
anchorEthAmount = anchorAmounts.amount1 / 1e18;
|
||||
anchorKraikenAmount = anchorAmounts.amount0 / 1e18;
|
||||
discoveryEthAmount = discoveryAmounts.amount1 / 1e18;
|
||||
discoveryKraikenAmount = discoveryAmounts.amount0 / 1e18;
|
||||
}
|
||||
|
||||
const totalEth = floorEthAmount + anchorEthAmount + discoveryEthAmount;
|
||||
|
||||
|
||||
|
||||
// Use the already calculated ETH and KRAIKEN amounts from above
|
||||
let floorEth = floorEthAmount;
|
||||
let floorKraiken = floorKraikenAmount;
|
||||
let anchorEth = anchorEthAmount;
|
||||
let anchorKraiken = anchorKraikenAmount;
|
||||
let discoveryEth = discoveryEthAmount;
|
||||
let discoveryKraiken = discoveryKraikenAmount;
|
||||
|
||||
let actionAmount = '';
|
||||
let additionalInfo = '';
|
||||
|
||||
if (previousRow) {
|
||||
const prevFloorEth = parseFloat(previousRow.floorToken1 || 0) / 1e18;
|
||||
const prevFloorKraiken = parseFloat(previousRow.floorToken0 || 0) / 1e18;
|
||||
const prevAnchorEth = parseFloat(previousRow.anchorToken0 || 0) / 1e18;
|
||||
const prevAnchorKraiken = parseFloat(previousRow.anchorToken1 || 0) / 1e18;
|
||||
const prevDiscoveryEth = parseFloat(previousRow.discoveryToken1 || 0) / 1e18;
|
||||
const prevDiscoveryKraiken = parseFloat(previousRow.discoveryToken0 || 0) / 1e18;
|
||||
// Calculate previous token amounts from liquidity
|
||||
const prevCurrentTick = parseFloat(previousRow.currentTick);
|
||||
const prevFloorLiquidity = parseFloat(previousRow.floorLiquidity || 0);
|
||||
const prevAnchorLiquidity = parseFloat(previousRow.anchorLiquidity || 0);
|
||||
const prevDiscoveryLiquidity = parseFloat(previousRow.discoveryLiquidity || 0);
|
||||
|
||||
const prevFloorAmounts = getAmountsForLiquidity(prevFloorLiquidity, floorTickLower, floorTickUpper, prevCurrentTick);
|
||||
const prevAnchorAmounts = getAmountsForLiquidity(prevAnchorLiquidity, anchorTickLower, anchorTickUpper, prevCurrentTick);
|
||||
const prevDiscoveryAmounts = getAmountsForLiquidity(prevDiscoveryLiquidity, discoveryTickLower, discoveryTickUpper, prevCurrentTick);
|
||||
|
||||
let prevFloorEth, prevFloorKraiken, prevAnchorEth, prevAnchorKraiken, prevDiscoveryEth, prevDiscoveryKraiken;
|
||||
|
||||
if (token0isWeth === true) {
|
||||
prevFloorEth = prevFloorAmounts.amount0 / 1e18;
|
||||
prevFloorKraiken = prevFloorAmounts.amount1 / 1e18;
|
||||
prevAnchorEth = prevAnchorAmounts.amount0 / 1e18;
|
||||
prevAnchorKraiken = prevAnchorAmounts.amount1 / 1e18;
|
||||
prevDiscoveryEth = prevDiscoveryAmounts.amount0 / 1e18;
|
||||
prevDiscoveryKraiken = prevDiscoveryAmounts.amount1 / 1e18;
|
||||
} else {
|
||||
prevFloorEth = prevFloorAmounts.amount1 / 1e18;
|
||||
prevFloorKraiken = prevFloorAmounts.amount0 / 1e18;
|
||||
prevAnchorEth = prevAnchorAmounts.amount1 / 1e18;
|
||||
prevAnchorKraiken = prevAnchorAmounts.amount0 / 1e18;
|
||||
prevDiscoveryEth = prevDiscoveryAmounts.amount1 / 1e18;
|
||||
prevDiscoveryKraiken = prevDiscoveryAmounts.amount0 / 1e18;
|
||||
}
|
||||
|
||||
const ethDifference = (floorEth + anchorEth + discoveryEth) - (prevFloorEth + prevAnchorEth + prevDiscoveryEth);
|
||||
const kraikenDifference = (floorKraiken + anchorKraiken + discoveryKraiken) - (prevFloorKraiken + prevAnchorKraiken + prevDiscoveryKraiken);
|
||||
|
|
@ -302,12 +379,35 @@
|
|||
const lineNumber = index + 2;
|
||||
const headline = `Line ${lineNumber}: ${precedingAction} ${additionalInfo}`;
|
||||
|
||||
|
||||
simulateEnhanced(headline, currentTick,
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, token0isWeth);
|
||||
previousRow = row;
|
||||
});
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken, floorLiquidity,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken, anchorLiquidity,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, discoveryLiquidity, token0isWeth, index, precedingAction);
|
||||
|
||||
// Add navigation buttons below
|
||||
const bottomNavDiv = document.createElement('div');
|
||||
bottomNavDiv.style.cssText = 'margin: 20px 0; text-align: center;';
|
||||
const prevButton = selectedIndex > 0 ?
|
||||
`<button onclick="navigateRow(-1)" style="padding: 10px 20px; font-size: 16px; margin-right: 20px;">
|
||||
← Previous Row (Line ${selectedIndex + 1})
|
||||
</button>` : '';
|
||||
const nextButton = selectedIndex < data.length - 1 ?
|
||||
`<button onclick="navigateRow(1)" style="padding: 10px 20px; font-size: 16px;">
|
||||
Next Row (Line ${selectedIndex + 3}) →
|
||||
</button>` : '';
|
||||
bottomNavDiv.innerHTML = prevButton + nextButton;
|
||||
document.getElementById('simulations').appendChild(bottomNavDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to navigate between rows
|
||||
function navigateRow(direction) {
|
||||
const currentIndex = getRowParameter();
|
||||
const newLineNumber = currentIndex + direction + 2; // Convert back to CSV line number
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('row', newLineNumber);
|
||||
window.location = url;
|
||||
}
|
||||
|
||||
// Uniswap V3 liquidity calculation functions
|
||||
|
|
@ -315,6 +415,47 @@
|
|||
return Math.pow(1.0001, tick);
|
||||
}
|
||||
|
||||
function tickToSqrtPriceX96(tick) {
|
||||
return Math.sqrt(Math.pow(1.0001, tick)) * (2 ** 96);
|
||||
}
|
||||
|
||||
// Calculate token amounts from liquidity for a position
|
||||
function getAmountsForLiquidity(liquidity, tickLower, tickUpper, currentTick) {
|
||||
// Sort ticks to ensure tickLower < tickUpper
|
||||
if (tickLower > tickUpper) {
|
||||
[tickLower, tickUpper] = [tickUpper, tickLower];
|
||||
}
|
||||
|
||||
const sqrtRatioAX96 = tickToSqrtPriceX96(tickLower);
|
||||
const sqrtRatioBX96 = tickToSqrtPriceX96(tickUpper);
|
||||
const sqrtRatioX96 = tickToSqrtPriceX96(currentTick);
|
||||
|
||||
let amount0 = 0;
|
||||
let amount1 = 0;
|
||||
|
||||
const Q96 = 2 ** 96;
|
||||
|
||||
|
||||
if (currentTick < tickLower) {
|
||||
// Current price is below the range, position holds only token0
|
||||
// When position was created above current price, ETH was used as amount1
|
||||
// Use the amount1 formula to get the ETH that was deposited
|
||||
amount0 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96;
|
||||
} else if (currentTick >= tickUpper) {
|
||||
// Current price is above the range, position holds only token1
|
||||
// amount1 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96
|
||||
amount1 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96;
|
||||
} else {
|
||||
// Current price is within the range
|
||||
// amount0 = liquidity * (sqrtRatioBX96 - sqrtRatioX96) / (sqrtRatioX96 * sqrtRatioBX96) * Q96
|
||||
amount0 = liquidity * Q96 * (sqrtRatioBX96 - sqrtRatioX96) / sqrtRatioBX96 / sqrtRatioX96;
|
||||
// amount1 = liquidity * (sqrtRatioX96 - sqrtRatioAX96) / Q96
|
||||
amount1 = liquidity * (sqrtRatioX96 - sqrtRatioAX96) / Q96;
|
||||
}
|
||||
|
||||
return { amount0, amount1 };
|
||||
}
|
||||
|
||||
function priceToSqrtPrice(price) {
|
||||
return Math.sqrt(price);
|
||||
}
|
||||
|
|
@ -372,18 +513,6 @@
|
|||
}
|
||||
|
||||
// Debug logging
|
||||
if (positionName) {
|
||||
console.log(`${positionName} liquidity calculation:`, {
|
||||
token0Amount,
|
||||
token1Amount,
|
||||
tickRange: [tickLower, tickUpper],
|
||||
sqrtPriceLower,
|
||||
sqrtPriceUpper,
|
||||
sqrtPriceDiff: sqrtPriceUpper - sqrtPriceLower,
|
||||
liquidity,
|
||||
calculatedFrom
|
||||
});
|
||||
}
|
||||
|
||||
return liquidity;
|
||||
}
|
||||
|
|
@ -438,9 +567,9 @@
|
|||
}
|
||||
|
||||
function simulateEnhanced(precedingAction, currentTick,
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, token0isWeth) {
|
||||
floorTickLower, floorTickUpper, floorEth, floorKraiken, floorLiquidity,
|
||||
anchorTickLower, anchorTickUpper, anchorEth, anchorKraiken, anchorLiquidity,
|
||||
discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryKraiken, discoveryLiquidity, token0isWeth, index, originalAction) {
|
||||
|
||||
// Position data structure with liquidity calculations
|
||||
const positions = {
|
||||
|
|
@ -450,9 +579,7 @@
|
|||
eth: floorEth,
|
||||
kraiken: floorKraiken,
|
||||
name: 'Floor',
|
||||
liquidity: token0isWeth ?
|
||||
calculateInvariantLiquidity(floorEth, floorKraiken, floorTickLower, floorTickUpper, 'Floor') :
|
||||
calculateInvariantLiquidity(floorKraiken, floorEth, floorTickLower, floorTickUpper, 'Floor')
|
||||
liquidity: floorLiquidity
|
||||
},
|
||||
anchor: {
|
||||
tickLower: anchorTickLower,
|
||||
|
|
@ -460,9 +587,7 @@
|
|||
eth: anchorEth,
|
||||
kraiken: anchorKraiken,
|
||||
name: 'Anchor (Shallow Pool)',
|
||||
liquidity: token0isWeth ?
|
||||
calculateInvariantLiquidity(anchorEth, anchorKraiken, anchorTickLower, anchorTickUpper, 'Anchor') :
|
||||
calculateInvariantLiquidity(anchorKraiken, anchorEth, anchorTickLower, anchorTickUpper, 'Anchor')
|
||||
liquidity: anchorLiquidity
|
||||
},
|
||||
discovery: {
|
||||
tickLower: discoveryTickLower,
|
||||
|
|
@ -470,35 +595,10 @@
|
|||
eth: discoveryEth,
|
||||
kraiken: discoveryKraiken,
|
||||
name: 'Discovery',
|
||||
liquidity: token0isWeth ?
|
||||
calculateInvariantLiquidity(discoveryEth, discoveryKraiken, discoveryTickLower, discoveryTickUpper, 'Discovery') :
|
||||
calculateInvariantLiquidity(discoveryKraiken, discoveryEth, discoveryTickLower, discoveryTickUpper, 'Discovery')
|
||||
liquidity: discoveryLiquidity
|
||||
}
|
||||
};
|
||||
|
||||
// Debug logging for all positions
|
||||
console.log('Position liquidity values:', {
|
||||
floor: {
|
||||
liquidity: positions.floor.liquidity,
|
||||
eth: floorEth,
|
||||
kraiken: floorKraiken,
|
||||
range: [floorTickLower, floorTickUpper]
|
||||
},
|
||||
anchor: {
|
||||
liquidity: positions.anchor.liquidity,
|
||||
eth: anchorEth,
|
||||
kraiken: anchorKraiken,
|
||||
range: [anchorTickLower, anchorTickUpper]
|
||||
},
|
||||
discovery: {
|
||||
liquidity: positions.discovery.liquidity,
|
||||
eth: discoveryEth,
|
||||
kraiken: discoveryKraiken,
|
||||
range: [discoveryTickLower, discoveryTickUpper]
|
||||
},
|
||||
currentTick: currentTick,
|
||||
token0isWeth: token0isWeth
|
||||
});
|
||||
|
||||
// Calculate total active liquidity
|
||||
const totalLiquidity = Object.values(positions).reduce((sum, pos) => sum + pos.liquidity, 0);
|
||||
|
|
@ -535,7 +635,7 @@
|
|||
scenarioContainer.appendChild(chartsContainer);
|
||||
|
||||
// Create summary panel
|
||||
const summaryPanel = createSummaryPanel(positions, currentTick, token0isWeth);
|
||||
const summaryPanel = createSummaryPanel(positions, currentTick, token0isWeth, originalAction || precedingAction, index);
|
||||
scenarioContainer.appendChild(summaryPanel);
|
||||
|
||||
// Add to page
|
||||
|
|
@ -565,18 +665,7 @@
|
|||
...pos
|
||||
};
|
||||
|
||||
console.log(`Position ${key}:`, {
|
||||
ticks: [pos.tickLower, pos.tickUpper],
|
||||
currentTick: currentTick,
|
||||
multiples: [lowerMultiple, upperMultiple],
|
||||
centerMultiple: centerMultiple,
|
||||
token0isWeth: token0isWeth
|
||||
});
|
||||
|
||||
// Warn about extreme positions
|
||||
if (pos.tickLower > 180000 || pos.tickUpper > 180000) {
|
||||
console.warn(`EXTREME TICKS: ${key} position has ticks above 180000, which represents extreme price multiples`);
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate bar widths to represent actual price multiple ranges
|
||||
|
|
@ -663,14 +752,6 @@
|
|||
const xAxisMin = Math.max(0.01, minMultiple - padding); // Don't go below 0.01x
|
||||
const xAxisMax = Math.min(100, maxMultiple + padding); // Cap at 100x max
|
||||
|
||||
// Debug logging for chart range
|
||||
console.log('Chart x-axis range:', { xAxisMin, xAxisMax });
|
||||
console.log('Bar positions:', barPositions);
|
||||
console.log('Bar widths:', barWidths);
|
||||
console.log('ETH values:', positionKeys.map(key => positions[key].eth));
|
||||
console.log('KRAIKEN values:', positionKeys.map(key => positions[key].kraiken));
|
||||
console.log('ETH trace y values (with min):', ethTrace.y);
|
||||
console.log('KRAIKEN trace y values (with min):', kraikenTrace.y);
|
||||
|
||||
// Calculate max values for proper y-axis alignment
|
||||
const maxEth = Math.max(...positionKeys.map(key => positions[key].eth));
|
||||
|
|
@ -692,17 +773,6 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Debug logging for very small or large values
|
||||
if (totalLiquidity < 1 || tickRange > 10000 || pos.tickLower > 180000) {
|
||||
console.log(`Warning: ${key} position has unusual values:`, {
|
||||
liquidity: pos.liquidity,
|
||||
tickRange: tickRange,
|
||||
totalLiquidity: totalLiquidity,
|
||||
ticks: [pos.tickLower, pos.tickUpper],
|
||||
lowerMultiple: pos.lowerMultiple,
|
||||
upperMultiple: pos.upperMultiple
|
||||
});
|
||||
}
|
||||
|
||||
// Create a filled area for each position to show its exact range
|
||||
// Cap display coordinates to keep within visible range
|
||||
|
|
@ -1027,7 +1097,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
function createSummaryPanel(positions, currentTick, token0isWeth) {
|
||||
function createSummaryPanel(positions, currentTick, token0isWeth, precedingAction, index) {
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'summary-panel';
|
||||
|
||||
|
|
@ -1047,12 +1117,13 @@
|
|||
// Add total summary
|
||||
const totalItem = document.createElement('div');
|
||||
totalItem.className = 'summary-item';
|
||||
totalItem.innerHTML = `
|
||||
const totalHtml = `
|
||||
<strong>Total Portfolio</strong><br>
|
||||
Token ETH: ${totalEth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
||||
Token KRAIKEN: ${totalKraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
||||
Uniswap V3 Liquidity: ${totalUniV3Liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}
|
||||
ETH: ${totalEth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
||||
KRAIKEN: ${totalKraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
||||
Uniswap V3 Liquidity: ${totalUniV3Liquidity.toExponential(2)}
|
||||
`;
|
||||
totalItem.innerHTML = totalHtml;
|
||||
grid.appendChild(totalItem);
|
||||
|
||||
// Add position summaries
|
||||
|
|
@ -1070,8 +1141,8 @@
|
|||
<strong>${pos.name} Position</strong><br>
|
||||
ETH: ${pos.eth.toLocaleString(undefined, {minimumFractionDigits: 6, maximumFractionDigits: 6})}<br>
|
||||
KRAIKEN: ${pos.kraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}<br>
|
||||
Uniswap V3 Liquidity: ${pos.liquidity.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} (${liquidityPercent}%)<br>
|
||||
Range: ${lowerMultiple.toFixed(3)}x - ${upperMultiple.toFixed(3)}x
|
||||
Liquidity: ${pos.liquidity.toExponential(2)} (${liquidityPercent}%)<br>
|
||||
Ticks: [${pos.tickLower.toLocaleString()}, ${pos.tickUpper.toLocaleString()}]
|
||||
`;
|
||||
grid.appendChild(item);
|
||||
});
|
||||
|
|
@ -1079,10 +1150,26 @@
|
|||
// Add current price info
|
||||
const priceItem = document.createElement('div');
|
||||
priceItem.className = 'summary-item';
|
||||
|
||||
// Calculate current price
|
||||
const currentPrice = tickToPrice(currentTick);
|
||||
let ethPriceInKraiken, kraikenPriceInEth;
|
||||
|
||||
if (token0isWeth) {
|
||||
// price = KRAIKEN/ETH
|
||||
ethPriceInKraiken = currentPrice;
|
||||
kraikenPriceInEth = 1 / currentPrice;
|
||||
} else {
|
||||
// price = ETH/KRAIKEN
|
||||
kraikenPriceInEth = currentPrice;
|
||||
ethPriceInKraiken = 1 / currentPrice;
|
||||
}
|
||||
|
||||
priceItem.innerHTML = `
|
||||
<strong>Current Price</strong><br>
|
||||
Tick: ${currentTick}<br>
|
||||
<small>Price line shown in red</small>
|
||||
Tick: ${currentTick.toLocaleString()}<br>
|
||||
1 ETH = ${ethPriceInKraiken.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} KRAIKEN<br>
|
||||
1 KRAIKEN = ${kraikenPriceInEth.toExponential(3)} ETH
|
||||
`;
|
||||
grid.appendChild(priceItem);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue