diff --git a/README.md b/README.md index 8002609..db375cd 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # CoverMax ๐Ÿ›ก๏ธ -*A decentralized insurance protocol powered by tradeable risk tokens* +_A decentralized insurance protocol powered by tradeable risk tokens_ ## ๐ŸŽฎ Quick Start for Testing Want to try out CoverMax? Use this test wallet that already has test tokens: **Test Private Key:** + ``` 994fc012e651448eacdba62f2e49c17796e44aac67155e512894c23ddbf3e1fd ``` This wallet is pre-funded with test tokens (aUSDC, cUSDT) that you can use to: + - Deposit into insurance pools - Receive and trade risk tokens - Submit test claims @@ -33,13 +35,13 @@ Watch CoverMax in action: https://youtu.be/CvOf5WiP0co ### The Insurance Mechanism -1. **Deposit Phase** (2 days): Users deposit yield-bearing assets (aUSDC, cUSDT) into insurance pools +1. **Continuous Deposits**: Users can deposit yield-bearing assets (aUSDC, cUSDT) at any time during the protocol lifecycle 2. **Token Issuance**: For each deposit, users receive equal amounts of: - **Senior Risk Tokens** (CM-SENIOR) - Lower risk, priority claims - **Junior Risk Tokens** (CM-JUNIOR) - Higher risk, subordinate claims -3. **Trading Phase**: Risk tokens can be traded on Uniswap like any ERC20 token -4. **Coverage Phase** (3 days): Active insurance period where claims can be submitted -5. **Redemption Phase**: Token holders can redeem remaining tokens for underlying assets +3. **Trading**: Risk tokens can be traded on Uniswap like any ERC20 token throughout all phases +4. **Active Phase** (5 days): Combined deposit and coverage period where claims can be submitted +5. **Flexible Redemption**: Token holders can redeem tokens at any time with phase-specific rules ### The Risk-Insurance Relationship @@ -52,6 +54,7 @@ The core innovation is that **selling risk tokens = providing insurance coverage ### Example Scenarios #### Risk Tier Trading (Advanced Strategy) + ``` 1. Alice deposits 1000 aUSDC โ†’ receives 500 CM-SENIOR + 500 CM-JUNIOR tokens 2. Bob deposits 1000 cUSDT โ†’ receives 500 CM-SENIOR + 500 CM-JUNIOR tokens @@ -76,48 +79,58 @@ The core innovation is that **selling risk tokens = providing insurance coverage - **aUSDC**: Aave interest-bearing USDC - **cUSDT**: Compound interest-bearing USDT -- *Extensible to other yield-bearing assets* +- _Extensible to other yield-bearing assets_ ### Risk Token Tiers -| Token Type | Risk Level | Claim Priority | Use Case | -|------------|------------|----------------|----------| +| Token Type | Risk Level | Claim Priority | Use Case | +| ---------- | ---------- | -------------- | -------------------------------- | | CM-SENIOR | Lower | First claims | Conservative insurance providers | -| CM-JUNIOR | Higher | Subordinate | Risk-seeking yield farmers | +| CM-JUNIOR | Higher | Subordinate | Risk-seeking yield farmers | ## ๐Ÿ”„ Protocol Lifecycle -### Phase 1: Deposit Period (2 days) -- Users deposit yield-bearing assets +### Phase 1: Active Period (5 days) + +- Combined deposit and coverage period for maximum flexibility +- Users can deposit yield-bearing assets at any time during the protocol lifecycle +- Active insurance coverage for all deposited assets - Dual-tier risk tokens are minted 1:1 with deposits - Tokens can be traded immediately on Uniswap - -### Phase 2: Coverage Period (5 days) -- Active insurance coverage for deposited assets +- Withdrawals require equal amounts of senior and junior tokens - Claims can be submitted and processed - Risk tokens continue trading on secondary markets -### Phase 3: Senior Claims (1 day) +### Phase 2: Claims Period (1 day) + - Priority redemption period for senior token holders - Senior tokens have first claim on remaining assets +- Any combination of senior/junior tokens can be withdrawn (in normal mode) +- In emergency mode: only senior token withdrawals allowed +- Deposits continue to be accepted + +### Phase 3: Final Claims (1 day) -### Phase 4: Final Claims (1 day) -- All remaining tokens can be redeemed +- All remaining tokens can be redeemed with any combination of senior/junior +- Deposits continue to be accepted - Protocol cycle completes and can restart ## ๐Ÿ’ฐ Economic Model ### For Insurance Providers + - **Deposit** yield-bearing assets to earn insurance premiums - **Hold tokens** to bear risk and earn yield from claims that don't materialize - **Sell tokens** to reduce risk exposure and lock in profits ### For Risk Traders + - **Buy risk tokens** on Uniswap to speculate on insurance outcomes - **Sell risk tokens** to exit positions or reduce exposure - **Arbitrage** between risk levels and market pricing ### For Insurance Seekers + - **Submit claims** backed by evidence when covered events occur - **Receive payouts** from the insurance pool when claims are approved - **Benefit** from community-funded insurance coverage @@ -125,82 +138,25 @@ The core innovation is that **selling risk tokens = providing insurance coverage ## ๐Ÿฆ„ Uniswap Integration ### Why Uniswap? + Risk tokens are standard ERC20 tokens that can be traded on any DEX. Uniswap provides: + - **Liquidity**: Deep markets for risk token trading - **Price Discovery**: Market-driven risk pricing - **Accessibility**: Anyone can buy/sell insurance risk - **Composability**: Risk tokens can be used in other DeFi protocols -### Trading Strategy Examples - -#### 1. Risk Tier Arbitrage (CM-SENIOR โ†” CM-JUNIOR Pool) -```solidity -// Strategy: Trade down risk by converting junior to senior tokens -// Bob wants more downside protection, less upside exposure -uniswapRouter.swapExactTokensForTokens( - 200 * 1e18, // Sell 200 CM-JUNIOR tokens - 190 * 1e18, // Expect ~190 CM-SENIOR tokens (forfeit some upside) - [juniorToken, seniorToken], - msg.sender, - deadline -); - -// Result: Bob now has more senior tokens (priority claims) -// but less junior tokens (subordinate claims) -``` - -#### 2. Complete Risk Exit (Risk Token โ†’ Stablecoin) -```solidity -// Strategy: Exit insurance position entirely -// Sell risk tokens for stablecoins to eliminate exposure -uniswapRouter.swapExactTokensForTokens( - 250 * 1e18, // Sell 250 CM-SENIOR tokens - minAmountOut, - [seniorToken, USDC], - msg.sender, - deadline -); -``` - -#### 3. Risk Speculation (Stablecoin โ†’ Risk Token) -```solidity -// Strategy: Buy underpriced risk for potential yield -// Charlie thinks insurance claims are unlikely -uniswapRouter.swapExactTokensForTokens( - 1000 * 1e6, // Spend 1000 USDC - minTokensOut, - [USDC, juniorToken], - msg.sender, - deadline -); -``` - -## ๐Ÿ”ง Technical Implementation - -### Smart Contract Features - -- **Reentrancy Protection**: All external functions protected against reentrancy attacks -- **Phase-Based Logic**: Automated lifecycle management with time-based phases -- **Proportional Redemption**: Fair distribution of assets based on token holdings -- **Emergency Pause**: Owner can pause protocol in emergencies -- **Claim Processing**: Structured insurance claim submission and approval system - -### Security Considerations - -- **Audited OpenZeppelin contracts** for standard functionality -- **Custom errors** for gas-efficient error handling -- **Precision math** using 27-decimal precision for accurate calculations -- **Access controls** with owner-only administrative functions - ## ๐Ÿ“Š Key Metrics ### Protocol Health + - **Total Value Locked (TVL)**: Combined value of all deposited assets - **Insurance Coverage Ratio**: Amount of active insurance coverage - **Token Distribution**: Spread of risk across token holders - **Claim Success Rate**: Percentage of approved vs submitted claims ### Market Dynamics + - **Risk Token Price**: Market valuation of insurance risk - **Liquidity Depth**: Available trading volume on Uniswap - **Volatility**: Price stability of risk tokens @@ -237,11 +193,13 @@ npx hardhat test All tests should pass. For troubleshooting, ensure you are using a compatible Node.js version and Hardhat is installed. # Deploy to local network + npx hardhat ignition deploy ignition/modules/RiskToken.ts --network localhost ## ๐Ÿค Contributing Contributions welcome! This project includes: + - **Hardhat development environment** - **TypeScript support** - **Comprehensive test suite** @@ -257,4 +215,4 @@ For commercial licensing inquiries, contact: legal@covermax.fi --- -*CoverMax: Phase-based risk management with dual-tier tokens* +_CoverMax: Phase-based risk management with dual-tier tokens_ diff --git a/contracts/RiskVault.sol b/contracts/RiskVault.sol index 3326ebe..b3a0e12 100644 --- a/contracts/RiskVault.sol +++ b/contracts/RiskVault.sol @@ -12,10 +12,9 @@ import "./IRiskToken.sol"; contract RiskVault is Ownable, ReentrancyGuard { /* Protocol Phases */ enum Phase { - DEPOSIT, // Phase 1: Deposit Period (2 days) - COVERAGE, // Phase 2: Active Coverage Period (3 days) - CLAIMS, // Phase 3: Claims Period (1 day) - FINAL_CLAIMS // Phase 4: Final Claims Period (1 day) + ACTIVE, // Phase 1: Active Period - Deposits and Coverage (5 days) + CLAIMS, // Phase 2: Claims Period (1 day) + FINAL_CLAIMS // Phase 3: Final Claims Period (1 day) } /* Core Protocol Assets */ @@ -26,9 +25,8 @@ contract RiskVault is Ownable, ReentrancyGuard { /* Protocol Constants */ uint256 private constant MIN_DEPOSIT_AMOUNT = 10; // Minimum deposit threshold - uint256 private constant DEPOSIT_PHASE_DURATION = 2 days; - uint256 private constant COVERAGE_PHASE_DURATION = 3 days; - uint256 private constant SENIOR_CLAIMS_DURATION = 1 days; + uint256 private constant ACTIVE_PHASE_DURATION = 5 days; // Combined deposit + coverage period + uint256 private constant CLAIMS_PHASE_DURATION = 1 days; uint256 private constant FINAL_CLAIMS_DURATION = 1 days; /* Protocol State */ @@ -85,13 +83,13 @@ contract RiskVault is Ownable, ReentrancyGuard { seniorToken = address(_seniorToken); juniorToken = address(_juniorToken); - // Initialize lifecycle - start in DEPOSIT phase - currentPhase = Phase.DEPOSIT; + // Initialize lifecycle - start in ACTIVE phase + currentPhase = Phase.ACTIVE; phaseStartTime = block.timestamp; cycleStartTime = block.timestamp; emit CycleStarted(1, block.timestamp); - emit PhaseTransitioned(0, uint8(Phase.DEPOSIT), block.timestamp); + emit PhaseTransitioned(0, uint8(Phase.ACTIVE), block.timestamp); } /* Access Control Modifiers */ @@ -103,7 +101,7 @@ contract RiskVault is Ownable, ReentrancyGuard { modifier onlyDuringPhase(Phase requiredPhase) { _updatePhaseIfNeeded(); if (currentPhase != requiredPhase) { - if (requiredPhase == Phase.DEPOSIT) revert InvalidPhaseForDeposit(); + if (requiredPhase == Phase.ACTIVE) revert InvalidPhaseForDeposit(); else revert InvalidPhaseForWithdrawal(); } _; @@ -125,15 +123,11 @@ contract RiskVault is Ownable, ReentrancyGuard { uint256 timeElapsed = block.timestamp - phaseStartTime; Phase oldPhase = currentPhase; - if (currentPhase == Phase.DEPOSIT && timeElapsed >= DEPOSIT_PHASE_DURATION) { - currentPhase = Phase.COVERAGE; - phaseStartTime = block.timestamp; - emit PhaseTransitioned(uint8(oldPhase), uint8(currentPhase), block.timestamp); - } else if (currentPhase == Phase.COVERAGE && timeElapsed >= COVERAGE_PHASE_DURATION) { + if (currentPhase == Phase.ACTIVE && timeElapsed >= ACTIVE_PHASE_DURATION) { currentPhase = Phase.CLAIMS; phaseStartTime = block.timestamp; emit PhaseTransitioned(uint8(oldPhase), uint8(currentPhase), block.timestamp); - } else if (currentPhase == Phase.CLAIMS && timeElapsed >= SENIOR_CLAIMS_DURATION) { + } else if (currentPhase == Phase.CLAIMS && timeElapsed >= CLAIMS_PHASE_DURATION) { currentPhase = Phase.FINAL_CLAIMS; phaseStartTime = block.timestamp; emit PhaseTransitioned(uint8(oldPhase), uint8(currentPhase), block.timestamp); @@ -153,11 +147,7 @@ contract RiskVault is Ownable, ReentrancyGuard { function forcePhaseTransitionImmediate() external onlyOwner { Phase oldPhase = currentPhase; - if (currentPhase == Phase.DEPOSIT) { - currentPhase = Phase.COVERAGE; - phaseStartTime = block.timestamp; - emit PhaseTransitioned(uint8(oldPhase), uint8(currentPhase), block.timestamp); - } else if (currentPhase == Phase.COVERAGE) { + if (currentPhase == Phase.ACTIVE) { currentPhase = Phase.CLAIMS; phaseStartTime = block.timestamp; emit PhaseTransitioned(uint8(oldPhase), uint8(currentPhase), block.timestamp); @@ -166,7 +156,7 @@ contract RiskVault is Ownable, ReentrancyGuard { phaseStartTime = block.timestamp; emit PhaseTransitioned(uint8(oldPhase), uint8(currentPhase), block.timestamp); } - // Note: FINAL_CLAIMS requires startNewCycle() to reset to DEPOSIT + // Note: FINAL_CLAIMS requires startNewCycle() to reset to ACTIVE } /** @@ -181,7 +171,7 @@ contract RiskVault is Ownable, ReentrancyGuard { if (timeElapsed < FINAL_CLAIMS_DURATION) revert PhaseTransitionNotReady(); Phase oldPhase = currentPhase; - currentPhase = Phase.DEPOSIT; + currentPhase = Phase.ACTIVE; phaseStartTime = block.timestamp; cycleStartTime = block.timestamp; @@ -310,18 +300,17 @@ contract RiskVault is Ownable, ReentrancyGuard { // Core Vault Functions /** - * @dev Deposits yield-bearing assets to get CM tokens (only during DEPOSIT phase) + * @dev Deposits yield-bearing assets to get CM tokens (can deposit at any time) * @param asset The yield-bearing asset to deposit (aUSDC or cUSDT) * @param depositAmount Amount of asset to deposit */ function depositAsset(address asset, uint256 depositAmount) external - onlyDuringPhase(Phase.DEPOSIT) whenNotEmergency nonReentrant { if (depositAmount <= MIN_DEPOSIT_AMOUNT) revert InsufficientDepositAmount(); - if (depositAmount & 1 != 0) revert UnevenDepositAmount(); // Must be even for equal token split + if ((depositAmount & 1) != 0) revert UnevenDepositAmount(); // Must be even for equal token split if (!_isAssetSupported(asset)) revert UnsupportedAsset(); // Transfer asset from depositor @@ -344,7 +333,10 @@ contract RiskVault is Ownable, ReentrancyGuard { /** - * @dev Withdraws tokens during any phase with specific conditions + * @dev Withdraws tokens at any time with phase-specific conditions: + * - ACTIVE phase: Requires equal senior and junior amounts + * - CLAIMS phase (emergency): Only senior tokens allowed + * - CLAIMS phase (normal)/FINAL_CLAIMS: Any token combination allowed * @param seniorAmount Amount of senior tokens to withdraw * @param juniorAmount Amount of junior tokens to withdraw * @param preferredAsset Optional: specific asset to withdraw (address(0) for proportional split) @@ -364,13 +356,13 @@ contract RiskVault is Ownable, ReentrancyGuard { if (currentPhase == Phase.CLAIMS && emergencyMode) { // During senior claims in emergency mode, only senior tokens allowed if (juniorAmount > 0) revert OnlySeniorTokensAllowed(); - } else if (currentPhase != Phase.CLAIMS && currentPhase != Phase.FINAL_CLAIMS) { - // During DEPOSIT and COVERAGE phases, require equal amounts + } else if (currentPhase == Phase.ACTIVE) { + // During ACTIVE phase, require equal amounts if (seniorAmount != juniorAmount) { revert EqualAmountsRequired(); } } - // During SENIOR_CLAIMS (non-emergency) and FINAL_CLAIMS phases, any combination is allowed + // During CLAIMS (non-emergency) and FINAL_CLAIMS phases, any combination is allowed uint256 aUSDCAmount; uint256 cUSDTAmount; @@ -558,7 +550,7 @@ contract RiskVault is Ownable, ReentrancyGuard { * @dev Gets protocol status including current phase * @return emergency Whether emergency mode is active * @return totalTokens Total CM tokens issued - * @return phase Current protocol phase (0=DEPOSIT, 1=COVERAGE, 2=SENIOR_CLAIMS, 3=FINAL_CLAIMS) + * @return phase Current protocol phase (0=ACTIVE, 1=CLAIMS, 2=FINAL_CLAIMS) * @return phaseEndTime When current phase ends */ function getProtocolStatus() external view returns ( @@ -568,12 +560,10 @@ contract RiskVault is Ownable, ReentrancyGuard { uint256 phaseEndTime ) { uint256 phaseDuration; - if (currentPhase == Phase.DEPOSIT) { - phaseDuration = DEPOSIT_PHASE_DURATION; - } else if (currentPhase == Phase.COVERAGE) { - phaseDuration = COVERAGE_PHASE_DURATION; + if (currentPhase == Phase.ACTIVE) { + phaseDuration = ACTIVE_PHASE_DURATION; } else if (currentPhase == Phase.CLAIMS) { - phaseDuration = SENIOR_CLAIMS_DURATION; + phaseDuration = CLAIMS_PHASE_DURATION; } else { phaseDuration = FINAL_CLAIMS_DURATION; } @@ -600,12 +590,10 @@ contract RiskVault is Ownable, ReentrancyGuard { uint256 timeRemaining ) { uint256 phaseDuration; - if (currentPhase == Phase.DEPOSIT) { - phaseDuration = DEPOSIT_PHASE_DURATION; - } else if (currentPhase == Phase.COVERAGE) { - phaseDuration = COVERAGE_PHASE_DURATION; + if (currentPhase == Phase.ACTIVE) { + phaseDuration = ACTIVE_PHASE_DURATION; } else if (currentPhase == Phase.CLAIMS) { - phaseDuration = SENIOR_CLAIMS_DURATION; + phaseDuration = CLAIMS_PHASE_DURATION; } else { phaseDuration = FINAL_CLAIMS_DURATION; } diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..d826224 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,12 @@ +# AI Configuration +# Get your free Groq API key from https://console.groq.com/keys +VITE_GROQ_API_KEY=your_groq_api_key_here + +# Hedera Configuration (Optional - for advanced features) +# Get testnet credentials from https://portal.hedera.com/ +VITE_HEDERA_OPERATOR_ID=0.0.xxxxx +VITE_HEDERA_OPERATOR_KEY=your_private_key_here + +# Existing Configuration +VITE_PRIVY_APP_ID=your_privy_app_id +VITE_INFURA_API_KEY=your_infura_api_key diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e4737c6..8eb4f40 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,9 @@ "name": "vite_react_shadcn_ts", "version": "0.0.0", "dependencies": { + "@langchain/core": "^0.3.67", + "@langchain/groq": "^0.2.3", + "@langchain/openai": "^0.6.4", "@privy-io/react-auth": "^1.99.1", "@privy-io/wagmi-connector": "^0.1.13", "@radix-ui/react-accordion": "^1.2.0", @@ -41,9 +44,11 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", + "dotenv": "^17.2.1", "embla-carousel-react": "^8.3.0", "ethers": "^6.15.0", "input-otp": "^1.2.4", + "langchain": "^0.3.30", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", "react": "^18.3.1", @@ -97,9 +102,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -107,9 +112,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { @@ -117,13 +122,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz", - "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -145,19 +150,25 @@ } }, "node_modules/@babel/types": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz", - "integrity": "sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, "node_modules/@coinbase/wallet-sdk": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.0.3.tgz", @@ -1697,17 +1708,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1719,13 +1726,17 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -1735,15 +1746,208 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@langchain/core": { + "version": "0.3.67", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.67.tgz", + "integrity": "sha512-ATmkAj8V+FaHM2UYX+O9tdaKJr4eu8FSVPF6F6aaD+0Gq980Eza1qp15kV3jCpG+f5mGwkFToQixkT+SSmZJDg==", + "license": "MIT", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.3.46", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.25.32", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core/node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@langchain/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@langchain/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/core/node_modules/langsmith": { + "version": "0.3.53", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.53.tgz", + "integrity": "sha512-cnEcJEiYjbcFQy7vTCQb7sR4w70UtBCu9loCOON+yYcK6T1lVmx27lQ4AF2KY7xzKY+FhbxWms5PV3SocizzaQ==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/@langchain/core/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/groq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@langchain/groq/-/groq-0.2.3.tgz", + "integrity": "sha512-r+yjysG36a0IZxTlCMr655Feumfb4IrOyA0jLLq4l7gEhVyMpYXMwyE6evseyU2LRP+7qOPbGRVpGqAIK0MsUA==", + "license": "MIT", + "dependencies": { + "groq-sdk": "^0.19.0", + "zod": "^3.22.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, + "node_modules/@langchain/openai": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.6.4.tgz", + "integrity": "sha512-0B8AdImAjIgvGKnNN8URGVB4JUg61Of9EW+scZTS0OJOKjhdybe6ars/OwXkY9pacU3lMxIIKEMm9Op9DMaXiw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^5.3.0", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, + "node_modules/@langchain/openai/node_modules/openai": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.12.0.tgz", + "integrity": "sha512-vUdt02xiWgOHiYUmW0Hj1Qu9OKAiVQu5Bd547ktVCiMKC1BkB5L3ImeEnCyq3WpRKR6ZTaPgekzqdozwdPs7Lg==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@langchain/openai/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, "node_modules/@lit-labs/ssr-dom-shim": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", @@ -2101,6 +2305,17 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2329,6 +2544,18 @@ } } }, + "node_modules/@privy-io/react-auth/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/@privy-io/react-auth/node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -6713,6 +6940,16 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -6764,6 +7001,12 @@ "@types/react-router": "*" } }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, "node_modules/@types/stylis": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", @@ -9332,9 +9575,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -9365,7 +9608,6 @@ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", - "peer": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -9446,7 +9688,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-hidden": { @@ -9471,6 +9712,12 @@ "tslib": "^2.0.0" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -9585,9 +9832,9 @@ } }, "node_modules/bignumber.js": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", - "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { "node": "*" @@ -9744,6 +9991,15 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/bufferutil": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", @@ -9842,9 +10098,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001669", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", - "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "dev": true, "funding": [ { @@ -9866,7 +10122,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -10412,6 +10667,18 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -10428,6 +10695,15 @@ "dev": true, "license": "MIT" }, + "node_modules/console-table-printer": { + "version": "2.14.6", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.6.tgz", + "integrity": "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, "node_modules/cookie-es": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", @@ -10694,9 +10970,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -10777,6 +11053,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/derive-valtio": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/derive-valtio/-/derive-valtio-0.1.0.tgz", @@ -10833,9 +11118,9 @@ } }, "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -10992,6 +11277,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-toolkit": { "version": "1.39.3", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.3.tgz", @@ -11797,6 +12097,62 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -11977,6 +12333,36 @@ "dev": true, "license": "MIT" }, + "node_modules/groq-sdk": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/groq-sdk/-/groq-sdk-0.19.0.tgz", + "integrity": "sha512-vdh5h7ORvwvOvutA80dKF81b0gPWHxu6K/GOJBOM0n6p6CSqAVLhFfeS79Ef0j/yCycDR09jqY7jkYz9dLiS6w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/groq-sdk/node_modules/@types/node": { + "version": "18.19.121", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.121.tgz", + "integrity": "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/groq-sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, "node_modules/h3": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.3.tgz", @@ -11998,7 +12384,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12099,7 +12484,6 @@ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.0.0" } @@ -12567,6 +12951,15 @@ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "license": "MIT" }, + "node_modules/js-tiktoken": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.20.tgz", + "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12577,7 +12970,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -12642,6 +13034,15 @@ "license": "ISC", "peer": true }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", @@ -12673,6 +13074,155 @@ "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==", "license": "MIT" }, + "node_modules/langchain": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.30.tgz", + "integrity": "sha512-UyVsfwHDpHbrnWrjWuhJHqi8Non+Zcsf2kdpDTqyJF8NXrHBOpjdHT5LvPuW9fnE7miDTWf5mLcrWAGZgcrznQ==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.7.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.3.33", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cerebras": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.3.58 <0.4.0", + "@langchain/deepseek": "*", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/google-vertexai-web": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "@langchain/xai": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cerebras": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/deepseek": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/google-vertexai-web": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "@langchain/xai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/langchain/node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/langchain/node_modules/langsmith": { + "version": "0.3.53", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.53.tgz", + "integrity": "sha512-cnEcJEiYjbcFQy7vTCQb7sR4w70UtBCu9loCOON+yYcK6T1lVmx27lQ4AF2KY7xzKY+FhbxWms5PV3SocizzaQ==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/langchain/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -13420,6 +13970,15 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -13432,9 +13991,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -13478,6 +14037,26 @@ "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -13620,6 +14199,12 @@ "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -13734,6 +14319,15 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -13766,6 +14360,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -14627,6 +15262,15 @@ "node": ">=4" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14926,6 +15570,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-wcswidth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", + "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", + "license": "MIT" + }, "node_modules/sonic-boom": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", @@ -14954,6 +15604,31 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", @@ -15215,7 +15890,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -15298,6 +15972,36 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/text-encoding-utf-8": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", @@ -15412,9 +16116,9 @@ "license": "Apache-2.0" }, "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/tweetnacl": { @@ -16043,6 +16747,15 @@ } } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/web3-core": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.4.tgz", @@ -16633,14 +17346,23 @@ } }, "node_modules/zod": { - "version": "3.25.74", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.74.tgz", - "integrity": "sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/zustand": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4df0ea6..e6c9c40 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,9 @@ "preview": "vite preview" }, "dependencies": { + "@langchain/core": "^0.3.67", + "@langchain/groq": "^0.2.3", + "@langchain/openai": "^0.6.4", "@privy-io/react-auth": "^1.99.1", "@privy-io/wagmi-connector": "^0.1.13", "@radix-ui/react-accordion": "^1.2.0", @@ -44,9 +47,11 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", + "dotenv": "^17.2.1", "embla-carousel-react": "^8.3.0", "ethers": "^6.15.0", "input-otp": "^1.2.4", + "langchain": "^0.3.30", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", "react": "^18.3.1", diff --git a/frontend/public/New_CBT_Standalone.png b/frontend/public/New_CBT_Standalone.png new file mode 100644 index 0000000..317c57f Binary files /dev/null and b/frontend/public/New_CBT_Standalone.png differ diff --git a/frontend/public/UCLIE_logo_black.png b/frontend/public/UCLIE_logo_black.png new file mode 100644 index 0000000..ed12153 Binary files /dev/null and b/frontend/public/UCLIE_logo_black.png differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d7d6d33..a21aa4e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,9 +9,10 @@ import React from "react"; import Index from "./pages/Index"; import Dashboard from "./pages/Dashboard"; import Insurance from "./pages/Insurance"; -import Advanced from "./pages/Advanced"; import Admin from "./pages/Admin"; +import WidgetDemo from "./pages/WidgetDemo"; import NotFound from "./pages/NotFound"; +import { Navigate } from "react-router-dom"; const AppProviders: React.FC<{ children: React.ReactNode }> = React.memo(({ children }) => ( @@ -30,8 +31,9 @@ const App: React.FC = () => { } /> } /> } /> - } /> + } /> } /> + } /> } /> diff --git a/frontend/src/components/ActionCard.tsx b/frontend/src/components/ActionCard.tsx deleted file mode 100644 index 5c2cf58..0000000 --- a/frontend/src/components/ActionCard.tsx +++ /dev/null @@ -1,147 +0,0 @@ - -import React, { useState } from 'react'; -import { Card, CardContent, CardFooter } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Slider } from '@/components/ui/slider'; - -interface ActionCardProps { - title: string; - description: string; - buttonText: string; - isWithdraw?: boolean; - isPercentage?: boolean; - maxValue: number; - onAction: ((amount: number) => void) | (() => void); - disabled?: boolean; -} - -const ActionCard: React.FC = ({ - title, - description, - buttonText, - isWithdraw = false, - isPercentage = false, - maxValue, - onAction, - disabled = false, -}) => { - const [amount, setAmount] = useState(0); - const [percentage, setPercentage] = useState(0); - - const handleSliderChange = (value: number[]) => { - const newValue = value[0]; - setPercentage(newValue); - - if (isPercentage) { - setAmount(newValue); - } else { - setAmount((maxValue * newValue) / 100); - } - }; - - const handleInputChange = (e: React.ChangeEvent) => { - const value = parseFloat(e.target.value) || 0; - - if (isPercentage) { - setAmount(Math.min(value, 100)); - setPercentage(Math.min(value, 100)); - } else { - setAmount(Math.min(value, maxValue)); - setPercentage((value / maxValue) * 100); - } - }; - - const handleMax = () => { - setAmount(isPercentage ? 100 : maxValue); - setPercentage(100); - }; - - const handleHalf = () => { - setAmount(isPercentage ? 50 : maxValue / 2); - setPercentage(50); - }; - - const handleAction = () => { - // Try to call as a function with amount parameter first - try { - (onAction as (amount: number) => void)(amount); - } catch { - // If that fails, try calling as a function with no parameters - (onAction as () => void)(); - } - setAmount(0); - setPercentage(0); - }; - - return ( - - -
- - - {isPercentage ? '%' : '$'} - -
- -
-
- 0% - 50% - 100% -
- -
- -
- - -
-
- - - -
- ); -}; - -export default ActionCard; diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 7b04d78..cfe8e98 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -11,7 +11,7 @@ const Navbar: React.FC = () => { // Helper function to determine if a route is active const isActive = (path: string) => location.pathname === path; - + // Format address for display const formatAddress = (addr: string) => { return `${addr.slice(0, 6)}...${addr.slice(-4)}`; @@ -33,17 +33,10 @@ const Navbar: React.FC = () => { - + + + ); + } + + return ( + + + + {protocolLogo} +
+
{protocolName} Prediction Market
+
Real betting with risk tokens
+
+
+
+ + + {/* Protocol Status */} +
+
+
+ + {vaultInfo.emergencyMode ? 'Emergency Mode Active' : 'Live Betting Available'} + + + Phase: {getPhaseNameFromBigInt(vaultInfo.currentPhase)} + +
+
+ + {/* Question */} +
+

+ Will {protocolName} get exploited? +

+

+ Real betting with risk tokens โ€ข Earn up to {currentOdds.safe.toFixed(2)}x returns +

+
+ + {/* AI Analysis Button or Results */} + {!showAIAnalysis ? ( + + ) : aiAnalysis && ( +
+
+
+ + + AI Recommends: {aiAnalysis.recommendation === 'hack' ? 'HACK' : 'SAFE'} + +
+
+ Confidence: + {aiAnalysis.confidence}% +
+
+ + {/* Expected Value */} +
+
+ Expected Value: + 0 ? 'text-green-400' : 'text-red-400' + }`}> + {aiAnalysis.expectedValue > 0 ? '+' : ''}{(aiAnalysis.expectedValue * 100).toFixed(1)}% + +
+
+ + {/* Key Reasoning Points */} +
+ {aiAnalysis.reasoning.slice(0, 3).map((reason, index) => ( +
+ + {reason} +
+ ))} +
+ + {/* Portfolio Context Badge */} + {walletAnalysis && ( +
+
+ + Portfolio Context +
+
+ Risk Profile: {walletAnalysis.riskExposure} +
+
+ Total Portfolio: ${walletAnalysis.totalValue.toFixed(0)} +
+
+ )} + + +
+ )} + + {/* Asset Selection */} +
+ +
+ {supportedAssets.map((asset) => ( + + ))} +
+
+ + {/* Betting Options */} +
+ {/* YES - Gets Hacked (Safety Strategy) */} + + + {/* NO - Stays Safe (Upside Strategy) */} + +
+ + {/* Bet Amount Input */} +
+ +
+ setBetAmount(e.target.value)} + min={minBet} + max={maxPayout} + className="bg-slate-700/50 border-slate-600 text-white pr-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> + +
+
+ Available: {formatTokenAmount(balances[assetType])} + Min: {minBet} +
+
+ + {/* Enhanced Cost/Payout Breakdown */} + {selectedBet && betAmount && parseFloat(betAmount) > 0 && ( +
+
+ + What You Pay, What You Get +
+ + {(() => { + const shareInfo = calculateShareInfo(betAmount, selectedBet); + return ( +
+ {/* Cost Breakdown */} +
+
+ Cost per share: + ${shareInfo.costPerShare} +
+
+ Number of shares: + {shareInfo.numShares} +
+
+ ${betAmount} รท ${shareInfo.costPerShare} = {shareInfo.numShares} shares +
+
+ + {/* Payout Breakdown */} +
+
+ If you win: + ${shareInfo.totalPayout} +
+
+ Profit/Loss: + {(() => { + const profit = parseFloat(shareInfo.totalPayout) - parseFloat(betAmount); + const isProfit = profit >= 0; + return ( + + {isProfit ? '+' : ''}${profit.toFixed(2)} + + ); + })()} +
+
+ {shareInfo.numShares} shares ร— $1.00 each = ${shareInfo.totalPayout} +
+
+ + {/* Risk Explanation */} +
1.0 + ? 'text-red-300 bg-red-900/30' + : 'text-slate-400 bg-slate-800/50' + }`}> + How it works: Each share pays $1.00 if your prediction is correct, $0.00 if wrong. + You're buying {shareInfo.numShares} shares at ${shareInfo.costPerShare} each. + {parseFloat(shareInfo.costPerShare) > 1.0 && ( +
+ โš ๏ธ Warning: You're paying more than $1.00 per share that only pays $1.00 - this bet would lose money even if you win! +
+ )} +
+ + {/* Strategy note */} +
+ Strategy: {selectedBet === 'hack' ? 'MAX SAFETY (Senior tokens)' : 'MAX UPSIDE (Junior tokens)'} +
+
+ ); + })()} +
+ )} + + {/* Action Button */} + + + {/* Portfolio Summary */} +
+
+ + Your Portfolio +
+ + {/* Basic Position Info */} +
+
+ Senior Tokens: + + {formatTokenAmount(balances.seniorTokens)} + {Number(balances.seniorTokens) > 0 && ( + + (${(Number(balances.seniorTokens) / 1e18 * parseFloat(seniorPrice)).toFixed(2)}) + + )} + +
+
+ Junior Tokens: + + {formatTokenAmount(balances.juniorTokens)} + {Number(balances.juniorTokens) > 0 && ( + + (${(Number(balances.juniorTokens) / 1e18 * parseFloat(juniorPrice)).toFixed(2)}) + + )} + +
+
+ Available {assetType}: + {formatTokenAmount(balances[assetType])} +
+
+ +
+ AI analyzes your wallet + market conditions for optimal bets +
+
+ + {/* Stats Footer */} +
+
+ + Ends in {timeframe} +
+
+ + Fixed odds โ€ข Real positions +
+
+
+
+ ); +}; + +export default PredictionMarketWidget; diff --git a/frontend/src/components/QuickTrade.tsx b/frontend/src/components/QuickTrade.tsx index 4810e1f..aec8f68 100644 --- a/frontend/src/components/QuickTrade.tsx +++ b/frontend/src/components/QuickTrade.tsx @@ -6,11 +6,11 @@ import { Label } from '@/components/ui/label'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { useWeb3 } from '@/context/PrivyWeb3Context'; import { Shield, TrendingUp, Scale, Zap, DollarSign, AlertCircle } from 'lucide-react'; -import { Phase, ContractName, getContractAddress, SupportedChainId } from '@/config/contracts'; +import { Phase, ContractName, getContractAddress, SupportedChainId, getPhaseNameFromBigInt } from '@/config/contracts'; import SmartLiquiditySuggestion from './SmartLiquiditySuggestion'; import { ethers } from 'ethers'; -type TradeIntent = 'safety' | 'upside' | 'equalize' | 'fullCoverage' | 'fullRisk' | 'balanced' | 'maxSafety' | 'maxUpside' | 'addLiquidity'; +type TradeIntent = 'safety' | 'upside' | 'equalize' | 'fullCoverage' | 'fullRisk' | 'balanced' | 'maxSafety' | 'maxUpside' | 'stakeRiskTokens'; const QuickTrade: React.FC = () => { const { @@ -22,7 +22,7 @@ const QuickTrade: React.FC = () => { swapExactTokensForTokens, getAmountsOut, depositAsset, - addLiquidity, + stakeRiskTokens, refreshData, getPairReserves, getTokenBalance, @@ -197,7 +197,7 @@ const QuickTrade: React.FC = () => { console.log(`Adding liquidity: ${seniorAmountString} SENIOR + ${juniorAmountString} JUNIOR`); console.log(`Pool ratio: ${poolRatio.toFixed(6)} (${juniorReserve}/${seniorReserve})`); - await addLiquidity(seniorAmountString, juniorAmountString, seniorTokenAddress!, juniorTokenAddress!); + await stakeRiskTokens(seniorAmountString, juniorAmountString, seniorTokenAddress!, juniorTokenAddress!); setShowLiquiditySuggestion(false); } else { alert('Insufficient tokens to add meaningful liquidity while maintaining pool ratio.'); @@ -206,7 +206,7 @@ const QuickTrade: React.FC = () => { alert('Cannot determine pool ratio. Pool may be empty.'); } } catch (error) { - console.error('Add liquidity failed:', error); + console.error('Stake risk tokens failed:', error); alert('Adding liquidity failed. Please try again.'); } finally { setIsExecuting(false); @@ -294,15 +294,13 @@ const QuickTrade: React.FC = () => { - {/* Deposit Phase Check */} - {Number(vaultInfo.currentPhase) !== Phase.DEPOSIT && ( - - - - Deposits are only allowed during the Deposit phase. Current phase: {vaultInfo.currentPhase !== undefined ? Phase[vaultInfo.currentPhase] : 'Loading...'} - - - )} + {/* Phase Info */} + + + + Current phase: {getPhaseNameFromBigInt(vaultInfo.currentPhase)}. Deposits and withdrawals allowed at any time. + + {/* Deposit Action Buttons */}
@@ -310,7 +308,7 @@ const QuickTrade: React.FC = () => {
- - - ); -}; - -export default React.memo(QuickTrade); \ No newline at end of file diff --git a/frontend/src/components/dashboard/AdvancedFeatures.tsx b/frontend/src/components/dashboard/AdvancedFeatures.tsx new file mode 100644 index 0000000..8fbaa24 --- /dev/null +++ b/frontend/src/components/dashboard/AdvancedFeatures.tsx @@ -0,0 +1,324 @@ +import React, { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { + Minus, + RefreshCw, + Info, + Plus, +} from 'lucide-react'; + +interface AdvancedFeaturesProps { + seniorBalance: number; + juniorBalance: number; + lpBalance: number; + formatNumber: (num: number, decimals?: number) => string; + isExecuting: boolean; + vaultInfo: { emergencyMode: boolean }; + onStakeRiskTokens: (seniorAmount: string, juniorAmount: string) => void; + onUnstakeRiskTokens: (amount: string) => void; + onEmergencyWithdraw: (amount: string, asset: 'aUSDC' | 'cUSDT') => void; +} + +const AdvancedFeatures: React.FC = ({ + seniorBalance, + juniorBalance, + lpBalance, + formatNumber, + isExecuting, + vaultInfo, + onStakeRiskTokens, + onUnstakeRiskTokens, + onEmergencyWithdraw, +}) => { + const [stakingSeniorAmount, setStakingSeniorAmount] = useState(''); + const [stakingJuniorAmount, setStakingJuniorAmount] = useState(''); + const [unstakeAmount, setUnstakeAmount] = useState(''); + const [emergencyAmount, setEmergencyAmount] = useState(''); + const [preferredAsset, setPreferredAsset] = useState<'aUSDC' | 'cUSDT'>('aUSDC'); + + const handleStakeRiskTokens = () => { + if (!stakingSeniorAmount || !stakingJuniorAmount) return; + onStakeRiskTokens(stakingSeniorAmount, stakingJuniorAmount); + setStakingSeniorAmount(''); + setStakingJuniorAmount(''); + }; + + const handleUnstakeRiskTokens = () => { + if (!unstakeAmount) return; + onUnstakeRiskTokens(unstakeAmount); + setUnstakeAmount(''); + }; + + const handleEmergencyWithdraw = () => { + if (!emergencyAmount) return; + onEmergencyWithdraw(emergencyAmount, preferredAsset); + setEmergencyAmount(''); + }; + + const handleOptimalStaking = () => { + // Use equal amounts for simplicity - could be enhanced with pool ratio logic + const maxAmount = Math.min(seniorBalance, juniorBalance); + setStakingSeniorAmount(maxAmount.toFixed(6)); + setStakingJuniorAmount(maxAmount.toFixed(6)); + }; + + return ( +
+ {/* Stake Tokens */} + + + + + Stake Risk Tokens + + + Stake your SENIOR and JUNIOR tokens to earn higher rewards from trading fees + + + + + + +
+
+ +
+ setStakingSeniorAmount(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400 pr-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> + +
+

+ Balance: {formatNumber(seniorBalance)} +

+
+ +
+ +
+ setStakingJuniorAmount(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400 pr-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> + +
+

+ Balance: {formatNumber(juniorBalance)} +

+
+
+ + {stakingSeniorAmount && stakingJuniorAmount && ( + + + + You'll receive staking rewards proportional to your share of the pool + + + )} + + +
+
+ + {/* Unstake Tokens */} + + + + + Unstake Risk Tokens + + + Unstake your tokens from the reward pool to get back your underlying tokens. You'll receive proportional amounts of both SENIOR and JUNIOR tokens. + + + + +
+ +
+ setUnstakeAmount(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400 pr-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> + +
+

+ Staked Balance: {formatNumber(lpBalance)} +

+
+ + {unstakeAmount && parseFloat(unstakeAmount) > 0 && ( + + + + You'll receive proportional amounts of SENIOR and JUNIOR tokens plus any earned rewards + + + )} + + +
+
+ + {/* Emergency Withdrawal */} + {vaultInfo.emergencyMode && ( + + + Emergency Withdrawal + + Emergency mode is active. Senior token holders can withdraw with preferred asset. + + + +
+
+ +
+ setEmergencyAmount(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400 pr-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> + +
+
+
+ +
+ + +
+
+
+ +
+
+ )} +
+ ); +}; + +export default AdvancedFeatures; diff --git a/frontend/src/components/dashboard/DepositStrategies.tsx b/frontend/src/components/dashboard/DepositStrategies.tsx new file mode 100644 index 0000000..b113bf2 --- /dev/null +++ b/frontend/src/components/dashboard/DepositStrategies.tsx @@ -0,0 +1,220 @@ +import React, { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Badge } from '@/components/ui/badge'; +import { + Shield, + TrendingUp, + Activity, + Target, + Info, + RefreshCw, +} from 'lucide-react'; + +interface DepositStrategiesProps { + aUSDCBalance: number; + cUSDTBalance: number; + formatNumber: (num: number, decimals?: number) => string; + isExecuting: boolean; + onExecuteStrategy: (strategy: 'safety' | 'upside' | 'balanced', amount: string, asset: 'aUSDC' | 'cUSDT') => void; + seniorPrice?: string; + juniorPrice?: string; +} + +const DepositStrategies: React.FC = ({ + aUSDCBalance, + cUSDTBalance, + formatNumber, + isExecuting, + onExecuteStrategy, + seniorPrice = '1.00', + juniorPrice = '1.00', +}) => { + const [activeStrategy, setActiveStrategy] = useState<'safety' | 'upside' | 'balanced'>('balanced'); + const [amount, setAmount] = useState(''); + const [selectedAsset, setSelectedAsset] = useState<'aUSDC' | 'cUSDT'>('aUSDC'); + + const handleExecute = () => { + if (!amount || parseFloat(amount) <= 0) return; + onExecuteStrategy(activeStrategy, amount, selectedAsset); + setAmount(''); + }; + + return ( + + + + + Smart Deposit Strategies + + + Choose your risk strategy and we'll optimize your token allocation + + + + {/* Strategy Selection */} +
+ setActiveStrategy('safety')} + > + + +

Max Safety

+

All funds โ†’ Senior tokens

+ + Priority Claims + +
+
+ + setActiveStrategy('balanced')} + > + + +

Balanced

+

50% Senior / 50% Junior

+ + Moderate Risk + +
+
+ + setActiveStrategy('upside')} + > + + +

Max Upside

+

All funds โ†’ Junior tokens

+ + Higher Returns + +
+
+
+ + {/* Deposit Form */} +
+
+ +
+ + +
+
+ +
+ +
+ setAmount(e.target.value)} + className="bg-slate-700/50 border-slate-600 text-white pr-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> + +
+
+ Available: {formatNumber(selectedAsset === 'aUSDC' ? aUSDCBalance : cUSDTBalance, 2)} +
+
+
+ + {/* Strategy Preview */} + {amount && parseFloat(amount) > 0 && ( + + + +
+
Strategy Preview:
+
+ {activeStrategy === 'safety' && ( +
Depositing ${amount} will get you ~{formatNumber(parseFloat(amount) / parseFloat(seniorPrice))} Senior tokens (priority claims)
+ )} + {activeStrategy === 'balanced' && ( +
Depositing ${amount} will get you ~{formatNumber((parseFloat(amount) / 2) / parseFloat(seniorPrice))} Senior + ~{formatNumber((parseFloat(amount) / 2) / parseFloat(juniorPrice))} Junior tokens
+ )} + {activeStrategy === 'upside' && ( +
Depositing ${amount} will get you ~{formatNumber(parseFloat(amount) / parseFloat(juniorPrice))} Junior tokens (higher upside potential)
+ )} +
+
+
+
+ )} + + {/* Execute Button */} + +
+
+ ); +}; + +export default DepositStrategies; diff --git a/frontend/src/components/dashboard/MarketOverview.tsx b/frontend/src/components/dashboard/MarketOverview.tsx new file mode 100644 index 0000000..22ff639 --- /dev/null +++ b/frontend/src/components/dashboard/MarketOverview.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { + Activity, + TrendingUp, + Droplets, + Users, + ExternalLink, +} from 'lucide-react'; + +interface MarketOverviewProps { + seniorPrice: string; + juniorPrice: string; + poolReserves: { senior: string; junior: string }; + protocolTVL: number; + formatNumber: (num: number, decimals?: number) => string; +} + +const MarketOverview: React.FC = ({ + seniorPrice, + juniorPrice, + poolReserves, + protocolTVL, + formatNumber, +}) => { + return ( + + +
+ + + Market Overview + +
+
+ +
+
+

Senior Price

+

${seniorPrice}

+
+ + Stable +
+
+
+

Junior Price

+

${juniorPrice}

+
+ + Volatile +
+
+
+

Pool Liquidity

+

+ ${formatNumber((parseFloat(poolReserves.senior) + parseFloat(poolReserves.junior)), 0)} +

+
+ + Deep +
+
+
+

Protocol TVL

+

${formatNumber(protocolTVL, 0)}

+
+ + Growing +
+
+
+
+
+ ); +}; + +export default MarketOverview; diff --git a/frontend/src/components/dashboard/PortfolioOverview.tsx b/frontend/src/components/dashboard/PortfolioOverview.tsx new file mode 100644 index 0000000..b523cfb --- /dev/null +++ b/frontend/src/components/dashboard/PortfolioOverview.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Progress } from '@/components/ui/progress'; +import { + Shield, + TrendingUp, + Zap, + Settings, + Droplets, + Plus, + BarChart3, +} from 'lucide-react'; + +interface VaultInfo { + emergencyMode: boolean; +} + +interface PortfolioOverviewProps { + seniorBalance: number; + juniorBalance: number; + lpBalance: number; + seniorPrice: string; + juniorPrice: string; + riskProfile: { + level: string; + color: string; + percentage: number; + }; + formatNumber: (num: number, decimals?: number) => string; + protocolTVL: number; + userSharePercent: number; + vaultInfo: VaultInfo; + onTabChange: (tab: string) => void; +} + +const PortfolioOverview: React.FC = ({ + seniorBalance, + juniorBalance, + lpBalance, + seniorPrice, + juniorPrice, + riskProfile, + formatNumber, + protocolTVL, + userSharePercent, + vaultInfo, + onTabChange, +}) => { + return ( +
+ {/* Portfolio Breakdown */} + + + + + Portfolio Breakdown + + + + {/* Risk Profile Visualization */} +
+
+ Risk Distribution + + {formatNumber(riskProfile.percentage)}% Senior + +
+ +
+ Conservative + Aggressive +
+
+ + {/* Token Holdings */} +
+
+
+ +
+

Senior Tokens

+

Priority claims โ€ข Lower risk

+
+
+
+

{formatNumber(seniorBalance)}

+

${seniorPrice} each

+
+
+ +
+
+ +
+

Junior Tokens

+

Higher upside โ€ข Higher risk

+
+
+
+

{formatNumber(juniorBalance)}

+

${juniorPrice} each

+
+
+ +
+
+ +
+

Staked Tokens

+

Risk token staking rewards

+
+
+
+

{formatNumber(lpBalance)}

+

Pool share

+
+
+
+
+
+ + {/* Quick Actions */} + + + + + Quick Actions + + + + + + + + + + {/* Protocol Analytics */} +
+
+

+ + Protocol Analytics +

+
+
+
+

Total TVL

+

${formatNumber(protocolTVL, 0)}

+
+
+

Your Share

+

{formatNumber(userSharePercent, 4)}%

+
+
+

Pool Ratio

+

1.00

+
+
+

Emergency

+

+ {vaultInfo.emergencyMode ? 'Active' : 'Inactive'} +

+
+
+
+
+
+
+ ); +}; + +export default PortfolioOverview; diff --git a/frontend/src/components/dashboard/PositionManagement.tsx b/frontend/src/components/dashboard/PositionManagement.tsx new file mode 100644 index 0000000..98fb787 --- /dev/null +++ b/frontend/src/components/dashboard/PositionManagement.tsx @@ -0,0 +1,279 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Progress } from '@/components/ui/progress'; +import { Phase } from '@/config/contracts'; +import { useWeb3 } from '@/context/PrivyWeb3Context'; +import { + Activity, + Minus, + ArrowUpRight, + ArrowDownRight, + RefreshCw, + Info, + AlertCircle, + DollarSign, +} from 'lucide-react'; + +interface PositionManagementProps { + riskProfile: { percentage: number }; + totalPortfolioValue: number; + formatNumber: (num: number, decimals?: number) => string; + isRebalancing: boolean; + isWithdrawing: boolean; + onRebalance: (targetPercent: number) => void; + onWithdraw: (amount: string) => void; + vaultInfo: any; +} + +const PositionManagement: React.FC = ({ + riskProfile, + totalPortfolioValue, + formatNumber, + isRebalancing, + isWithdrawing, + onRebalance, + onWithdraw, + vaultInfo, +}) => { + const { balances } = useWeb3(); + const [targetSeniorPercent, setTargetSeniorPercent] = useState(50); + const [isRebalancePreview, setIsRebalancePreview] = useState(false); + const [withdrawAmount, setWithdrawAmount] = useState(''); + + // Initialize target percent to current allocation + useEffect(() => { + setTargetSeniorPercent(Math.round(riskProfile.percentage)); + }, [riskProfile.percentage]); + + const handleRebalancePreview = (targetPercent: number) => { + setTargetSeniorPercent(targetPercent); + setIsRebalancePreview(true); + }; + + const handleRebalanceExecute = () => { + onRebalance(targetSeniorPercent); + // Keep the preview visible during execution + // It will reset when the component re-renders after successful rebalancing + }; + + const handleWithdrawExecute = () => { + if (!withdrawAmount) return; + onWithdraw(withdrawAmount); + setWithdrawAmount(''); + }; + + return ( +
+ {/* Rebalance Portfolio */} + + + + + Rebalance Portfolio + + + Adjust your risk exposure by trading between Senior and Junior tokens + + + + {/* Current vs Target Visualization */} +
+
+
+ Current Allocation + {formatNumber(riskProfile.percentage)}% Senior / {formatNumber(100 - riskProfile.percentage)}% Junior +
+ +
+ + {(isRebalancePreview || isRebalancing) && ( +
+
+ Target Allocation + {targetSeniorPercent}% Senior / {100 - targetSeniorPercent}% Junior +
+ +
+ +
+ )} +
+ + {/* Target Allocation Buttons */} +
+ +
+ {[0, 25, 50, 75, 100].map((percent) => ( + + ))} +
+
+ Max Risk + Balanced + Max Safety +
+
+ + {/* Action Buttons */} + {(isRebalancePreview || isRebalancing) ? ( +
+ + +
+ ) : ( + + + + Use the buttons above to set your target allocation. Trades will be executed via AMM. + + + )} + + + + {/* Withdraw */} + + + + + Withdraw + + + Withdraw from your position to both aUSDC and cUSDT proportionally + + + + {/* Current Asset Holdings */} +
+ +
+
+
+
+
aUSDC
+
Aave USDC
+
+
+
{formatNumber(Number(balances.aUSDC) / 1e18)}
+
+
+
+
+
+
+
cUSDT
+
Compound USDT
+
+
+
{formatNumber(Number(balances.cUSDT) / 1e18)}
+
+
+
+
+
+ + {/* Amount Input */} +
+ +
+ + setWithdrawAmount(e.target.value)} + className="pl-10 bg-slate-700/50 border-slate-600 text-white placeholder-slate-400 pr-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> + +
+
+ Max withdrawable: ${formatNumber(totalPortfolioValue)} +
+
+ Will be distributed proportionally to both aUSDC and cUSDT +
+
+ + + + + + + {vaultInfo.currentPhase === 0n && 'Active Period: Withdrawals require equal amounts of Senior and Junior tokens.'} + {vaultInfo.currentPhase === 1n && 'Claims Period: Any combination of Senior and Junior tokens can be withdrawn.'} + {vaultInfo.currentPhase === 2n && 'Final Claims Period: All token holders can withdraw remaining funds with any token combination.'} + {vaultInfo.currentPhase === undefined && 'Loading withdrawal rules...'} + {(vaultInfo.currentPhase !== 0n && vaultInfo.currentPhase !== 1n && vaultInfo.currentPhase !== 2n && vaultInfo.currentPhase !== undefined) && + `Phase ${vaultInfo.currentPhase?.toString()}: Any combination of tokens can be withdrawn.`} + + +
+
+
+ ); +}; + +export default PositionManagement; diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx new file mode 100644 index 0000000..82badaa --- /dev/null +++ b/frontend/src/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } \ No newline at end of file diff --git a/frontend/src/config/contracts.ts b/frontend/src/config/contracts.ts index 1894c9c..e7c0740 100644 --- a/frontend/src/config/contracts.ts +++ b/frontend/src/config/contracts.ts @@ -26,15 +26,15 @@ export enum ContractName { // Multi-chain contract addresses export const MULTI_CHAIN_ADDRESSES: Record>> = { [SupportedChainId.HEDERA_TESTNET]: { - [ContractName.MOCK_AUSDC]: "0x2Ec140Ba967BD9d2B5C0C138F22E20A530beFaF4", - [ContractName.MOCK_CUSDT]: "0xbc20b977781705589EC578aC7d5680Af821D4c1c", - [ContractName.UNISWAP_V2_FACTORY]: "0x0310c9Cf82c2b0a426F2a5e09d1d670B31442a63", - [ContractName.WETH]: "0x86840528937f5f1578601d3d31Bc7cFF6A540009", - [ContractName.RISK_VAULT]: "0x78a9bCbA870A367559258AAAC5b7e533cF9C0EB7", - [ContractName.UNISWAP_V2_ROUTER]: "0x80E7C7932059ce5E67506B0BC25971B8209e003f", - [ContractName.JUNIOR_TOKEN]: "0xc3e818236440461916BF75C91bd28Bb27bEa746e", - [ContractName.SENIOR_TOKEN]: "0x9c6A5C213F5CB326186E876C3e37ec8F73768b2B", - [ContractName.SENIOR_JUNIOR_PAIR]: "0xb6368834321662839717dbd3dea511525B8382B5", + [ContractName.MOCK_AUSDC]: "0xc6461cf8E77b40293d90c9670FcC2Da04346Df1A", + [ContractName.MOCK_CUSDT]: "0xe786547f22F29E477B51135b24A39E937d291d17", + [ContractName.UNISWAP_V2_FACTORY]: "0x3e83552ED9bF1418Bf50fbc7071C87361Ce156b5", + [ContractName.WETH]: "0xED6eF615b61D37a5E1cce5D5Aaddc1064Fb9fA07", + [ContractName.RISK_VAULT]: "0x86840DBAE8aF63780ffFB2990aADDF5C78aeb184", + [ContractName.UNISWAP_V2_ROUTER]: "0xBA659094Ffd44F3CCcFffd7c172cB42f0aD362b0", + [ContractName.JUNIOR_TOKEN]: "0x2DDd2DD26A3d90d4a67F02A69728B386B1461710", + [ContractName.SENIOR_TOKEN]: "0xC5F805bBD905803e5Ec1280827068B9889978cF3", + [ContractName.SENIOR_JUNIOR_PAIR]: "0x40C4F4B6fB472bFE986134A2A99C691E4323b09E", }, [SupportedChainId.FLOW_TESTNET]: { [ContractName.MOCK_AUSDC]: "0x27448B112B42c930915bF3953A691c80BdcE7208", @@ -238,23 +238,26 @@ export function getChainDeploymentStatus(chainId: SupportedChainId): Record Promise; - addLiquidity: ( + stakeRiskTokens: ( tokenAAmount: string, tokenBAmount: string, tokenA: string, tokenB: string, ) => Promise; - removeLiquidity: ( - lpTokenAmount: string, + unstakeRiskTokens: ( + stakedTokenAmount: string, tokenA: string, tokenB: string, ) => Promise; @@ -143,7 +143,7 @@ const Web3Context = createContext({ cUSDTBalance: 0n, totalTokensIssued: 0n, emergencyMode: false, - currentPhase: Phase.DEPOSIT, + currentPhase: Phase.ACTIVE, phaseStartTime: 0n, cycleStartTime: 0n, timeRemaining: 0n, @@ -160,14 +160,15 @@ const Web3Context = createContext({ emergencyWithdraw: async () => {}, toggleEmergencyMode: async () => {}, forcePhaseTransition: async () => {}, + forcePhaseTransitionImmediate: async () => {}, startNewCycle: async () => {}, refreshData: async () => {}, approveToken: async () => {}, calculateWithdrawalAmounts: async () => ({ aUSDC: 0n, cUSDT: 0n }), swapExactTokensForTokens: async () => {}, getAmountsOut: async () => "0", - addLiquidity: async () => {}, - removeLiquidity: async () => {}, + stakeRiskTokens: async () => {}, + unstakeRiskTokens: async () => {}, getPairReserves: async () => ({ reserve0: 0n, reserve1: 0n }), getTokenBalance: async () => 0n, }); @@ -197,7 +198,7 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { cUSDTBalance: 0n, totalTokensIssued: 0n, emergencyMode: false, - currentPhase: Phase.DEPOSIT, + currentPhase: 0n, phaseStartTime: 0n, cycleStartTime: 0n, timeRemaining: 0n, @@ -205,6 +206,10 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { const [seniorTokenAddress, setSeniorTokenAddress] = useState(null); const [juniorTokenAddress, setJuniorTokenAddress] = useState(null); + + // Add ref to track last refresh time for debouncing + const lastRefreshTime = useRef(0); + const isRefreshing = useRef(false); // Initial state values for cleanup const initialBalances: TokenBalances = { @@ -220,7 +225,7 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { cUSDTBalance: 0n, totalTokensIssued: 0n, emergencyMode: false, - currentPhase: Phase.DEPOSIT, + currentPhase: 0n, phaseStartTime: 0n, cycleStartTime: 0n, timeRemaining: 0n, @@ -353,27 +358,77 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { setupNetworkListener(); }, [ready, authenticated, wallets, logout]); - // Load contract data + // Load contract data for initial connection const loadContractData = async (provider: BrowserProvider, signer: Signer, chainId: SupportedChainId) => { try { + const userAddress = await signer.getAddress(); + + // Get all contract addresses const vaultAddress = getContractAddress(chainId, ContractName.RISK_VAULT); - if (!vaultAddress) { - console.warn('Risk vault not deployed on this chain'); + const aUSDCAddress = getContractAddress(chainId, ContractName.MOCK_AUSDC); + const cUSDTAddress = getContractAddress(chainId, ContractName.MOCK_CUSDT); + const pairAddress = getContractAddress(chainId, ContractName.SENIOR_JUNIOR_PAIR); + + if (!vaultAddress || !aUSDCAddress || !cUSDTAddress || !pairAddress) { + console.warn('Some contracts not available on current chain'); return; } - + + // Create contract instances const vaultContract = new Contract(vaultAddress, RISK_VAULT_ABI, provider); + const aUSDCContract = new Contract(aUSDCAddress, ERC20_ABI, provider); + const cUSDTContract = new Contract(cUSDTAddress, ERC20_ABI, provider); + const pairContract = new Contract(pairAddress, ERC20_ABI, provider); + + console.log('๐Ÿš€ Loading initial data...'); - // Get token addresses - const [seniorAddr, juniorAddr] = await Promise.all([ + // Fetch everything in parallel - simple and fast + const [ + seniorAddr, + juniorAddr, + protocolStatus, + phaseInfo, + vaultBalances, + userTokenBalances, + aUSDCBalance, + cUSDTBalance, + lpBalance, + ] = await Promise.all([ vaultContract.seniorToken(), vaultContract.juniorToken(), + vaultContract.getProtocolStatus(), + vaultContract.getPhaseInfo(), + vaultContract.getVaultBalances(), + vaultContract.getUserTokenBalances(userAddress), + aUSDCContract.balanceOf(userAddress), + cUSDTContract.balanceOf(userAddress), + pairContract.balanceOf(userAddress), ]); + + console.log('โœ… Initial data loaded'); + + // Set all state at once setSeniorTokenAddress(seniorAddr); setJuniorTokenAddress(juniorAddr); - // Load balances and vault info - await refreshData(); + setVaultInfo({ + aUSDCBalance: vaultBalances.aUSDCVaultBalance, + cUSDTBalance: vaultBalances.cUSDTVaultBalance, + totalTokensIssued: protocolStatus.totalTokens, + emergencyMode: protocolStatus.emergency, + currentPhase: protocolStatus.phase, + phaseStartTime: phaseInfo.phaseStart, + cycleStartTime: phaseInfo.cycleStart, + timeRemaining: phaseInfo.timeRemaining, + }); + + setBalances({ + seniorTokens: userTokenBalances.seniorBalance, + juniorTokens: userTokenBalances.juniorBalance, + aUSDC: aUSDCBalance, + cUSDT: cUSDTBalance, + lpTokens: lpBalance, + }); } catch (error) { console.error('Error loading contract data:', error); toast.error('Failed to load contract data for this network'); @@ -424,137 +479,80 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { } }; - // Refresh all data + // Refresh data (simpler version) const refreshData = useCallback(async () => { if (!provider || !signer || !address || !currentChain) { - console.log('โญ๏ธ Skipping refresh - missing provider, signer, address, or currentChain'); return; } - // Check if network is consistent before proceeding - const networkChainId = await provider.getNetwork().then(n => Number(n.chainId)).catch(() => null); - if (networkChainId && networkChainId !== currentChain) { - console.log(`โš ๏ธ Network mismatch detected: provider=${networkChainId}, context=${currentChain}. Skipping refresh.`); + // Simple debouncing + const now = Date.now(); + if (now - lastRefreshTime.current < 500 || isRefreshing.current) { return; } - console.log(`๐Ÿ”„ Refreshing data for chain ${currentChain} (${getChainConfig(currentChain)?.chainName})`); + isRefreshing.current = true; + lastRefreshTime.current = now; try { + // Get contract addresses const vaultAddress = getCurrentChainAddress(ContractName.RISK_VAULT); - if (!vaultAddress) { - console.warn('Risk vault not available on current chain'); - return; - } - - console.log(`๐Ÿ“„ Using vault contract at: ${vaultAddress}`); - const vaultContract = new Contract(vaultAddress, RISK_VAULT_ABI, provider); - - // Get vault info with detailed logging - console.log('๐Ÿ“Š Fetching vault contract data...'); - let userTokenBalances: any; - try { - const [ - protocolStatus, - phaseInfo, - vaultBalances, - userTokenBalancesResult, - ] = await Promise.all([ - vaultContract.getProtocolStatus(), - vaultContract.getPhaseInfo(), - vaultContract.getVaultBalances(), - vaultContract.getUserTokenBalances(address), - ]); - - userTokenBalances = userTokenBalancesResult; - console.log('โœ… Vault contract data fetched successfully'); - - setVaultInfo({ - aUSDCBalance: vaultBalances.aUSDCVaultBalance, - cUSDTBalance: vaultBalances.cUSDTVaultBalance, - totalTokensIssued: protocolStatus.totalTokens, - emergencyMode: protocolStatus.emergency, - currentPhase: protocolStatus.phase, - phaseStartTime: phaseInfo.phaseStart, - cycleStartTime: phaseInfo.cycleStart, - timeRemaining: phaseInfo.timeRemaining, - }); - } catch (vaultError) { - if (vaultError.code === 'NETWORK_ERROR') { - console.log('๐Ÿ”„ Network change detected in vault calls, skipping...'); - return; - } - if (vaultError.code === 'CALL_EXCEPTION' && currentChain === 296) { - console.log('โš ๏ธ Hedera RPC issue detected in vault calls, skipping...'); - return; - } - console.error('โŒ Vault contract calls failed:', vaultError); - throw vaultError; - } - - // Get user token balances const aUSDCAddress = getCurrentChainAddress(ContractName.MOCK_AUSDC); const cUSDTAddress = getCurrentChainAddress(ContractName.MOCK_CUSDT); const pairAddress = getCurrentChainAddress(ContractName.SENIOR_JUNIOR_PAIR); - - if (!aUSDCAddress || !cUSDTAddress || !pairAddress) { - console.warn('Some token contracts not available on current chain'); + + if (!vaultAddress || !aUSDCAddress || !cUSDTAddress || !pairAddress) { return; } - - console.log(`๐Ÿ’ฐ Fetching token balances...`); - console.log(` aUSDC: ${aUSDCAddress}`); - console.log(` cUSDT: ${cUSDTAddress}`); - console.log(` Pair: ${pairAddress}`); - + + // Create contract instances + const vaultContract = new Contract(vaultAddress, RISK_VAULT_ABI, provider); const aUSDCContract = new Contract(aUSDCAddress, ERC20_ABI, provider); const cUSDTContract = new Contract(cUSDTAddress, ERC20_ABI, provider); const pairContract = new Contract(pairAddress, ERC20_ABI, provider); - try { - const [aUSDCBalance, cUSDTBalance, lpBalance] = await Promise.all([ - aUSDCContract.balanceOf(address), - cUSDTContract.balanceOf(address), - pairContract.balanceOf(address), - ]); - - console.log('โœ… Token balances fetched successfully'); - - setBalances({ - seniorTokens: userTokenBalances.seniorBalance, - juniorTokens: userTokenBalances.juniorBalance, - aUSDC: aUSDCBalance, - cUSDT: cUSDTBalance, - lpTokens: lpBalance, - }); - } catch (tokenError) { - if (tokenError.code === 'NETWORK_ERROR') { - console.log('๐Ÿ”„ Network change detected in token calls, skipping...'); - return; - } - if (tokenError.code === 'CALL_EXCEPTION' && currentChain === 296) { - console.log('โš ๏ธ Hedera RPC issue detected in token calls, skipping...'); - return; - } - console.error('โŒ Token balance calls failed:', tokenError); - throw tokenError; - } - } catch (error) { - if (error.code === 'NETWORK_ERROR') { - console.log('๐Ÿ”„ Network change detected in main refresh, skipping...'); - return; - } - if (error.code === 'CALL_EXCEPTION' && currentChain === 296) { - console.log('โš ๏ธ Hedera RPC rate limiting detected, skipping refresh...'); - return; - } - console.error('๐Ÿ’ฅ Error refreshing data on chain', currentChain, ':', error); - console.error('Error details:', { - message: error.message, - code: error.code, - reason: error.reason + // Fetch all data in parallel (token addresses already loaded, so skip them) + const [ + protocolStatus, + phaseInfo, + vaultBalances, + userTokenBalances, + aUSDCBalance, + cUSDTBalance, + lpBalance, + ] = await Promise.all([ + vaultContract.getProtocolStatus(), + vaultContract.getPhaseInfo(), + vaultContract.getVaultBalances(), + vaultContract.getUserTokenBalances(address), + aUSDCContract.balanceOf(address), + cUSDTContract.balanceOf(address), + pairContract.balanceOf(address), + ]); + + // Update state + setVaultInfo({ + aUSDCBalance: vaultBalances.aUSDCVaultBalance, + cUSDTBalance: vaultBalances.cUSDTVaultBalance, + totalTokensIssued: protocolStatus.totalTokens, + emergencyMode: protocolStatus.emergency, + currentPhase: protocolStatus.phase, + phaseStartTime: phaseInfo.phaseStart, + cycleStartTime: phaseInfo.cycleStart, + timeRemaining: phaseInfo.timeRemaining, + }); + + setBalances({ + seniorTokens: userTokenBalances.seniorBalance, + juniorTokens: userTokenBalances.juniorBalance, + aUSDC: aUSDCBalance, + cUSDT: cUSDTBalance, + lpTokens: lpBalance, }); - toast.error(`Failed to refresh data on ${getChainConfig(currentChain)?.chainName || 'unknown network'}`); + } catch (error) { + console.error('Error refreshing data:', error); + } finally { + isRefreshing.current = false; } }, [provider, signer, address, currentChain, getCurrentChainAddress]); @@ -1032,7 +1030,7 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { } }; - const addLiquidity = async (tokenAAmount: string, tokenBAmount: string, tokenA: string, tokenB: string) => { + const stakeRiskTokens = async (tokenAAmount: string, tokenBAmount: string, tokenA: string, tokenB: string) => { if (!signer || !address) { toast.error('Please connect your wallet'); return; @@ -1041,13 +1039,68 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { try { const amountADesired = ethers.parseEther(tokenAAmount); const amountBDesired = ethers.parseEther(tokenBAmount); - const amountAMin = amountADesired * 95n / 100n; // 5% slippage - const amountBMin = amountBDesired * 95n / 100n; // 5% slippage const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes - // Approve both tokens + // Check token balances first const tokenAContract = new Contract(tokenA, ERC20_ABI, signer); const tokenBContract = new Contract(tokenB, ERC20_ABI, signer); + + const balanceA = await tokenAContract.balanceOf(address); + const balanceB = await tokenBContract.balanceOf(address); + + // Use actual balance if requested amount is very close (within 0.01% difference) + let finalAmountADesired = amountADesired; + let finalAmountBDesired = amountBDesired; + + if (balanceA < amountADesired) { + const diff = amountADesired - balanceA; + const diffPercentage = (diff * 10000n) / amountADesired; // 0.01% = 1 basis point + + if (diffPercentage <= 1n) { // If difference is <= 0.01%, use available balance + finalAmountADesired = balanceA; + console.log(`Adjusting tokenA amount from ${ethers.formatEther(amountADesired)} to ${ethers.formatEther(balanceA)} due to precision`); + } else { + let symbolA = 'Token A'; + try { + symbolA = await tokenAContract.symbol(); + } catch (e) { + if (tokenA === getCurrentChainAddress(ContractName.SENIOR_TOKEN)) { + symbolA = 'SENIOR'; + } else if (tokenA === getCurrentChainAddress(ContractName.JUNIOR_TOKEN)) { + symbolA = 'JUNIOR'; + } + } + toast.error(`Insufficient ${symbolA} balance. Required: ${ethers.formatEther(amountADesired)}, Available: ${ethers.formatEther(balanceA)}`); + return; + } + } + + if (balanceB < amountBDesired) { + const diff = amountBDesired - balanceB; + const diffPercentage = (diff * 10000n) / amountBDesired; // 0.01% = 1 basis point + + if (diffPercentage <= 1n) { // If difference is <= 0.01%, use available balance + finalAmountBDesired = balanceB; + console.log(`Adjusting tokenB amount from ${ethers.formatEther(amountBDesired)} to ${ethers.formatEther(balanceB)} due to precision`); + } else { + let symbolB = 'Token B'; + try { + symbolB = await tokenBContract.symbol(); + } catch (e) { + if (tokenB === getCurrentChainAddress(ContractName.SENIOR_TOKEN)) { + symbolB = 'SENIOR'; + } else if (tokenB === getCurrentChainAddress(ContractName.JUNIOR_TOKEN)) { + symbolB = 'JUNIOR'; + } + } + toast.error(`Insufficient ${symbolB} balance. Required: ${ethers.formatEther(amountBDesired)}, Available: ${ethers.formatEther(balanceB)}`); + return; + } + } + + // Update amounts and slippage calculations with final amounts + const amountAMin = finalAmountADesired * 95n / 100n; // 5% slippage + const amountBMin = finalAmountBDesired * 95n / 100n; // 5% slippage const routerAddress = getCurrentChainAddress(ContractName.UNISWAP_V2_ROUTER); if (!routerAddress) { @@ -1058,8 +1111,9 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { const allowanceA = await tokenAContract.allowance(address, routerAddress); const allowanceB = await tokenBContract.allowance(address, routerAddress); - if (allowanceA < amountADesired) { - const approveTx = await tokenAContract.approve(routerAddress, amountADesired); + if (allowanceA < finalAmountADesired) { + // Approve max amount to avoid future approval transactions + const approveTx = await tokenAContract.approve(routerAddress, ethers.MaxUint256); let symbolA = 'Token A'; try { symbolA = await tokenAContract.symbol(); @@ -1075,8 +1129,9 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { await approveTx.wait(); } - if (allowanceB < amountBDesired) { - const approveTx = await tokenBContract.approve(routerAddress, amountBDesired); + if (allowanceB < finalAmountBDesired) { + // Approve max amount to avoid future approval transactions + const approveTx = await tokenBContract.approve(routerAddress, ethers.MaxUint256); let symbolB = 'Token B'; try { symbolB = await tokenBContract.symbol(); @@ -1092,41 +1147,41 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { await approveTx.wait(); } - // Add liquidity + // Stake risk tokens const router = new Contract(routerAddress, UNISWAP_V2_ROUTER_ABI, signer); const tx = await router.addLiquidity( tokenA, tokenB, - amountADesired, - amountBDesired, + finalAmountADesired, + finalAmountBDesired, amountAMin, amountBMin, address, deadline, ); - toast.info('Adding liquidity...'); + toast.info('Staking risk tokens...'); await tx.wait(); - toast.success('Liquidity added'); + toast.success('Risk tokens staked'); await refreshData(); } catch (error: any) { - console.error('Failed to add liquidity:', error); - toast.error(error.reason || 'Failed to add liquidity'); + console.error('Failed to stake risk tokens:', error); + toast.error(error.reason || 'Failed to stake risk tokens'); } }; - const removeLiquidity = async (lpTokenAmount: string, tokenA: string, tokenB: string) => { + const unstakeRiskTokens = async (stakedTokenAmount: string, tokenA: string, tokenB: string) => { if (!signer || !address) { toast.error('Please connect your wallet'); return; } try { - const lpAmountWei = ethers.parseEther(lpTokenAmount); + const stakedAmountWei = ethers.parseEther(stakedTokenAmount); const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes - // Approve LP tokens + // Approve staked tokens const pairAddress = getCurrentChainAddress(ContractName.SENIOR_JUNIOR_PAIR); const routerAddress = getCurrentChainAddress(ContractName.UNISWAP_V2_ROUTER); @@ -1138,32 +1193,32 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { const pairContract = new Contract(pairAddress, ERC20_ABI, signer); const allowance = await pairContract.allowance(address, routerAddress); - if (allowance < lpAmountWei) { - const approveTx = await pairContract.approve(routerAddress, lpAmountWei); - toast.info('Approving LP tokens...'); + if (allowance < stakedAmountWei) { + const approveTx = await pairContract.approve(routerAddress, stakedAmountWei); + toast.info('Approving Staked tokens...'); await approveTx.wait(); } - // Remove liquidity + // Unstake risk tokens const router = new Contract(routerAddress, UNISWAP_V2_ROUTER_ABI, signer); const tx = await router.removeLiquidity( tokenA, tokenB, - lpAmountWei, + stakedAmountWei, 0, // amountAMin (accept any amount) 0, // amountBMin (accept any amount) address, deadline, ); - toast.info('Removing liquidity...'); + toast.info('Unstaking risk tokens...'); await tx.wait(); - toast.success('Liquidity removed'); + toast.success('Risk tokens unstaked'); await refreshData(); } catch (error: any) { - console.error('Failed to remove liquidity:', error); - toast.error(error.reason || 'Failed to remove liquidity'); + console.error('Failed to unstake risk tokens:', error); + toast.error(error.reason || 'Failed to unstake risk tokens'); } }; @@ -1225,8 +1280,8 @@ const InnerWeb3Provider: React.FC<{ children: ReactNode }> = ({ children }) => { swapExactTokensForTokens, getAmountsOut, getTokenBalance, - addLiquidity, - removeLiquidity, + stakeRiskTokens, + unstakeRiskTokens, getPairReserves, }; diff --git a/frontend/src/hooks/use-toast.ts b/frontend/src/hooks/use-toast.ts index 6ecbae0..8af9735 100644 --- a/frontend/src/hooks/use-toast.ts +++ b/frontend/src/hooks/use-toast.ts @@ -1,3 +1,3 @@ import { toast } from "@/components/ui/sonner"; -export { toast }; +export { toast }; \ No newline at end of file diff --git a/frontend/src/hooks/usePortfolioCalculations.ts b/frontend/src/hooks/usePortfolioCalculations.ts new file mode 100644 index 0000000..143ecd5 --- /dev/null +++ b/frontend/src/hooks/usePortfolioCalculations.ts @@ -0,0 +1,54 @@ +import { useWeb3 } from '@/context/PrivyWeb3Context'; +import { ethers } from 'ethers'; + +export const usePortfolioCalculations = (seniorPrice: string, juniorPrice: string) => { + const { balances, vaultInfo } = useWeb3(); + + const formatTokenAmount = (amount: bigint) => ethers.formatEther(amount); + const formatNumber = (num: number, decimals = 2) => num.toFixed(decimals); + + // Calculated values + const seniorBalance = Number(formatTokenAmount(balances.seniorTokens)); + const juniorBalance = Number(formatTokenAmount(balances.juniorTokens)); + const aUSDCBalance = Number(formatTokenAmount(balances.aUSDC)); + const cUSDTBalance = Number(formatTokenAmount(balances.cUSDT)); + const lpBalance = Number(formatTokenAmount(balances.lpTokens)); + + const totalPortfolioValue = + (seniorBalance * parseFloat(seniorPrice)) + + (juniorBalance * parseFloat(juniorPrice)); + + const protocolTVL = (Number(vaultInfo.aUSDCBalance) + Number(vaultInfo.cUSDTBalance)) / 1e18; + const userSharePercent = vaultInfo.totalTokensIssued > 0n + ? ((seniorBalance + juniorBalance) / (Number(vaultInfo.totalTokensIssued) / 1e18) * 100) + : 0; + + // Risk Assessment + const getRiskProfile = () => { + const totalTokens = seniorBalance + juniorBalance; + if (totalTokens === 0) return { level: 'None', color: 'slate', percentage: 0 }; + + const seniorRatio = seniorBalance / totalTokens; + if (seniorRatio >= 0.8) return { level: 'Conservative', color: 'blue', percentage: seniorRatio * 100 }; + if (seniorRatio >= 0.6) return { level: 'Moderate', color: 'purple', percentage: seniorRatio * 100 }; + if (seniorRatio >= 0.4) return { level: 'Balanced', color: 'green', percentage: seniorRatio * 100 }; + if (seniorRatio >= 0.2) return { level: 'Growth', color: 'yellow', percentage: seniorRatio * 100 }; + return { level: 'Aggressive', color: 'red', percentage: seniorRatio * 100 }; + }; + + const riskProfile = getRiskProfile(); + + return { + formatTokenAmount, + formatNumber, + seniorBalance, + juniorBalance, + aUSDCBalance, + cUSDTBalance, + lpBalance, + totalPortfolioValue, + protocolTVL, + userSharePercent, + riskProfile, + }; +}; \ No newline at end of file diff --git a/frontend/src/hooks/usePricing.ts b/frontend/src/hooks/usePricing.ts new file mode 100644 index 0000000..9791695 --- /dev/null +++ b/frontend/src/hooks/usePricing.ts @@ -0,0 +1,103 @@ +import { useState, useEffect } from 'react'; +import { useWeb3 } from '@/context/PrivyWeb3Context'; +import { ContractName, getContractAddress } from '@/config/contracts'; +import { ethers } from 'ethers'; + +export const usePricing = () => { + const { + seniorTokenAddress, + juniorTokenAddress, + getAmountsOut, + getPairReserves, + currentChain, + } = useWeb3(); + + const [seniorPrice, setSeniorPrice] = useState('1.00'); + const [juniorPrice, setJuniorPrice] = useState('1.00'); + const [poolReserves, setPoolReserves] = useState({ senior: '0', junior: '0' }); + + useEffect(() => { + const fetchTokenPrices = async () => { + if (!seniorTokenAddress || !juniorTokenAddress || !getAmountsOut || !currentChain) return; + + try { + // Get pool reserves to calculate proper AMM pricing + const pairAddress = getContractAddress(currentChain, ContractName.SENIOR_JUNIOR_PAIR); + const reserves = await getPairReserves(pairAddress); + const seniorReserve = parseFloat(ethers.formatEther(reserves.reserve0)); + const juniorReserve = parseFloat(ethers.formatEther(reserves.reserve1)); + + // Calculate prices directly from Uniswap AMM reserves + const seniorPriceInJunior = juniorReserve / seniorReserve; + const juniorPriceInSenior = seniorReserve / juniorReserve; + + try { + // Get price of 1 SENIOR in terms of JUNIOR + const seniorToJuniorPath = [seniorTokenAddress, juniorTokenAddress]; + const seniorPrice1Unit = await getAmountsOut('1', seniorToJuniorPath); + + // Get price of 1 JUNIOR in terms of SENIOR + const juniorToSeniorPath = [juniorTokenAddress, seniorTokenAddress]; + const juniorPrice1Unit = await getAmountsOut('1', juniorToSeniorPath); + + setSeniorPrice(parseFloat(seniorPrice1Unit).toFixed(2)); + setJuniorPrice(parseFloat(juniorPrice1Unit).toFixed(2)); + } catch (error) { + console.error('Error getting AMM prices:', error); + // Fallback to reserve-based calculation + setSeniorPrice(seniorPriceInJunior.toFixed(2)); + setJuniorPrice(juniorPriceInSenior.toFixed(2)); + } + } catch (error) { + console.error('Error fetching token prices:', error); + setSeniorPrice('1.00'); + setJuniorPrice('1.00'); + } + }; + + const fetchPoolReserves = async () => { + if (!getPairReserves || !currentChain) return; + + try { + const pairAddress = getContractAddress(currentChain, ContractName.SENIOR_JUNIOR_PAIR); + const reserves = await getPairReserves(pairAddress); + + const seniorReserve = ethers.formatEther(reserves.reserve0); + const juniorReserve = ethers.formatEther(reserves.reserve1); + + // Only update if values have changed significantly + const currentSenior = parseFloat(poolReserves.senior); + const currentJunior = parseFloat(poolReserves.junior); + const newSenior = parseFloat(seniorReserve); + const newJunior = parseFloat(juniorReserve); + + if (Math.abs(currentSenior - newSenior) > 0.01 || Math.abs(currentJunior - newJunior) > 0.01) { + setPoolReserves({ + senior: seniorReserve, + junior: juniorReserve, + }); + } + } catch (error) { + console.error('Error fetching pool reserves:', error); + if (poolReserves.senior === '0' && poolReserves.junior === '0') { + setPoolReserves({ senior: '0', junior: '0' }); + } + } + }; + + fetchTokenPrices(); + fetchPoolReserves(); + const interval = setInterval(() => { + fetchTokenPrices(); + fetchPoolReserves(); + }, 30000); + + return () => clearInterval(interval); + }, [seniorTokenAddress, juniorTokenAddress, getAmountsOut, getPairReserves, currentChain, poolReserves.senior, poolReserves.junior]); + + return { + seniorPrice, + juniorPrice, + poolReserves, + }; +}; \ No newline at end of file diff --git a/frontend/src/pages/Admin.tsx b/frontend/src/pages/Admin.tsx index 773aef9..3403015 100644 --- a/frontend/src/pages/Admin.tsx +++ b/frontend/src/pages/Admin.tsx @@ -143,34 +143,21 @@ const Admin = () => {

Phase Progression:

- - Deposit (2d) + + Active Period (5d) โ†’ - - Coverage (3d) + + Claims Period (1d) โ†’ - - Claims (1d) - - โ†’ - - Final Claims (1d) + + Final Claims Period (1d)
- - - {vaultInfo.currentPhase === Phase.FINAL_CLAIMS && ( + + + {Number(vaultInfo.currentPhase) === Phase.FINAL_CLAIMS && (
- {vaultInfo.currentPhase !== Phase.FINAL_CLAIMS && ( + {Number(vaultInfo.currentPhase) !== Phase.FINAL_CLAIMS && ( - New cycles can only be started from the Final Claims phase + Cycle restart is only available from the Final Claims phase )} diff --git a/frontend/src/pages/Advanced.tsx b/frontend/src/pages/Advanced.tsx deleted file mode 100644 index 7286098..0000000 --- a/frontend/src/pages/Advanced.tsx +++ /dev/null @@ -1,939 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useWeb3 } from '@/context/PrivyWeb3Context'; -import Navbar from '@/components/Navbar'; -import PhaseDisplay from '@/components/PhaseDisplay'; -import Trade from '@/components/Trade'; -import SmartLiquiditySuggestion from '@/components/SmartLiquiditySuggestion'; -import StatCard from '@/components/StatCard'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Alert, AlertDescription } from '@/components/ui/alert'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Info, DollarSign, Coins, Shield, AlertCircle, RefreshCw, Droplets, Minus, Activity, Settings, BarChart3 } from 'lucide-react'; -import { Phase, ContractName, getContractAddress, SupportedChainId } from '@/config/contracts'; -import { ethers } from 'ethers'; - -const Advanced = () => { - const { - isConnected, - balances, - vaultInfo, - depositAsset, - withdraw, - emergencyWithdraw, - getAmountsOut, - seniorTokenAddress, - juniorTokenAddress, - addLiquidity, - removeLiquidity, - getPairReserves, - currentChain, - isUnsupportedChain, - } = useWeb3(); - - const [depositAmount, setDepositAmount] = useState(''); - const [depositAssetType, setDepositAssetType] = useState<'aUSDC' | 'cUSDT'>('aUSDC'); - const [emergencyAmount, setEmergencyAmount] = useState(''); - const [preferredAsset, setPreferredAsset] = useState<'aUSDC' | 'cUSDT'>('aUSDC'); - const [seniorPrice, setSeniorPrice] = useState('0.98'); - const [juniorPrice, setJuniorPrice] = useState('1.05'); - const [pricesLoading, setPricesLoading] = useState(false); - - // Pool reserves state - const [poolReserves, setPoolReserves] = useState({ senior: '0', junior: '0' }); - - // Liquidity management state - const [liquiditySeniorAmount, setLiquiditySeniorAmount] = useState(''); - const [liquidityJuniorAmount, setLiquidityJuniorAmount] = useState(''); - const [removeLiquidityAmount, setRemoveLiquidityAmount] = useState(''); - const [showLiquiditySuggestion, setShowLiquiditySuggestion] = useState(false); - const [liquidityMode, setLiquidityMode] = useState<'manual' | 'optimal'>('optimal'); - - // New redemption state - const [selectedWithdrawAsset, setSelectedWithdrawAsset] = useState<'aUSDC' | 'cUSDT' | null>(null); - const [withdrawAssetAmount, setWithdrawAssetAmount] = useState(''); - const [calculatedTokenAmounts, setCalculatedTokenAmounts] = useState({ senior: '0', junior: '0' }); - const [effectivePhase, setEffectivePhase] = useState(Phase.DEPOSIT); - - // Format token amounts for display - const formatTokenAmount = (amount: bigint) => { - return ethers.formatEther(amount); - }; - - // Fetch token prices and pool reserves from Uniswap pair - useEffect(() => { - const fetchTokenPrices = async () => { - if (!seniorTokenAddress || !juniorTokenAddress || !getAmountsOut || !currentChain) return; - - setPricesLoading(true); - try { - // Get pool reserves to calculate proper AMM pricing - const pairAddress = getContractAddress(currentChain, ContractName.SENIOR_JUNIOR_PAIR); - const reserves = await getPairReserves(pairAddress); - const seniorReserve = parseFloat(ethers.formatEther(reserves.reserve0)); - const juniorReserve = parseFloat(ethers.formatEther(reserves.reserve1)); - - // Calculate prices directly from Uniswap AMM reserves - // In a Uniswap pair, price = other_reserve / this_reserve - const seniorPriceInJunior = juniorReserve / seniorReserve; - const juniorPriceInSenior = seniorReserve / juniorReserve; - - // For USD pricing, we need to establish a base. - // Let's use getAmountsOut to get actual market prices - try { - // Get price of 1 SENIOR in terms of JUNIOR - const seniorToJuniorPath = [seniorTokenAddress, juniorTokenAddress]; - const seniorPrice1Unit = await getAmountsOut('1', seniorToJuniorPath); - - // Get price of 1 JUNIOR in terms of SENIOR - const juniorToSeniorPath = [juniorTokenAddress, seniorTokenAddress]; - const juniorPrice1Unit = await getAmountsOut('1', juniorToSeniorPath); - - setSeniorPrice(parseFloat(seniorPrice1Unit).toFixed(2)); - setJuniorPrice(parseFloat(juniorPrice1Unit).toFixed(2)); - } catch (error) { - console.error('Error getting AMM prices:', error); - // Fallback to reserve-based calculation - setSeniorPrice(seniorPriceInJunior.toFixed(2)); - setJuniorPrice(juniorPriceInSenior.toFixed(2)); - } - } catch (error) { - console.error('Error fetching token prices:', error); - // Keep default prices on error (equal weighting) - setSeniorPrice('1.00'); - setJuniorPrice('1.00'); - } finally { - setPricesLoading(false); - } - }; - - const fetchPoolReserves = async () => { - if (!getPairReserves || !currentChain) return; - - try { - const pairAddress = getContractAddress(currentChain, ContractName.SENIOR_JUNIOR_PAIR); - const reserves = await getPairReserves(pairAddress); - - // Format reserves from wei to ether - const seniorReserve = ethers.formatEther(reserves.reserve0); - const juniorReserve = ethers.formatEther(reserves.reserve1); - - // Only update if values have changed significantly (avoid micro-updates) - const currentSenior = parseFloat(poolReserves.senior); - const currentJunior = parseFloat(poolReserves.junior); - const newSenior = parseFloat(seniorReserve); - const newJunior = parseFloat(juniorReserve); - - if (Math.abs(currentSenior - newSenior) > 0.01 || Math.abs(currentJunior - newJunior) > 0.01) { - setPoolReserves({ - senior: seniorReserve, - junior: juniorReserve, - }); - } - } catch (error) { - console.error('Error fetching pool reserves:', error); - // Only reset if we don't have valid data - if (poolReserves.senior === '0' && poolReserves.junior === '0') { - setPoolReserves({ senior: '0', junior: '0' }); - } - } - }; - - fetchTokenPrices(); - fetchPoolReserves(); - const interval = setInterval(() => { - fetchTokenPrices(); - fetchPoolReserves(); - }, 30000); // Update every 30 seconds to reduce twitching - - return () => clearInterval(interval); - }, [seniorTokenAddress, juniorTokenAddress, getAmountsOut, getPairReserves, currentChain]); - - - // Calculate optimal token amounts when user selects asset and amount - useEffect(() => { - const calculateOptimalTokens = () => { - if (!selectedWithdrawAsset || !withdrawAssetAmount || parseFloat(withdrawAssetAmount) <= 0) { - setCalculatedTokenAmounts({ senior: '0', junior: '0' }); - return; - } - - const targetAmount = parseFloat(withdrawAssetAmount); - const seniorBalance = parseFloat(formatTokenAmount(balances.seniorTokens)); - const juniorBalance = parseFloat(formatTokenAmount(balances.juniorTokens)); - - let seniorToUse = 0; - let juniorToUse = 0; - - // Handle case where phase is not loaded yet - default to DEPOSIT phase logic - // Convert bigint to number for comparison - const currentPhase = vaultInfo.currentPhase !== undefined ? Number(vaultInfo.currentPhase) : Phase.DEPOSIT; - setEffectivePhase(currentPhase); - - if (currentPhase === Phase.DEPOSIT) { - // Deposit phase: equal amounts required - const requiredPerToken = targetAmount / 2; // Each token contributes half - const maxPossible = Math.min(seniorBalance, juniorBalance); - const actualPerToken = Math.min(requiredPerToken, maxPossible); - seniorToUse = actualPerToken; - juniorToUse = actualPerToken; - } else if (currentPhase === Phase.CLAIMS) { - // Claims phase: senior tokens only - seniorToUse = Math.min(targetAmount, seniorBalance); - juniorToUse = 0; - } else { - // Other phases: prefer junior tokens first - if (juniorBalance >= targetAmount) { - juniorToUse = targetAmount; - seniorToUse = 0; - } else { - juniorToUse = juniorBalance; - seniorToUse = Math.min(targetAmount - juniorBalance, seniorBalance); - } - } - - setCalculatedTokenAmounts({ - senior: seniorToUse.toFixed(6), - junior: juniorToUse.toFixed(6), - }); - }; - - calculateOptimalTokens(); - }, [selectedWithdrawAsset, withdrawAssetAmount, balances.seniorTokens, balances.juniorTokens, vaultInfo.currentPhase]); - - const handleDeposit = async () => { - if (!depositAmount || parseFloat(depositAmount) <= 0) return; - await depositAsset(depositAssetType, depositAmount); - setDepositAmount(''); - }; - - - const handleOptimalWithdraw = async () => { - if (!selectedWithdrawAsset || !withdrawAssetAmount || parseFloat(withdrawAssetAmount) <= 0) return; - - const { senior, junior } = calculatedTokenAmounts; - if (parseFloat(senior) <= 0 && parseFloat(junior) <= 0) return; - - await withdraw(senior, junior); - setSelectedWithdrawAsset(null); - setWithdrawAssetAmount(''); - setCalculatedTokenAmounts({ senior: '0', junior: '0' }); - }; - - const handleEmergencyWithdraw = async () => { - if (!emergencyAmount || parseFloat(emergencyAmount) <= 0) return; - await emergencyWithdraw(emergencyAmount, preferredAsset); - setEmergencyAmount(''); - }; - - const handleAddLiquidity = async () => { - if (!liquiditySeniorAmount || !liquidityJuniorAmount || !seniorTokenAddress || !juniorTokenAddress) return; - if (parseFloat(liquiditySeniorAmount) <= 0 || parseFloat(liquidityJuniorAmount) <= 0) return; - - await addLiquidity(liquiditySeniorAmount, liquidityJuniorAmount, seniorTokenAddress, juniorTokenAddress); - setLiquiditySeniorAmount(''); - setLiquidityJuniorAmount(''); - setShowLiquiditySuggestion(false); - }; - - const handleOptimalLiquidity = () => { - const seniorBalance = parseFloat(formatTokenAmount(balances.seniorTokens)); - const juniorBalance = parseFloat(formatTokenAmount(balances.juniorTokens)); - const poolSenior = parseFloat(poolReserves.senior); - const poolJunior = parseFloat(poolReserves.junior); - - if (poolSenior > 0 && poolJunior > 0) { - // Calculate optimal amounts based on pool ratio - const poolRatio = poolSenior / poolJunior; - const userRatio = seniorBalance / juniorBalance; - - let optimalSenior: number, optimalJunior: number; - - if (userRatio > poolRatio) { - // User has more senior relative to pool ratio - optimalJunior = juniorBalance; - optimalSenior = Math.min(optimalJunior * poolRatio, seniorBalance); - } else { - // User has more junior relative to pool ratio - optimalSenior = seniorBalance; - optimalJunior = Math.min(optimalSenior / poolRatio, juniorBalance); - } - - setLiquiditySeniorAmount(optimalSenior.toFixed(6)); - setLiquidityJuniorAmount(optimalJunior.toFixed(6)); - } else { - // If no pool reserves, use equal amounts - const maxAmount = Math.min(seniorBalance, juniorBalance); - setLiquiditySeniorAmount(maxAmount.toFixed(6)); - setLiquidityJuniorAmount(maxAmount.toFixed(6)); - } - }; - - - const handleRemoveLiquidity = async () => { - if (!removeLiquidityAmount || !seniorTokenAddress || !juniorTokenAddress) return; - if (parseFloat(removeLiquidityAmount) <= 0) return; - - await removeLiquidity(removeLiquidityAmount, seniorTokenAddress, juniorTokenAddress); - setRemoveLiquidityAmount(''); - }; - - // Calculate total portfolio value based on risk token holdings and current market prices - const seniorTokenAmount = parseFloat(formatTokenAmount(balances.seniorTokens)); - const juniorTokenAmount = parseFloat(formatTokenAmount(balances.juniorTokens)); - - const totalPortfolioValue = - (seniorTokenAmount * parseFloat(seniorPrice)) + - (juniorTokenAmount * parseFloat(juniorPrice)); - - return ( -
- {/* Animated background elements */} -
-
-
-
-
- - - -
-
-
-
-

- - Advanced Trading -

-

- Complete control over protocol operations, manual trading, and liquidity management -

-
-
- - Real-time data -
-
-
- - {/* Connection Status */} - {!isConnected && ( - - - - Please connect your wallet to interact with advanced protocol features - - - )} - - {/* Phase Display */} -
- -
- - {/* Advanced Portfolio Overview */} -
- } - description={`${seniorTokenAmount.toFixed(2)} SENIOR + ${juniorTokenAmount.toFixed(2)} JUNIOR`} - className="transition-all duration-300" - /> - } - description="Priority claims token" - className="transition-all duration-300" - /> - } - description="Higher yield token" - className="transition-all duration-300" - /> - } - description={`${(parseFloat(poolReserves.senior) / 1000).toFixed(3)}K SENIOR / ${(parseFloat(poolReserves.junior) / 1000).toFixed(3)}K JUNIOR`} - className="transition-all duration-300" - /> -
- - {/* Manual Risk Trading */} -
- -
- - {/* Advanced Protocol Actions */} -
-
-

- - Advanced Protocol Actions -

-
-
- {/* Issue Risk Tokens */} - - - Issue Risk Tokens - - Deposit assets to receive CM-SENIOR and CM-JUNIOR tokens - - - - {Number(vaultInfo.currentPhase) !== Phase.DEPOSIT ? ( - - - - Deposits are only allowed during the Deposit phase. Current phase: {Phase[vaultInfo.currentPhase]} - - - ) : ( - <> -
- -
- - -
-
-
- - setDepositAmount(e.target.value)} - disabled={!isConnected} - className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400" - /> -
- - - - You will receive {depositAmount ? (parseFloat(depositAmount) / 2) : '0'} CM-SENIOR and {depositAmount ? (parseFloat(depositAmount) / 2) : '0'} CM-JUNIOR tokens - - - - - )} -
-
- - {/* Optimal Asset Redemption */} - - - Optimal Asset Redemption - - Choose target asset and amount - system calculates optimal token usage - - - - {/* Asset Selection */} -
- -
- - -
-
- - {/* Amount Input */} - {selectedWithdrawAsset && ( -
- - setWithdrawAssetAmount(e.target.value)} - disabled={!isConnected} - className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400" - /> -
- )} - - {/* Automatic Token Allocation Display */} - {selectedWithdrawAsset && withdrawAssetAmount && parseFloat(withdrawAssetAmount) > 0 && ( - - - -
-
Optimal Token Allocation:
-
-
Senior Tokens: {calculatedTokenAmounts.senior}
-
Junior Tokens: {calculatedTokenAmounts.junior}
-
- {effectivePhase === Phase.DEPOSIT - ? 'Deposit phase: Using equal amounts of senior and junior tokens' - : effectivePhase === Phase.CLAIMS - ? 'Claims phase: Using senior tokens only' - : 'Using max junior tokens first, then senior tokens'} -
-
-
-
-
- )} - - -
-
- - {/* Add Liquidity */} - - - - - Add Liquidity - - - Provide liquidity to earn trading fees. Choose optimal or manual mode. - - - - {/* Mode Selection */} -
- -
- - -
-
- - {liquidityMode === 'optimal' ? ( -
- {/* Available Tokens Display */} -
-
-
-

Available tokens:

-

- {seniorTokenAmount.toFixed(4)} SENIOR + {juniorTokenAmount.toFixed(4)} JUNIOR -

-

- Will add optimal amounts to match pool ratio -

-
-
-
- - {/* Quick Actions */} -
- - - {(liquiditySeniorAmount || liquidityJuniorAmount) && ( - - )} -
- - {/* Show calculated amounts */} - {(liquiditySeniorAmount || liquidityJuniorAmount) && ( - - - -
-
Ready to add:
-
- {liquiditySeniorAmount} SENIOR + {liquidityJuniorAmount} JUNIOR -
-
- These amounts match the current pool ratio for optimal liquidity provision -
-
-
-
- )} -
- ) : ( -
- {/* Manual Input */} -
-
- - setLiquiditySeniorAmount(e.target.value)} - disabled={!isConnected} - className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400" - /> -

- Balance: {formatTokenAmount(balances.seniorTokens)} -

-
- -
- - setLiquidityJuniorAmount(e.target.value)} - disabled={!isConnected} - className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400" - /> -

- Balance: {formatTokenAmount(balances.juniorTokens)} -

-
-
- - {liquiditySeniorAmount && liquidityJuniorAmount && ( - - - - You'll receive LP tokens proportional to your share of the pool - - - )} - - -
- )} -
-
- - {/* Remove Liquidity */} - - - - - Remove Liquidity - - - Remove liquidity from the SENIOR/JUNIOR pool - - - -
- - setRemoveLiquidityAmount(e.target.value)} - disabled={!isConnected} - className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400" - /> -

- LP Balance: {formatTokenAmount(balances.lpTokens)} -

-
- - {removeLiquidityAmount && parseFloat(removeLiquidityAmount) > 0 && ( - - - - You'll receive proportional amounts of SENIOR and JUNIOR tokens - - - )} - - -
-
- - {/* Emergency Withdrawal */} - {vaultInfo.emergencyMode && ( - - - Emergency Withdrawal - - Emergency mode is active. Senior token holders can withdraw with preferred asset. - - - -
-
- - setEmergencyAmount(e.target.value)} - disabled={!isConnected} - className="bg-slate-700/50 border-slate-600 text-white placeholder-slate-400" - /> -
-
- -
- - -
-
-
- -
-
- )} -
-
- - {/* Smart Liquidity Suggestion */} - setShowLiquiditySuggestion(false)} - /> - - {/* Advanced Portfolio Analytics */} -
-
-

Advanced Portfolio Analytics

-
-
- - - Current Positions - Detailed breakdown of your holdings - - -
-
-
- -
-
-

SENIOR

-

Priority claims

-
-
-
-

{formatTokenAmount(balances.seniorTokens)}

-

Value: ${(seniorTokenAmount * parseFloat(seniorPrice)).toFixed(2)}

-
-
- -
-
-
- -
-
-

JUNIOR

-

Higher upside

-
-
-
-

{formatTokenAmount(balances.juniorTokens)}

-

Value: ${(juniorTokenAmount * parseFloat(juniorPrice)).toFixed(2)}

-
-
- -
-
-
- -
-
-

LP TOKENS

-

Liquidity provider

-
-
-
-

{formatTokenAmount(balances.lpTokens)}

-

Pool share

-
-
-
-
- - - - Protocol Status - Current protocol information - - -
- Current Phase: - {vaultInfo.currentPhase !== undefined ? Phase[vaultInfo.currentPhase] : 'Loading...'} -
- -
- Emergency Mode: - - {vaultInfo.emergencyMode ? '๐Ÿšจ Active' : 'โœ… Inactive'} - -
- -
- Total TVL: - ${((Number(vaultInfo.aUSDCBalance) + Number(vaultInfo.cUSDTBalance)) / 1e18).toFixed(2)} -
- -
- Your Share: - - {vaultInfo.totalTokensIssued > 0n - ? ((seniorTokenAmount + juniorTokenAmount) / (Number(vaultInfo.totalTokensIssued) / 1e18) * 100).toFixed(2) - : '0.00'}% - -
-
-
-
-
-
-
- ); -}; - -export default React.memo(Advanced); \ No newline at end of file diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 849fef8..6903b74 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,281 +1,388 @@ -import React, { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; +import React, { useState } from 'react'; import { useWeb3 } from '@/context/PrivyWeb3Context'; import Navbar from '@/components/Navbar'; -import PhaseDisplay from '@/components/PhaseDisplay'; -import QuickTrade from '@/components/QuickTrade'; -import StatCard from '@/components/StatCard'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Alert, AlertDescription } from '@/components/ui/alert'; -import { DollarSign, Coins, Shield, AlertCircle, RefreshCw, Activity, ArrowRight, Droplets } from 'lucide-react'; -import { Phase, ContractName, getContractAddress, SupportedChainId } from '@/config/contracts'; -import { ethers } from 'ethers'; +import { Badge } from '@/components/ui/badge'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { + Shield, + TrendingUp, + DollarSign, + Clock, + RefreshCw, +} from 'lucide-react'; +import { Phase, getPhaseNameFromBigInt } from '@/config/contracts'; +import StatCard from '@/components/StatCard'; + +// Import custom hooks +import { usePricing } from '@/hooks/usePricing'; +import { usePortfolioCalculations } from '@/hooks/usePortfolioCalculations'; + +// Import dashboard components +import PortfolioOverview from '@/components/dashboard/PortfolioOverview'; +import DepositStrategies from '@/components/dashboard/DepositStrategies'; +import PositionManagement from '@/components/dashboard/PositionManagement'; +import AdvancedFeatures from '@/components/dashboard/AdvancedFeatures'; +import MarketOverview from '@/components/dashboard/MarketOverview'; const Dashboard = () => { const { isConnected, - balances, vaultInfo, + depositAsset, + withdraw, + emergencyWithdraw, + swapExactTokensForTokens, getAmountsOut, seniorTokenAddress, juniorTokenAddress, - getPairReserves, - currentChain, - isUnsupportedChain, + stakeRiskTokens, + unstakeRiskTokens, + refreshData, + getTokenBalance, } = useWeb3(); - const [seniorPrice, setSeniorPrice] = useState('0.98'); - const [juniorPrice, setJuniorPrice] = useState('1.05'); - const [pricesLoading, setPricesLoading] = useState(false); - - // Pool reserves state - const [poolReserves, setPoolReserves] = useState({ senior: '0', junior: '0' }); + // Custom hooks for pricing and calculations + const { seniorPrice, juniorPrice, poolReserves } = usePricing(); + const { + formatNumber, + seniorBalance, + juniorBalance, + aUSDCBalance, + cUSDTBalance, + lpBalance, + totalPortfolioValue, + protocolTVL, + userSharePercent, + riskProfile, + } = usePortfolioCalculations(seniorPrice, juniorPrice); - // Format token amounts for display - const formatTokenAmount = (amount: bigint) => { - return ethers.formatEther(amount); - }; + // State Management + const [activeTab, setActiveTab] = useState('overview'); + const [isExecuting, setIsExecuting] = useState(false); + const [isRebalancing, setIsRebalancing] = useState(false); + const [isWithdrawing, setIsWithdrawing] = useState(false); - // Fetch token prices and pool reserves from Uniswap pair - useEffect(() => { - const fetchTokenPrices = async () => { - if (!seniorTokenAddress || !juniorTokenAddress || !getAmountsOut || !currentChain) return; - - setPricesLoading(true); - try { - // Get pool reserves to calculate proper AMM pricing - const pairAddress = getContractAddress(currentChain, ContractName.SENIOR_JUNIOR_PAIR); - const reserves = await getPairReserves(pairAddress); - const seniorReserve = parseFloat(ethers.formatEther(reserves.reserve0)); - const juniorReserve = parseFloat(ethers.formatEther(reserves.reserve1)); - - // Calculate prices directly from Uniswap AMM reserves - // In a Uniswap pair, price = other_reserve / this_reserve - const seniorPriceInJunior = juniorReserve / seniorReserve; - const juniorPriceInSenior = seniorReserve / juniorReserve; - - // For USD pricing, we need to establish a base. - // Let's use getAmountsOut to get actual market prices - try { - // Get price of 1 SENIOR in terms of JUNIOR - const seniorToJuniorPath = [seniorTokenAddress, juniorTokenAddress]; - const seniorPrice1Unit = await getAmountsOut('1', seniorToJuniorPath); - - // Get price of 1 JUNIOR in terms of SENIOR - const juniorToSeniorPath = [juniorTokenAddress, seniorTokenAddress]; - const juniorPrice1Unit = await getAmountsOut('1', juniorToSeniorPath); - - setSeniorPrice(parseFloat(seniorPrice1Unit).toFixed(2)); - setJuniorPrice(parseFloat(juniorPrice1Unit).toFixed(2)); - } catch (error) { - console.error('Error getting AMM prices:', error); - // Fallback to reserve-based calculation - setSeniorPrice(seniorPriceInJunior.toFixed(2)); - setJuniorPrice(juniorPriceInSenior.toFixed(2)); - } - } catch (error) { - console.error('Error fetching token prices:', error); - // Keep default prices on error (equal weighting) - setSeniorPrice('1.00'); - setJuniorPrice('1.00'); - } finally { - setPricesLoading(false); - } - }; - - const fetchPoolReserves = async () => { - if (!getPairReserves || !currentChain) return; - - try { - const pairAddress = getContractAddress(currentChain, ContractName.SENIOR_JUNIOR_PAIR); - const reserves = await getPairReserves(pairAddress); - - // Format reserves from wei to ether - const seniorReserve = ethers.formatEther(reserves.reserve0); - const juniorReserve = ethers.formatEther(reserves.reserve1); - - // Only update if values have changed significantly (avoid micro-updates) - const currentSenior = parseFloat(poolReserves.senior); - const currentJunior = parseFloat(poolReserves.junior); - const newSenior = parseFloat(seniorReserve); - const newJunior = parseFloat(juniorReserve); - - if (Math.abs(currentSenior - newSenior) > 0.01 || Math.abs(currentJunior - newJunior) > 0.01) { - setPoolReserves({ - senior: seniorReserve, - junior: juniorReserve, - }); + // Strategy execution + const executeStrategy = async (strategy: 'safety' | 'upside' | 'balanced', amount: string, asset: 'aUSDC' | 'cUSDT') => { + if (!amount || parseFloat(amount) <= 0) return; + + setIsExecuting(true); + try { + // First deposit to get tokens + await depositAsset(asset, amount); + + if (strategy === 'safety') { + // Convert all junior to senior + const freshJuniorBalance = await getTokenBalance(juniorTokenAddress!); + if (Number(freshJuniorBalance) > 0) { + const path = [juniorTokenAddress!, seniorTokenAddress!]; + const balanceString = formatNumber(Number(freshJuniorBalance) / 1e18, 18); + const estimate = await getAmountsOut(balanceString, path); + const minOutput = (parseFloat(estimate) * 0.95).toFixed(18); + await swapExactTokensForTokens(balanceString, minOutput, path); } - } catch (error) { - console.error('Error fetching pool reserves:', error); - // Only reset if we don't have valid data - if (poolReserves.senior === '0' && poolReserves.junior === '0') { - setPoolReserves({ senior: '0', junior: '0' }); + } else if (strategy === 'upside') { + // Convert all senior to junior + const freshSeniorBalance = await getTokenBalance(seniorTokenAddress!); + if (Number(freshSeniorBalance) > 0) { + const path = [seniorTokenAddress!, juniorTokenAddress!]; + const balanceString = formatNumber(Number(freshSeniorBalance) / 1e18, 18); + const estimate = await getAmountsOut(balanceString, path); + const minOutput = (parseFloat(estimate) * 0.95).toFixed(18); + await swapExactTokensForTokens(balanceString, minOutput, path); } } - }; - - fetchTokenPrices(); - fetchPoolReserves(); - const interval = setInterval(() => { - fetchTokenPrices(); - fetchPoolReserves(); - }, 30000); // Update every 30 seconds to reduce twitching - - return () => clearInterval(interval); - }, [seniorTokenAddress, juniorTokenAddress, getAmountsOut, getPairReserves, currentChain]); - - // Calculate total portfolio value based on risk token holdings and current market prices - const seniorTokenAmount = parseFloat(formatTokenAmount(balances.seniorTokens)); - const juniorTokenAmount = parseFloat(formatTokenAmount(balances.juniorTokens)); - - const totalPortfolioValue = - (seniorTokenAmount * parseFloat(seniorPrice)) + - (juniorTokenAmount * parseFloat(juniorPrice)); + // For balanced, keep 50/50 split from deposit + + await refreshData(); + } catch (error) { + console.error('Strategy execution failed:', error); + } finally { + setIsExecuting(false); + } + }; + + // Rebalancing handler + const handleRebalance = async (targetPercent: number) => { + if (!seniorTokenAddress || !juniorTokenAddress || !swapExactTokensForTokens || !getAmountsOut) return; + + const currentSeniorPercent = riskProfile.percentage; + const percentDiff = targetPercent - currentSeniorPercent; + + if (Math.abs(percentDiff) < 1) return; + + setIsRebalancing(true); + try { + let amountIn: string; + let path: string[]; + + if (targetPercent === 100) { + amountIn = juniorBalance.toFixed(18); + path = [juniorTokenAddress, seniorTokenAddress]; + } else if (targetPercent === 0) { + amountIn = seniorBalance.toFixed(18); + path = [seniorTokenAddress, juniorTokenAddress]; + } else if (percentDiff > 0) { + const totalTokens = seniorBalance + juniorBalance; + const targetSeniorAmount = (totalTokens * targetPercent) / 100; + const seniorNeeded = targetSeniorAmount - seniorBalance; + const juniorToSwap = Math.min(seniorNeeded / parseFloat(juniorPrice), juniorBalance); + amountIn = juniorToSwap.toFixed(18); + path = [juniorTokenAddress, seniorTokenAddress]; + } else { + const totalTokens = seniorBalance + juniorBalance; + const targetJuniorAmount = (totalTokens * (100 - targetPercent)) / 100; + const juniorNeeded = targetJuniorAmount - juniorBalance; + const seniorToSwap = Math.min(juniorNeeded * parseFloat(juniorPrice), seniorBalance); + amountIn = seniorToSwap.toFixed(18); + path = [seniorTokenAddress, juniorTokenAddress]; + } + + if (parseFloat(amountIn) > 0) { + const amountsOut = await getAmountsOut(amountIn, path); + const minAmountOut = (parseFloat(amountsOut) * 0.95).toFixed(18); + await swapExactTokensForTokens(amountIn, minAmountOut, path); + } + + await refreshData(); + } catch (error) { + console.error('Rebalancing failed:', error); + } finally { + setIsRebalancing(false); + } + }; + + // Withdrawal handler + const handleWithdraw = async (amount: string) => { + if (!amount || parseFloat(amount) <= 0) return; + + setIsWithdrawing(true); + try { + // Simplified withdrawal logic - withdraws proportionally to both aUSDC and cUSDT + const halfAmount = (parseFloat(amount) / 2).toFixed(6); + await withdraw(halfAmount, halfAmount); + await refreshData(); + } catch (error) { + console.error('Withdrawal failed:', error); + } finally { + setIsWithdrawing(false); + } + }; + + // Risk token staking handlers + const handleStakeRiskTokens = async (seniorAmount: string, juniorAmount: string) => { + if (!seniorTokenAddress || !juniorTokenAddress) return; + setIsExecuting(true); + try { + await stakeRiskTokens(seniorAmount, juniorAmount, seniorTokenAddress, juniorTokenAddress); + await refreshData(); + } catch (error) { + console.error('Stake risk tokens failed:', error); + } finally { + setIsExecuting(false); + } + }; + + const handleUnstakeRiskTokens = async (amount: string) => { + if (!seniorTokenAddress || !juniorTokenAddress) return; + setIsExecuting(true); + try { + await unstakeRiskTokens(amount, seniorTokenAddress, juniorTokenAddress); + await refreshData(); + } catch (error) { + console.error('Unstake risk tokens failed:', error); + } finally { + setIsExecuting(false); + } + }; + + const handleEmergencyWithdraw = async (amount: string, asset: 'aUSDC' | 'cUSDT') => { + setIsExecuting(true); + try { + await emergencyWithdraw(amount, asset); + await refreshData(); + } catch (error) { + console.error('Emergency withdrawal failed:', error); + } finally { + setIsExecuting(false); + } + }; + + if (!isConnected) { + return ( +
+ +
+ + + +

Connect Your Wallet

+

+ Connect to start earning yield with risk management +

+
+
+
+
+ ); + } return (
- {/* Animated background elements */} + {/* Animated background */}
- + -
-
-
+
+ {/* Header */} +
+
-

- Trading Dashboard -

-

- Smart trading with one-click deposit and risk management +

Portfolio Dashboard

+

+ Smart risk management with tradeable insurance tokens

-
- - Real-time data +
+ + {riskProfile.level} Risk Profile + +
- {/* Connection Status */} - {!isConnected && ( - - - - Please connect your wallet to interact with the protocol - - - )} + {/* Key Metrics Row */} +
+ } + className="text-white" + /> + } + className="text-blue-400" + /> - {/* Quick Trade - Hero Section */} -
- + } + className="text-amber-400" + /> + + } + className="text-white" + />
- - {/* Portfolio Overview */} -
-
-

Your Portfolio

-
Current positions and protocol status
-
-
- - - Your Positions - Current risk token holdings - - -
-
-
- -
-
-

SENIOR

-

Priority claims

-
-
-
-

{formatTokenAmount(balances.seniorTokens)}

-

Value: ${(seniorTokenAmount * parseFloat(seniorPrice)).toFixed(2)}

-
-
- -
-
-
- -
-
-

JUNIOR

-

Higher upside

-
-
-
-

{formatTokenAmount(balances.juniorTokens)}

-

Value: ${(juniorTokenAmount * parseFloat(juniorPrice)).toFixed(2)}

-
-
- -
-
-
- -
-
-

LP TOKENS

-

Liquidity provider

-
-
-
-

{formatTokenAmount(balances.lpTokens)}

-

Pool share

-
-
-
-
- - - Protocol Status - Current protocol information - - -
- Current Phase: - {vaultInfo.currentPhase !== undefined ? Phase[vaultInfo.currentPhase] : 'Loading...'} -
- -
- Emergency Mode: - - {vaultInfo.emergencyMode ? '๐Ÿšจ Active' : 'โœ… Inactive'} - -
- -
- Total TVL: - ${((Number(vaultInfo.aUSDCBalance) + Number(vaultInfo.cUSDTBalance)) / 1e18).toFixed(2)} -
- -
- Your Share: - - {vaultInfo.totalTokensIssued > 0n - ? ((seniorTokenAmount + juniorTokenAmount) / (Number(vaultInfo.totalTokensIssued) / 1e18) * 100).toFixed(2) - : '0.00'}% - -
-
-
-
+ {/* Main Content Tabs */} + + + Overview + Deposit & Trade + Manage Positions + Staking + + + {/* Overview Tab */} + + + + + {/* Deposit & Trade Tab */} + + + + + {/* Manage Positions Tab */} + + + + + {/* Advanced Tab */} + + + + + + {/* Market Overview - Always Visible at Bottom */} +
+
diff --git a/frontend/src/pages/Index.tsx b/frontend/src/pages/Index.tsx index 61c2115..1376b05 100644 --- a/frontend/src/pages/Index.tsx +++ b/frontend/src/pages/Index.tsx @@ -4,11 +4,11 @@ import Logo from '@/assets/images/CoverMax.svg'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { - Shield, - TrendingUp, - Clock, - ArrowRight, +import { + Shield, + TrendingUp, + Clock, + ArrowRight, Zap, BarChart3, Globe @@ -68,8 +68,25 @@ const Index = () => { CoverMax CoverMax - +
+ + +
- + - +
2 @@ -199,9 +216,9 @@ const Index = () => {

Get Tokens

Receive senior + junior

- + - +
3 @@ -210,7 +227,7 @@ const Index = () => {

Buy/sell on Uniswap

- +

Why? Because now you can buy/sell insurance risk like any other token. @@ -255,7 +272,7 @@ const Index = () => {

- +

Both types are tradeable on Uniswap โ€ข Adjust your risk anytime @@ -278,7 +295,7 @@ const Index = () => {

Turn your DeFi assets into tradeable risk tokens.

- +
+ {/* Partners Section */} +
+
+
+

+ Our Supporters +

+

+ Backed by leading organizations in the blockchain ecosystem +

+
+ +
+
+ CBT Partner +
+
+ UCLIE Partner +
+
+
+
+ {/* Footer */}
@@ -307,7 +355,7 @@ const Index = () => { Built for the future of protected finance.

- +

Protocol

@@ -316,7 +364,7 @@ const Index = () => { Analytics
- +

Resources

@@ -326,7 +374,7 @@ const Index = () => {
- +

ยฉ {new Date().getFullYear()} CoverMax Protocol. Decentralized and open source. @@ -343,4 +391,4 @@ const Index = () => { ); }; -export default Index; \ No newline at end of file +export default Index; diff --git a/frontend/src/pages/WidgetDemo.tsx b/frontend/src/pages/WidgetDemo.tsx new file mode 100644 index 0000000..bc3e1b7 --- /dev/null +++ b/frontend/src/pages/WidgetDemo.tsx @@ -0,0 +1,197 @@ +import React, { useState, useCallback } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import PredictionMarketWidget from '@/components/PredictionMarketWidget'; +import Logo from '@/assets/images/CoverMax.svg'; +import { Link } from 'react-router-dom'; +import { useWeb3 } from '@/context/PrivyWeb3Context'; +import NetworkSelector from '@/components/NetworkSelector'; +import { Zap, TrendingUp, DollarSign, Target, Brain } from 'lucide-react'; + +interface ProtocolDemo { + name: string; + logo: string; + supportedAssets: ('aUSDC' | 'cUSDT')[]; +} + +const demoProtocols: ProtocolDemo[] = [ + { + name: 'Aave', + logo: '๐Ÿฆ', + supportedAssets: ['aUSDC'] // Aave only supports aUSDC + }, + { + name: 'Compound', + logo: '๐Ÿ›๏ธ', + supportedAssets: ['cUSDT'] // Compound only supports cUSDT + } +]; + +const WidgetDemo: React.FC = () => { + const [currentOdds, setCurrentOdds] = useState({ hack: 1.02, safe: 1.25 }); + const [seniorPrice, setSeniorPrice] = useState('1.00'); + const [juniorPrice, setJuniorPrice] = useState('1.00'); + const [aiRecommendation, setAiRecommendation] = useState<{ betType: 'hack' | 'safe'; confidence: number } | null>(null); + const { isConnected, address, connectWallet, disconnectWallet, vaultInfo, balances } = useWeb3(); + + // Use Aave as the default protocol + const selectedProtocol = demoProtocols[0]; + + // Format address for display + const formatAddress = (addr: string) => { + return `${addr.slice(0, 6)}...${addr.slice(-4)}`; + }; + + // Memoize the callback to prevent infinite re-renders + const handleOddsUpdate = useCallback((odds: { hack: number; safe: number }, senior: string, junior: string) => { + setCurrentOdds(odds); + setSeniorPrice(senior); + setJuniorPrice(junior); + }, []); + + return ( +

+ {/* Animated background elements */} +
+
+
+
+
+ + {/* Isolated Header - With Clickable Logo */} +
+
+ {/* Clickable Logo and Title */} +
+ + CoverMax Logo + CoverMax + + Widget Demo +
+ + {/* Wallet Connection */} +
+ + {!isConnected ? ( + + ) : ( + + )} +
+
+
+ +
+ {/* Header */} +
+

+ Smart DeFi Insurance Made Simple +

+

+ Get AI-powered recommendations that analyze your portfolio to suggest optimal protection or yield strategies. + No complex insurance jargon - just smart betting with real returns. +

+
+
+
+ Live Protocol +
+
+
+ AI-Powered +
+
+
+ Real Returns +
+ {isConnected && ( +
+
+ Connected +
+ )} +
+
+ + {/* Main Widget Demo */} +
+ +
+ + {/* Key Features */} +
+ + + + + AI Analysis + + + +

AI analyzes your wallet composition and market conditions to recommend optimal betting strategies.

+
+
+ + + + + + Smart Positioning + + + +

Recommendations consider your existing positions to avoid over-concentration and optimize returns.

+
+
+ + + + + + Expected Value + + + +

Real-time calculations show mathematical edge and profit potential for each betting opportunity.

+
+
+
+ + +
+
+ ); +}; + +export default WidgetDemo; + diff --git a/ignition/deployments/chain-296/deployed_addresses.json b/ignition/deployments/chain-296/deployed_addresses.json index 098fd79..956dd4c 100644 --- a/ignition/deployments/chain-296/deployed_addresses.json +++ b/ignition/deployments/chain-296/deployed_addresses.json @@ -1,11 +1,11 @@ { - "RiskTokenModule#MockAUSDC": "0x2Ec140Ba967BD9d2B5C0C138F22E20A530beFaF4", - "RiskTokenModule#MockCUSDT": "0xbc20b977781705589EC578aC7d5680Af821D4c1c", - "RiskTokenModule#UniswapV2Factory": "0x0310c9Cf82c2b0a426F2a5e09d1d670B31442a63", - "RiskTokenModule#WETH": "0x86840528937f5f1578601d3d31Bc7cFF6A540009", - "RiskTokenModule#RiskVault": "0x78a9bCbA870A367559258AAAC5b7e533cF9C0EB7", - "RiskTokenModule#UniswapV2Router02": "0x80E7C7932059ce5E67506B0BC25971B8209e003f", - "RiskTokenModule#juniorTokenContract": "0xc3e818236440461916BF75C91bd28Bb27bEa746e", - "RiskTokenModule#seniorTokenContract": "0x9c6A5C213F5CB326186E876C3e37ec8F73768b2B", - "RiskTokenModule#UniswapV2Pair": "0xb6368834321662839717dbd3dea511525B8382B5" + "RiskTokenModule#MockAUSDC": "0xc6461cf8E77b40293d90c9670FcC2Da04346Df1A", + "RiskTokenModule#MockCUSDT": "0xe786547f22F29E477B51135b24A39E937d291d17", + "RiskTokenModule#UniswapV2Factory": "0x3e83552ED9bF1418Bf50fbc7071C87361Ce156b5", + "RiskTokenModule#WETH": "0xED6eF615b61D37a5E1cce5D5Aaddc1064Fb9fA07", + "RiskTokenModule#RiskVault": "0x86840DBAE8aF63780ffFB2990aADDF5C78aeb184", + "RiskTokenModule#UniswapV2Router02": "0xBA659094Ffd44F3CCcFffd7c172cB42f0aD362b0", + "RiskTokenModule#juniorTokenContract": "0x2DDd2DD26A3d90d4a67F02A69728B386B1461710", + "RiskTokenModule#seniorTokenContract": "0xC5F805bBD905803e5Ec1280827068B9889978cF3", + "RiskTokenModule#UniswapV2Pair": "0x40C4F4B6fB472bFE986134A2A99C691E4323b09E" } diff --git a/test/RiskVault.comprehensive.test.ts b/test/RiskVault.comprehensive.test.ts index 9a246ba..f0a9802 100644 --- a/test/RiskVault.comprehensive.test.ts +++ b/test/RiskVault.comprehensive.test.ts @@ -17,8 +17,7 @@ describe("RiskVault - Comprehensive Tests", function () { let user3: SignerWithAddress; // Phase durations - const DEPOSIT_PHASE_DURATION = 2 * 24 * 60 * 60; // 2 days - const COVERAGE_PHASE_DURATION = 3 * 24 * 60 * 60; // 3 days + const ACTIVE_PHASE_DURATION = 5 * 24 * 60 * 60; // 5 days (was DEPOSIT + COVERAGE) const CLAIMS_PHASE_DURATION = 1 * 24 * 60 * 60; // 1 day const FINAL_CLAIMS_DURATION = 1 * 24 * 60 * 60; // 1 day @@ -71,7 +70,7 @@ describe("RiskVault - Comprehensive Tests", function () { expect(await vault.owner()).to.equal(owner.address); expect(await vault.emergencyMode()).to.equal(false); expect(await vault.totalTokensIssued()).to.equal(0); - expect(await vault.currentPhase()).to.equal(0); // DEPOSIT phase + expect(await vault.currentPhase()).to.equal(0); // ACTIVE phase }); it("Should create and own the risk tokens", async function () { @@ -83,10 +82,10 @@ describe("RiskVault - Comprehensive Tests", function () { expect(await juniorToken.symbol()).to.equal("CM-JUNIOR"); }); - it("Should start in DEPOSIT phase with correct timestamps", async function () { + it("Should start in ACTIVE phase with correct timestamps", async function () { const phaseInfo = await vault.getPhaseInfo(); const currentTime = await time.latest(); - expect(phaseInfo.phase).to.equal(0); // DEPOSIT + expect(phaseInfo.phase).to.equal(0); // ACTIVE expect(phaseInfo.phaseStart).to.be.closeTo(currentTime, 50); expect(phaseInfo.cycleStart).to.be.closeTo(currentTime, 50); }); @@ -100,40 +99,35 @@ describe("RiskVault - Comprehensive Tests", function () { describe("Phase Management", function () { describe("Automatic Phase Transitions", function () { - it("Should transition from DEPOSIT to COVERAGE after 2 days", async function () { - expect(await vault.currentPhase()).to.equal(0); // DEPOSIT + it("Should transition from ACTIVE to CLAIMS after 5 days", async function () { + expect(await vault.currentPhase()).to.equal(0); // ACTIVE - // Move time forward by 2 days - await time.increase(DEPOSIT_PHASE_DURATION); + // Move time forward by 5 days + await time.increase(ACTIVE_PHASE_DURATION); // Trigger phase update manually await vault.forcePhaseTransition(); - expect(await vault.currentPhase()).to.equal(1); // COVERAGE + expect(await vault.currentPhase()).to.equal(1); // CLAIMS }); it("Should transition through all phases with correct timing", async function () { - // Start in DEPOSIT + // Start in ACTIVE expect(await vault.currentPhase()).to.equal(0); - // Move to COVERAGE - await time.increase(DEPOSIT_PHASE_DURATION); - await vault.forcePhaseTransition(); - expect(await vault.currentPhase()).to.equal(1); // COVERAGE - // Move to CLAIMS - await time.increase(COVERAGE_PHASE_DURATION); + await time.increase(ACTIVE_PHASE_DURATION); await vault.forcePhaseTransition(); - expect(await vault.currentPhase()).to.equal(2); // CLAIMS + expect(await vault.currentPhase()).to.equal(1); // CLAIMS // Move to FINAL_CLAIMS await time.increase(CLAIMS_PHASE_DURATION); await vault.forcePhaseTransition(); - expect(await vault.currentPhase()).to.equal(3); // FINAL_CLAIMS + expect(await vault.currentPhase()).to.equal(2); // FINAL_CLAIMS }); it("Should emit PhaseTransitioned events", async function () { - await time.increase(DEPOSIT_PHASE_DURATION); + await time.increase(ACTIVE_PHASE_DURATION); await expect(vault.forcePhaseTransition()) .to.emit(vault, "PhaseTransitioned") .withArgs(0, 1, await time.latest() + 1); @@ -142,16 +136,13 @@ describe("RiskVault - Comprehensive Tests", function () { describe("Manual Phase Transitions", function () { it("Should allow owner to force immediate phase transition", async function () { - expect(await vault.currentPhase()).to.equal(0); // DEPOSIT - - await vault.connect(owner).forcePhaseTransitionImmediate(); - expect(await vault.currentPhase()).to.equal(1); // COVERAGE + expect(await vault.currentPhase()).to.equal(0); // ACTIVE await vault.connect(owner).forcePhaseTransitionImmediate(); - expect(await vault.currentPhase()).to.equal(2); // CLAIMS + expect(await vault.currentPhase()).to.equal(1); // CLAIMS await vault.connect(owner).forcePhaseTransitionImmediate(); - expect(await vault.currentPhase()).to.equal(3); // FINAL_CLAIMS + expect(await vault.currentPhase()).to.equal(2); // FINAL_CLAIMS }); it("Should only allow owner to force transitions", async function () { @@ -163,7 +154,6 @@ describe("RiskVault - Comprehensive Tests", function () { describe("Cycle Management", function () { it("Should start new cycle after FINAL_CLAIMS phase ends", async function () { // Move through all phases - await vault.forcePhaseTransitionImmediate(); // COVERAGE await vault.forcePhaseTransitionImmediate(); // CLAIMS await vault.forcePhaseTransitionImmediate(); // FINAL_CLAIMS @@ -174,9 +164,9 @@ describe("RiskVault - Comprehensive Tests", function () { await expect(vault.startNewCycle()) .to.emit(vault, "CycleStarted") .to.emit(vault, "PhaseTransitioned") - .withArgs(3, 0, await time.latest() + 1); + .withArgs(2, 0, await time.latest() + 1); - expect(await vault.currentPhase()).to.equal(0); // Back to DEPOSIT + expect(await vault.currentPhase()).to.equal(0); // Back to ACTIVE }); it("Should not allow new cycle if not in FINAL_CLAIMS", async function () { @@ -185,7 +175,6 @@ describe("RiskVault - Comprehensive Tests", function () { it("Should not allow new cycle if FINAL_CLAIMS hasn't ended", async function () { // Move to FINAL_CLAIMS - await vault.forcePhaseTransitionImmediate(); // COVERAGE await vault.forcePhaseTransitionImmediate(); // CLAIMS await vault.forcePhaseTransitionImmediate(); // FINAL_CLAIMS @@ -201,8 +190,8 @@ describe("RiskVault - Comprehensive Tests", function () { describe("Phase Information", function () { it("Should return correct phase info", async function () { const phaseInfo = await vault.getPhaseInfo(); - expect(phaseInfo.phase).to.equal(0); // DEPOSIT - expect(phaseInfo.timeRemaining).to.be.closeTo(DEPOSIT_PHASE_DURATION, 50); + expect(phaseInfo.phase).to.equal(0); // ACTIVE + expect(phaseInfo.timeRemaining).to.be.closeTo(ACTIVE_PHASE_DURATION, 50); }); it("Should return correct protocol status", async function () { @@ -211,17 +200,17 @@ describe("RiskVault - Comprehensive Tests", function () { expect(status.emergency).to.equal(false); expect(status.totalTokens).to.equal(0); expect(status.phase).to.equal(0); - expect(status.phaseEndTime).to.be.closeTo(currentTime + DEPOSIT_PHASE_DURATION, 50); + expect(status.phaseEndTime).to.be.closeTo(currentTime + ACTIVE_PHASE_DURATION, 50); }); it("Should update time remaining correctly", async function () { - await time.increase(DEPOSIT_PHASE_DURATION / 2); + await time.increase(ACTIVE_PHASE_DURATION / 2); const phaseInfo = await vault.getPhaseInfo(); - expect(phaseInfo.timeRemaining).to.be.closeTo(DEPOSIT_PHASE_DURATION / 2, 50); + expect(phaseInfo.timeRemaining).to.be.closeTo(ACTIVE_PHASE_DURATION / 2, 50); }); it("Should show zero time remaining when phase should transition", async function () { - await time.increase(DEPOSIT_PHASE_DURATION + 100); + await time.increase(ACTIVE_PHASE_DURATION + 100); const phaseInfo = await vault.getPhaseInfo(); expect(phaseInfo.timeRemaining).to.equal(0); }); @@ -230,7 +219,7 @@ describe("RiskVault - Comprehensive Tests", function () { describe("Deposit Functionality", function () { describe("Basic Deposits", function () { - it("Should allow deposits during DEPOSIT phase", async function () { + it("Should allow deposits during ACTIVE phase", async function () { const tx = await vault.connect(user1).depositAsset(await ausdc.getAddress(), DEPOSIT_AMOUNT); await expect(tx) @@ -294,20 +283,24 @@ describe("RiskVault - Comprehensive Tests", function () { ).to.be.revertedWithCustomError(vault, "UnsupportedAsset"); }); - it("Should revert deposits during non-DEPOSIT phases", async function () { - // Move to COVERAGE phase + it("Should allow deposits during any phase", async function () { + // Move to CLAIMS phase await vault.forcePhaseTransitionImmediate(); + // Should allow deposit during CLAIMS phase + await ausdc.connect(user1).approve(await vault.getAddress(), DEPOSIT_AMOUNT); await expect( vault.connect(user1).depositAsset(await ausdc.getAddress(), DEPOSIT_AMOUNT) - ).to.be.revertedWithCustomError(vault, "InvalidPhaseForDeposit"); + ).to.not.be.reverted; - // Move to CLAIMS phase + // Move to FINAL_CLAIMS phase await vault.forcePhaseTransitionImmediate(); + // Should allow deposit during FINAL_CLAIMS phase + await ausdc.connect(user2).approve(await vault.getAddress(), DEPOSIT_AMOUNT); await expect( - vault.connect(user1).depositAsset(await ausdc.getAddress(), DEPOSIT_AMOUNT) - ).to.be.revertedWithCustomError(vault, "InvalidPhaseForDeposit"); + vault.connect(user2).depositAsset(await ausdc.getAddress(), DEPOSIT_AMOUNT) + ).to.not.be.reverted; }); it("Should revert deposits during emergency mode", async function () { diff --git a/test/RiskVault.integration.test.ts b/test/RiskVault.integration.test.ts index 49418f2..ab14397 100644 --- a/test/RiskVault.integration.test.ts +++ b/test/RiskVault.integration.test.ts @@ -19,8 +19,7 @@ describe("RiskVault - Integration Tests", function () { let mixedInvestor: SignerWithAddress; // Phase durations - const DEPOSIT_PHASE_DURATION = 2 * 24 * 60 * 60; // 2 days - const COVERAGE_PHASE_DURATION = 3 * 24 * 60 * 60; // 3 days + const ACTIVE_PHASE_DURATION = 5 * 24 * 60 * 60; // 5 days (merged deposit + coverage) const CLAIMS_PHASE_DURATION = 1 * 24 * 60 * 60; // 1 day const FINAL_CLAIMS_DURATION = 1 * 24 * 60 * 60; // 1 day @@ -71,25 +70,17 @@ describe("RiskVault - Integration Tests", function () { expect(await vault.getTotalValueLocked()).to.equal(DEPOSIT_AMOUNT * 5n); expect(await vault.totalTokensIssued()).to.equal(DEPOSIT_AMOUNT * 5n); - // Move to COVERAGE phase - await time.increase(DEPOSIT_PHASE_DURATION); - await vault.forcePhaseTransition(); - - // === COVERAGE PHASE === - console.log("=== COVERAGE PHASE ==="); - expect(await vault.currentPhase()).to.equal(1); // COVERAGE - - // During coverage, some users may want to exit early + // During active phase, some users may want to exit early const [seniorBal, juniorBal] = await vault.getUserTokenBalances(mixedInvestor.address); await vault.connect(mixedInvestor).withdraw(seniorBal / 2n, juniorBal / 2n, ZeroAddress); - // Simulate coverage period - await time.increase(COVERAGE_PHASE_DURATION); + // Move to CLAIMS phase after ACTIVE period + await time.increase(ACTIVE_PHASE_DURATION); await vault.forcePhaseTransition(); // === CLAIMS PHASE === console.log("=== CLAIMS PHASE ==="); - expect(await vault.currentPhase()).to.equal(2); // CLAIMS + expect(await vault.currentPhase()).to.equal(1); // CLAIMS // In claims phase, users can withdraw any combination // Senior investors might withdraw only senior tokens @@ -106,7 +97,7 @@ describe("RiskVault - Integration Tests", function () { // === FINAL_CLAIMS PHASE === console.log("=== FINAL_CLAIMS PHASE ==="); - expect(await vault.currentPhase()).to.equal(3); // FINAL_CLAIMS + expect(await vault.currentPhase()).to.equal(2); // FINAL_CLAIMS // Remaining users withdraw their tokens const [senior3Bal, junior3Bal] = await vault.getUserTokenBalances(seniorInvestor2.address); @@ -274,7 +265,7 @@ describe("RiskVault - Integration Tests", function () { await vault.connect(seniorInvestor1).depositAsset(await ausdc.getAddress(), DEPOSIT_AMOUNT); // Move time to just before transition (1 hour before) - await time.increase(DEPOSIT_PHASE_DURATION - 3600); + await time.increase(ACTIVE_PHASE_DURATION - 3600); // Try deposit near end of phase - should still work as we're in DEPOSIT phase await vault.connect(juniorInvestor1).depositAsset(await cusdt.getAddress(), DEPOSIT_AMOUNT); @@ -285,10 +276,11 @@ describe("RiskVault - Integration Tests", function () { // Trigger phase update first await vault.forcePhaseTransition(); - // This should fail because we're no longer in DEPOSIT phase + // Deposits should now work even after phase transition (deposits allowed at any time) + await ausdc.connect(mixedInvestor).approve(await vault.getAddress(), DEPOSIT_AMOUNT); await expect( vault.connect(mixedInvestor).depositAsset(await ausdc.getAddress(), DEPOSIT_AMOUNT) - ).to.be.revertedWithCustomError(vault, "InvalidPhaseForDeposit"); + ).to.not.be.reverted; // But withdrawals should work const [senior, junior] = await vault.getUserTokenBalances(seniorInvestor1.address); diff --git a/test/RiskVault.security.test.ts b/test/RiskVault.security.test.ts index f749077..9d128c5 100644 --- a/test/RiskVault.security.test.ts +++ b/test/RiskVault.security.test.ts @@ -228,20 +228,17 @@ describe("RiskVault - Security Tests", function () { it("Should handle edge cases in phase transitions", async function () { // Test rapid successive phase transitions - expect(await vault.currentPhase()).to.equal(0); // DEPOSIT + expect(await vault.currentPhase()).to.equal(0); // ACTIVE await vault.forcePhaseTransitionImmediate(); - expect(await vault.currentPhase()).to.equal(1); // COVERAGE + expect(await vault.currentPhase()).to.equal(1); // CLAIMS await vault.forcePhaseTransitionImmediate(); - expect(await vault.currentPhase()).to.equal(2); // CLAIMS - - await vault.forcePhaseTransitionImmediate(); - expect(await vault.currentPhase()).to.equal(3); // FINAL_CLAIMS + expect(await vault.currentPhase()).to.equal(2); // FINAL_CLAIMS // Can't transition further without starting new cycle await vault.forcePhaseTransitionImmediate(); - expect(await vault.currentPhase()).to.equal(3); // Still FINAL_CLAIMS + expect(await vault.currentPhase()).to.equal(2); // Still FINAL_CLAIMS }); }); diff --git a/test/RiskVault.withdrawals.test.ts b/test/RiskVault.withdrawals.test.ts index 61e6e77..1a5f723 100644 --- a/test/RiskVault.withdrawals.test.ts +++ b/test/RiskVault.withdrawals.test.ts @@ -115,22 +115,21 @@ describe("RiskVault - Withdrawal Tests", function () { }); }); - describe("Withdrawal during COVERAGE Phase", function () { + describe("Withdrawal during ACTIVE Phase", function () { beforeEach(async function () { await vault.connect(user1).depositAsset(await ausdc.getAddress(), DEPOSIT_AMOUNT); await vault.connect(user2).depositAsset(await cusdt.getAddress(), DEPOSIT_AMOUNT); - // Move to COVERAGE phase - await vault.forcePhaseTransitionImmediate(); + // Stay in ACTIVE phase (no transition needed) }); - it("Should allow withdrawal with equal amounts during COVERAGE", async function () { + it("Should allow withdrawal with equal amounts during ACTIVE", async function () { const [senior, junior] = await vault.getUserTokenBalances(user1.address); await expect(vault.connect(user1).withdraw(senior, junior, ZeroAddress)) .to.emit(vault, "TokensWithdrawn"); }); - it("Should revert withdrawal with unequal amounts during COVERAGE", async function () { + it("Should revert withdrawal with unequal amounts during ACTIVE", async function () { const [senior, junior] = await vault.getUserTokenBalances(user1.address); await expect(