<template>
  <div>
    <h1 id="first">Source Code</h1>
    <p>
      The Kraiken protocol is built on four core Solidity contracts deployed on Base.
      Every contract is immutable (except the upgradeable Optimizer) — what you see here is what runs on-chain.
    </p>
    <p class="warning">Full source and verification will be published at mainnet launch.</p>

    <div class="code-addresses" v-if="krkAddress || stakeAddress">
      <h2 id="contracts">Contract Addresses</h2>
      <div class="code-address-list">
        <div v-if="krkAddress" class="code-address-item">
          <span class="code-address-label">KRK Token</span>
          <a :href="`https://basescan.org/address/${krkAddress}`" target="_blank" rel="noopener noreferrer">{{ krkAddress }}</a>
        </div>
        <div v-if="stakeAddress" class="code-address-item">
          <span class="code-address-label">Stake</span>
          <a :href="`https://basescan.org/address/${stakeAddress}`" target="_blank" rel="noopener noreferrer">{{ stakeAddress }}</a>
        </div>
      </div>
    </div>

    <h2 id="kraiken">Kraiken.sol</h2>
    <p>
      ERC20 token with controlled minting/burning exclusively by the LiquidityManager.
      The staking pool grows proportionally with every mint — 20% of supply is reserved for staking positions.
    </p>
    <div class="code-block">
      <pre><code v-html="highlightSolidity(kraikenSol)"></code></pre>
    </div>

    <h2 id="liquidity-manager">LiquidityManager.sol</h2>
    <p>
      Manages the three-position anti-arbitrage strategy on Uniswap V3 (1% fee tier).
      Handles FLOOR, ANCHOR, and DISCOVERY positions with dynamic parameter adjustment via the Optimizer.
      Uses 5-minute TWAP with 50-tick tolerance to prevent oracle manipulation.
    </p>
    <div class="code-block">
      <pre><code v-html="highlightSolidity(liquidityManagerSol)"></code></pre>
    </div>

    <h2 id="stake">Stake.sol</h2>
    <p>
      Harberger tax-based staking with self-assessed valuations. 30-tier discrete tax rates prevent micro-increment snatching.
      Stakers set their own tax rate — higher rates protect against buyouts but cost more. Tax revenue flows back as UBI.
    </p>
    <div class="code-block">
      <pre><code v-html="highlightSolidity(stakeSol)"></code></pre>
    </div>

    <h2 id="optimizer">OptimizerV3.sol</h2>
    <p>
      Binary bear/bull liquidity optimizer using a direct 2D mapping from (staking%, avgTax) to configuration.
      Bull requires &gt;91% staked with low tax — any decline snaps instantly to bear mode.
      UUPS upgradeable proxy pattern allows parameter tuning without redeploying the core contracts.
    </p>
    <div class="code-block">
      <pre><code v-html="highlightSolidity(optimizerSol)"></code></pre>
    </div>

    <p>
      View the full repository on <a href="https://codeberg.org/johba/harb" target="_blank" rel="noopener noreferrer">Codeberg</a>.
    </p>
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue';

interface CodeDocsEmits {
  (event: 'onmounted'): void;
}

const emit = defineEmits<CodeDocsEmits>();

onMounted(() => {
  emit('onmounted');
});

const krkAddress = import.meta.env.VITE_KRAIKEN_ADDRESS || '';
const stakeAddress = import.meta.env.VITE_STAKE_ADDRESS || '';

function highlightSolidity(code: string): string {
  // Escape HTML first
  let html = code
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');

  // Comments (single-line and multi-line)
  html = html.replace(/(\/\/[^\n]*)/g, '<span class="sol-comment">$1</span>');
  html = html.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="sol-comment">$1</span>');

  // Strings
  html = html.replace(/(&quot;[^&]*?&quot;|"[^"]*?")/g, '<span class="sol-string">$1</span>');

  // Numbers
  html = html.replace(/\b(\d[\d_]*(?:e\d+)?)\b/g, '<span class="sol-number">$1</span>');

  // Keywords
  const keywords = [
    'pragma', 'solidity', 'import', 'contract', 'interface', 'library', 'abstract',
    'function', 'modifier', 'event', 'error', 'struct', 'enum', 'mapping',
    'public', 'private', 'internal', 'external', 'view', 'pure', 'payable',
    'returns', 'return', 'if', 'else', 'for', 'while', 'do', 'break', 'continue',
    'require', 'revert', 'emit', 'new', 'delete', 'try', 'catch',
    'memory', 'storage', 'calldata', 'immutable', 'constant', 'override', 'virtual',
    'is', 'using', 'constructor', 'receive', 'fallback',
  ];
  const kwPattern = new RegExp(`\\b(${keywords.join('|')})\\b`, 'g');
  html = html.replace(kwPattern, '<span class="sol-keyword">$1</span>');

  // Types
  const types = [
    'address', 'bool', 'string', 'bytes', 'bytes32',
    'uint256', 'uint128', 'uint64', 'uint32', 'uint24', 'uint8',
    'int256', 'int24',
  ];
  const typePattern = new RegExp(`\\b(${types.join('|')})\\b`, 'g');
  html = html.replace(typePattern, '<span class="sol-type">$1</span>');

  return html;
}

const kraikenSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import { ERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
import { Math } from "@openzeppelin/utils/math/Math.sol";

/**
 * @title stakeable ERC20 Token
 * @notice This contract implements an ERC20 token with mechanisms for minting and burning in which a single account (staking Pool) is proportionally receiving a share. Only the liquidity manager has permission to manage token supply.
 * @dev Key features:
 * - Controlled minting exclusively by LiquidityManager
 * - Tax collection and redistribution mechanism through staking pool
 * - 20% supply cap for staking (20,000 positions max)
 * - Staking pool receives proportional share of all mints/burns
 */
contract Kraiken is ERC20, ERC20Permit {
    using Math for uint256;

    uint256 public constant VERSION = 2;

    uint256 private constant MIN_STAKE_FRACTION = 3000;
    address private immutable deployer;
    address private liquidityManager;
    address private stakingPool;

    uint256 public previousTotalSupply;

    error ZeroAddressInSetter();
    error AddressAlreadySet();

    modifier onlyLiquidityManager() {
        require(msg.sender == address(liquidityManager), "only liquidity manager");
        _;
    }

    constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) ERC20Permit(name_) {
        deployer = msg.sender;
    }

    function setLiquidityManager(address liquidityManager_) external {
        require(msg.sender == deployer, "only deployer");
        if (address(0) == liquidityManager_) revert ZeroAddressInSetter();
        if (liquidityManager != address(0)) revert AddressAlreadySet();
        liquidityManager = liquidityManager_;
    }

    function setStakingPool(address stakingPool_) external {
        require(msg.sender == deployer, "only deployer");
        if (address(0) == stakingPool_) revert ZeroAddressInSetter();
        if (stakingPool != address(0)) revert AddressAlreadySet();
        stakingPool = stakingPool_;
    }

    function peripheryContracts() external view returns (address, address) {
        return (liquidityManager, stakingPool);
    }

    function minStake() external view returns (uint256) {
        return previousTotalSupply / MIN_STAKE_FRACTION;
    }

    function mint(uint256 _amount) external onlyLiquidityManager {
        if (_amount > 0) {
            uint256 stakingPoolBalance = balanceOf(stakingPool);
            if (stakingPoolBalance > 0) {
                uint256 newStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance);
                _mint(stakingPool, newStake);
            }
            _mint(address(liquidityManager), _amount);
        }
        if (previousTotalSupply == 0) {
            previousTotalSupply = totalSupply();
        }
    }

    function burn(uint256 _amount) external onlyLiquidityManager {
        if (_amount > 0) {
            uint256 stakingPoolBalance = balanceOf(stakingPool);
            if (stakingPoolBalance > 0) {
                uint256 excessStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance);
                _burn(stakingPool, excessStake);
            }
            _burn(address(liquidityManager), _amount);
        }
    }

    function setPreviousTotalSupply(uint256 _ts) external onlyLiquidityManager {
        previousTotalSupply = _ts;
    }

    function outstandingSupply() public view returns (uint256) {
        return totalSupply() - balanceOf(liquidityManager);
    }
}`;

const liquidityManagerSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import { Kraiken } from "./Kraiken.sol";
import { Optimizer } from "./Optimizer.sol";
import { PriceOracle } from "./abstracts/PriceOracle.sol";
import { ThreePositionStrategy } from "./abstracts/ThreePositionStrategy.sol";
import { IWETH9 } from "./interfaces/IWETH9.sol";
import { CallbackValidation } from "@aperture/uni-v3-lib/CallbackValidation.sol";
import { PoolAddress, PoolKey } from "@aperture/uni-v3-lib/PoolAddress.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { IUniswapV3Pool } from "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
import { PositionKey } from "@uniswap-v3-periphery/libraries/PositionKey.sol";

/**
 * @title LiquidityManager
 * @notice Manages liquidity provisioning on Uniswap V3 using the three-position anti-arbitrage strategy
 * @dev Inherits from modular contracts for better separation of concerns and testability
 *
 * Key features:
 * - Three-position anti-arbitrage strategy (ANCHOR, DISCOVERY, FLOOR)
 * - Dynamic parameter adjustment via Optimizer contract
 * - Asymmetric slippage profile prevents profitable arbitrage
 * - Exclusive minting rights for KRAIKEN token
 *
 * Price Validation:
 * - 5-minute TWAP with 50-tick tolerance
 * - Prevents oracle manipulation attacks
 */
contract LiquidityManager is ThreePositionStrategy, PriceOracle {
    using SafeERC20 for IERC20;

    uint24 internal constant FEE = uint24(10_000);

    address private immutable factory;
    IWETH9 private immutable weth;
    Kraiken private immutable kraiken;
    Optimizer private immutable optimizer;
    IUniswapV3Pool private immutable pool;
    bool private immutable token0isWeth;
    PoolKey private poolKey;

    address private immutable deployer;
    address public recenterAccess;
    address public feeDestination;

    int24 public lastRecenterTick;
    uint256 public lastRecenterTime;
    uint256 internal constant MIN_RECENTER_INTERVAL = 60;

    event Recentered(int24 indexed currentTick, bool indexed isUp);

    error ZeroAddressInSetter();
    error AddressAlreadySet();

    modifier onlyFeeDestination() {
        require(msg.sender == address(feeDestination), "only callable by feeDestination");
        _;
    }

    constructor(address _factory, address _WETH9, address _kraiken, address _optimizer) {
        deployer = msg.sender;
        factory = _factory;
        weth = IWETH9(_WETH9);
        poolKey = PoolAddress.getPoolKey(_WETH9, _kraiken, FEE);
        pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
        kraiken = Kraiken(_kraiken);
        token0isWeth = _WETH9 < _kraiken;
        optimizer = Optimizer(_optimizer);
    }

    function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
        CallbackValidation.verifyCallback(factory, poolKey);
        uint256 kraikenPulled = token0isWeth ? amount1Owed : amount0Owed;
        uint256 kraikenBalance = kraiken.balanceOf(address(this));
        if (kraikenBalance < kraikenPulled) {
            kraiken.mint(kraikenPulled - kraikenBalance);
        }
        uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed;
        if (weth.balanceOf(address(this)) < ethOwed) {
            weth.deposit{ value: address(this).balance }();
        }
        if (amount0Owed > 0) IERC20(poolKey.token0).safeTransfer(msg.sender, amount0Owed);
        if (amount1Owed > 0) IERC20(poolKey.token1).safeTransfer(msg.sender, amount1Owed);
    }

    function setFeeDestination(address feeDestination_) external {
        require(msg.sender == deployer, "only deployer");
        if (address(0) == feeDestination_) revert ZeroAddressInSetter();
        if (feeDestination != address(0)) revert AddressAlreadySet();
        feeDestination = feeDestination_;
    }

    function setRecenterAccess(address addr) external onlyFeeDestination {
        recenterAccess = addr;
    }

    function revokeRecenterAccess() external onlyFeeDestination {
        recenterAccess = address(0);
    }

    function recenter() external returns (bool isUp) {
        (, int24 currentTick,,,,,) = pool.slot0();
        if (recenterAccess != address(0)) {
            require(msg.sender == recenterAccess, "access denied");
        } else {
            require(block.timestamp >= lastRecenterTime + MIN_RECENTER_INTERVAL, "recenter cooldown");
            require(_isPriceStable(currentTick), "price deviated from oracle");
        }
        lastRecenterTime = block.timestamp;

        isUp = false;
        if (positions[Stage.ANCHOR].liquidity > 0) {
            int24 anchorTickLower = positions[Stage.ANCHOR].tickLower;
            int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
            int24 centerTick = anchorTickLower + (anchorTickUpper - anchorTickLower) / 2;
            bool isEnough;
            (isUp, isEnough) = _validatePriceMovement(currentTick, centerTick, TICK_SPACING, token0isWeth);
            require(isEnough, "amplitude not reached.");
        }

        bool shouldRecordVWAP;
        if (cumulativeVolume == 0) {
            shouldRecordVWAP = true;
        } else if (lastRecenterTick != 0) {
            shouldRecordVWAP = token0isWeth ? (currentTick < lastRecenterTick) : (currentTick > lastRecenterTick);
        } else {
            shouldRecordVWAP = true;
        }
        lastRecenterTick = currentTick;

        _scrapePositions(shouldRecordVWAP);

        if (isUp) {
            kraiken.setPreviousTotalSupply(kraiken.totalSupply());
        }

        try optimizer.getLiquidityParams() returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
            PositionParams memory params = PositionParams({
                capitalInefficiency: (capitalInefficiency > 10 ** 18) ? 10 ** 18 : capitalInefficiency,
                anchorShare: (anchorShare > 10 ** 18) ? 10 ** 18 : anchorShare,
                anchorWidth: (anchorWidth > 100) ? 100 : anchorWidth,
                discoveryDepth: (discoveryDepth > 10 ** 18) ? 10 ** 18 : discoveryDepth
            });
            _setPositions(currentTick, params);
        } catch {
            PositionParams memory defaultParams = PositionParams({
                capitalInefficiency: 0,
                anchorShare: 3e17,
                anchorWidth: 100,
                discoveryDepth: 3e17
             });
            _setPositions(currentTick, defaultParams);
        }

        emit Recentered(currentTick, isUp);
    }

    function _scrapePositions(bool recordVWAP) internal {
        uint256 fee0 = 0;
        uint256 fee1 = 0;
        uint256 currentPrice;

        for (uint256 i = uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
            TokenPosition storage position = positions[Stage(i)];
            if (position.liquidity > 0) {
                (uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
                (uint256 collected0, uint256 collected1) =
                    pool.collect(address(this), position.tickLower, position.tickUpper, type(uint128).max, type(uint128).max);
                fee0 += collected0 - amount0;
                fee1 += collected1 - amount1;
                if (i == uint256(Stage.ANCHOR)) {
                    int24 tick = position.tickLower + ((position.tickUpper - position.tickLower) / 2);
                    currentPrice = _priceAtTick(token0isWeth ? -1 * tick : tick);
                }
            }
        }

        if (fee0 > 0) {
            if (token0isWeth) {
                IERC20(address(weth)).safeTransfer(feeDestination, fee0);
                if (recordVWAP) _recordVolumeAndPrice(currentPrice, fee0);
            } else {
                IERC20(address(kraiken)).safeTransfer(feeDestination, fee0);
            }
        }
        if (fee1 > 0) {
            if (token0isWeth) {
                IERC20(address(kraiken)).safeTransfer(feeDestination, fee1);
            } else {
                IERC20(address(weth)).safeTransfer(feeDestination, fee1);
                if (recordVWAP) _recordVolumeAndPrice(currentPrice, fee1);
            }
        }
    }

    receive() external payable { }

    function _getPool() internal view override returns (IUniswapV3Pool) { return pool; }
    function _getKraikenToken() internal view override returns (address) { return address(kraiken); }
    function _getWethToken() internal view override returns (address) { return address(weth); }
    function _isToken0Weth() internal view override returns (bool) { return token0isWeth; }

    function _mintPosition(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal override {
        pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
        positions[stage] = TokenPosition({ liquidity: liquidity, tickLower: tickLower, tickUpper: tickUpper });
    }

    function _getEthBalance() internal view override returns (uint256) {
        return address(this).balance + weth.balanceOf(address(this));
    }

    function _getOutstandingSupply() internal view override returns (uint256) {
        uint256 supply = kraiken.outstandingSupply();
        if (feeDestination != address(0)) {
            supply -= kraiken.balanceOf(feeDestination);
        }
        (, address stakingPoolAddr) = kraiken.peripheryContracts();
        if (stakingPoolAddr != address(0)) {
            supply -= kraiken.balanceOf(stakingPoolAddr);
        }
        return supply;
    }
}`;

const stakeSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import { Kraiken } from "./Kraiken.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
import { IERC20Metadata } from "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/utils/math/Math.sol";

error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStake, uint256 smallestShare);

/**
 * @title Stake Contract for Kraiken Token
 * @notice Manages staking positions with Harberger tax. Stakers set and adjust tax rates,
 *         which affect UBI paid from the tax pool. Handles position creation, snatching,
 *         tax calculation, rate adjustment, and exit.
 */
contract Stake {
    using Math for uint256;

    uint256 internal DECIMAL_OFFSET = 5 + 2;
    uint256 internal constant MAX_STAKE = 20;
    uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3;
    uint256[] public TAX_RATES =
        [1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700];
    uint256 internal constant TAX_RATE_BASE = 100;

    error TaxTooLow(address receiver, uint64 taxRateWanted, uint64 taxRateMet, uint256 positionId);
    error StakeTooLow(address receiver, uint256 assets, uint256 minStake);
    error NoPermission(address requester, address owner);
    error PositionNotFound(uint256 positionId, address requester);

    event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 kraikenDeposit, uint256 share, uint32 taxRate);
    event PositionTaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxPaid, uint256 newShares, uint256 taxRate);
    event PositionRateHiked(uint256 indexed positionId, address indexed owner, uint256 newTaxRate);
    event PositionShrunk(uint256 indexed positionId, address indexed owner, uint256 newShares, uint256 kraikenPayout);
    event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 kraikenPayout);

    struct StakingPosition {
        uint256 share;
        address owner;
        uint32 creationTime;
        uint32 lastTaxTime;
        uint32 taxRate;
    }

    Kraiken private immutable kraiken;
    address private immutable taxReceiver;

    uint256 public immutable totalSupply;
    uint256 public outstandingStake;
    uint256 public nextPositionId;

    mapping(uint256 => StakingPosition) public positions;
    uint256[] public totalSharesAtTaxRate;

    constructor(address _kraiken, address _taxReceiver) {
        kraiken = Kraiken(_kraiken);
        taxReceiver = _taxReceiver;
        totalSupply = 10 ** (kraiken.decimals() + DECIMAL_OFFSET);
        nextPositionId = 654_321;
        totalSharesAtTaxRate = new uint256[](TAX_RATES.length);
    }

    function authorizedStake() private view returns (uint256) {
        return totalSupply * MAX_STAKE / 100;
    }

    function _payTax(uint256 positionId, StakingPosition storage pos, uint256 taxFloorDuration) private {
        uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : block.timestamp;
        uint256 elapsedTime = ihet - pos.lastTaxTime;
        uint256 assetsBefore = sharesToAssets(pos.share);
        uint256 taxAmountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
        if (taxAmountDue >= assetsBefore) {
            taxAmountDue = assetsBefore;
        }
        if (assetsBefore - taxAmountDue > 0) {
            uint256 shareAfterTax = assetsToShares(assetsBefore - taxAmountDue);
            uint256 deltaShare = pos.share - shareAfterTax;
            totalSharesAtTaxRate[pos.taxRate] -= deltaShare;
            outstandingStake -= deltaShare;
            pos.share = shareAfterTax;
            pos.lastTaxTime = uint32(block.timestamp);
            emit PositionTaxPaid(positionId, pos.owner, taxAmountDue, shareAfterTax, pos.taxRate);
        } else {
            totalSharesAtTaxRate[pos.taxRate] -= pos.share;
            outstandingStake -= pos.share;
            emit PositionTaxPaid(positionId, pos.owner, taxAmountDue, 0, pos.taxRate);
            emit PositionRemoved(positionId, pos.owner, 0);
            delete pos.owner;
            delete pos.creationTime;
            delete pos.share;
        }
        SafeERC20.safeTransfer(kraiken, taxReceiver, taxAmountDue);
    }

    function _exitPosition(uint256 positionId, StakingPosition storage pos) private {
        totalSharesAtTaxRate[pos.taxRate] -= pos.share;
        outstandingStake -= pos.share;
        address owner = pos.owner;
        uint256 assets = sharesToAssets(pos.share);
        emit PositionRemoved(positionId, owner, assets);
        delete pos.owner;
        delete pos.creationTime;
        delete pos.share;
        SafeERC20.safeTransfer(kraiken, owner, assets);
    }

    function _shrinkPosition(uint256 positionId, StakingPosition storage pos, uint256 sharesToTake) private {
        require(sharesToTake < pos.share, "position too small");
        uint256 assets = sharesToAssets(sharesToTake);
        pos.share -= sharesToTake;
        totalSharesAtTaxRate[pos.taxRate] -= sharesToTake;
        outstandingStake -= sharesToTake;
        emit PositionShrunk(positionId, pos.owner, pos.share, assets);
        SafeERC20.safeTransfer(kraiken, pos.owner, assets);
    }

    function assetsToShares(uint256 assets) public view returns (uint256) {
        return assets.mulDiv(totalSupply, kraiken.totalSupply(), Math.Rounding.Down);
    }

    function sharesToAssets(uint256 shares) public view returns (uint256) {
        return shares.mulDiv(kraiken.totalSupply(), totalSupply, Math.Rounding.Down);
    }

    function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch) public returns (uint256 positionId) {
        uint256 sharesWanted = assetsToShares(assets);
        {
            uint256 minStake = kraiken.minStake();
            if (assets < minStake) {
                revert StakeTooLow(receiver, assets, minStake);
            }
        }
        require(taxRate < TAX_RATES.length, "tax rate out of bounds");

        uint256 smallestPositionShare = totalSupply;
        uint256 availableStake = authorizedStake() - outstandingStake;

        if (positionsToSnatch.length >= 2) {
            for (uint256 i = 0; i < positionsToSnatch.length - 1; i++) {
                StakingPosition storage pos = positions[positionsToSnatch[i]];
                if (pos.creationTime == 0) { revert PositionNotFound(positionsToSnatch[i], receiver); }
                if (taxRate <= pos.taxRate) { revert TaxTooLow(receiver, taxRate, pos.taxRate, positionsToSnatch[i]); }
                if (pos.share < smallestPositionShare) { smallestPositionShare = pos.share; }
                _payTax(positionsToSnatch[i], pos, 0);
                _exitPosition(positionsToSnatch[i], pos);
            }
        }
        availableStake = authorizedStake() - outstandingStake;

        if (positionsToSnatch.length > 0) {
            uint256 index = positionsToSnatch.length - 1;
            StakingPosition storage lastPos = positions[positionsToSnatch[index]];
            if (lastPos.creationTime == 0) { revert PositionNotFound(positionsToSnatch[index], receiver); }
            if (taxRate <= lastPos.taxRate) { revert TaxTooLow(receiver, taxRate, lastPos.taxRate, positionsToSnatch[index]); }
            if (lastPos.share < smallestPositionShare) { smallestPositionShare = lastPos.share; }
            _payTax(positionsToSnatch[index], lastPos, TAX_FLOOR_DURATION);
            if (availableStake > sharesWanted) { revert TooMuchSnatch(receiver, sharesWanted, availableStake, smallestPositionShare); }
            uint256 lastSharesNeeded = sharesWanted - availableStake;
            if (lastSharesNeeded > lastPos.share * 80 / 100) {
                _exitPosition(positionsToSnatch[index], lastPos);
            } else {
                _shrinkPosition(positionsToSnatch[index], lastPos, lastSharesNeeded);
            }
        }
        availableStake = authorizedStake() - outstandingStake;

        if (sharesWanted > availableStake) { revert ExceededAvailableStake(receiver, sharesWanted, availableStake); }
        if (availableStake - sharesWanted > smallestPositionShare) { revert TooMuchSnatch(receiver, sharesWanted, availableStake, smallestPositionShare); }

        SafeERC20.safeTransferFrom(kraiken, msg.sender, address(this), assets);

        positionId = nextPositionId++;
        StakingPosition storage sp = positions[positionId];
        sp.share = sharesWanted;
        sp.owner = receiver;
        sp.lastTaxTime = uint32(block.timestamp);
        sp.creationTime = uint32(block.timestamp);
        sp.taxRate = taxRate;

        totalSharesAtTaxRate[taxRate] += sharesWanted;
        outstandingStake += sharesWanted;
        emit PositionCreated(positionId, sp.owner, assets, sp.share, sp.taxRate);
    }

    function permitAndSnatch(
        uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch,
        uint256 deadline, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint256 positionId) {
        ERC20Permit(address(kraiken)).permit(receiver, address(this), assets, deadline, v, r, s);
        return snatch(assets, receiver, taxRate, positionsToSnatch);
    }

    function changeTax(uint256 positionId, uint32 taxRate) external {
        require(taxRate < TAX_RATES.length, "tax rate out of bounds");
        StakingPosition storage pos = positions[positionId];
        if (pos.creationTime == 0) { revert PositionNotFound(positionId, msg.sender); }
        if (pos.owner != msg.sender) { revert NoPermission(msg.sender, pos.owner); }
        require(taxRate > pos.taxRate, "tax too low to snatch");
        _payTax(positionId, pos, 0);
        totalSharesAtTaxRate[pos.taxRate] -= pos.share;
        totalSharesAtTaxRate[taxRate] += pos.share;
        pos.taxRate = taxRate;
        emit PositionRateHiked(positionId, pos.owner, taxRate);
    }

    function exitPosition(uint256 positionId) external {
        StakingPosition storage pos = positions[positionId];
        if (pos.owner != msg.sender) { revert NoPermission(msg.sender, pos.owner); }
        if (pos.creationTime == 0) { revert PositionNotFound(positionId, msg.sender); }
        _payTax(positionId, pos, TAX_FLOOR_DURATION);
        _exitPosition(positionId, pos);
    }

    function payTax(uint256 positionId) external {
        StakingPosition storage pos = positions[positionId];
        if (pos.creationTime == 0) { revert PositionNotFound(positionId, msg.sender); }
        _payTax(positionId, pos, 0);
    }

    function taxDue(uint256 positionId, uint256 taxFloorDuration) public view returns (uint256 amountDue) {
        StakingPosition storage pos = positions[positionId];
        uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : block.timestamp;
        uint256 elapsedTime = ihet - pos.lastTaxTime;
        uint256 assetsBefore = sharesToAssets(pos.share);
        amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
    }

    function getAverageTaxRate() external view returns (uint256 averageTaxRate) {
        averageTaxRate = 0;
        if (outstandingStake > 0) {
            for (uint256 i = 0; i < TAX_RATES.length; i++) {
                averageTaxRate += TAX_RATES[i] * totalSharesAtTaxRate[i];
            }
            averageTaxRate = averageTaxRate / outstandingStake;
            averageTaxRate = averageTaxRate * 1e18 / TAX_RATES[TAX_RATES.length - 1];
        }
    }

    function getPercentageStaked() external view returns (uint256 percentageStaked) {
        percentageStaked = (outstandingStake * 1e18) / authorizedStake();
    }
}`;

const optimizerSol = `// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import { Kraiken } from "./Kraiken.sol";
import { Stake } from "./Stake.sol";

import { Initializable } from "@openzeppelin/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";

/**
 * @title OptimizerV3
 * @notice Direct 2D (staking%, avgTax) to binary bear/bull liquidity optimizer.
 * @dev Replaces the three-zone score-based model with a direct mapping:
 *
 *      staked <= 91% -> BEAR always (no euphoria signal)
 *      staked > 91% -> BULL if deltaS^3 * effIdx / 20 < 50, else BEAR
 *
 *      Bear: AS=30%, AW=100, CI=0, DD=0.3e18
 *      Bull: AS=100%, AW=20, CI=0, DD=1e18
 */
contract OptimizerV3 is Initializable, UUPSUpgradeable {
    Kraiken private kraiken;
    Stake private stake;

    uint256[48] private __gap;

    error UnauthorizedAccount(address account);

    function initialize(address _kraiken, address _stake) public initializer {
        _changeAdmin(msg.sender);
        kraiken = Kraiken(_kraiken);
        stake = Stake(_stake);
    }

    modifier onlyAdmin() {
        _checkAdmin();
        _;
    }

    function _checkAdmin() internal view virtual {
        if (_getAdmin() != msg.sender) {
            revert UnauthorizedAccount(msg.sender);
        }
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { }

    function _taxRateToEffectiveIndex(uint256 averageTaxRate) internal pure returns (uint256) {
        uint256 idx;
        if (averageTaxRate <= 206_185_567_010_309) idx = 0;
        else if (averageTaxRate <= 412_371_134_020_618) idx = 1;
        else if (averageTaxRate <= 618_556_701_030_927) idx = 2;
        else if (averageTaxRate <= 1_030_927_835_051_546) idx = 3;
        else if (averageTaxRate <= 1_546_391_752_577_319) idx = 4;
        else if (averageTaxRate <= 2_164_948_453_608_247) idx = 5;
        else if (averageTaxRate <= 2_783_505_154_639_175) idx = 6;
        else if (averageTaxRate <= 3_608_247_422_680_412) idx = 7;
        else if (averageTaxRate <= 4_639_175_257_731_958) idx = 8;
        else if (averageTaxRate <= 5_670_103_092_783_505) idx = 9;
        else if (averageTaxRate <= 7_216_494_845_360_824) idx = 10;
        else if (averageTaxRate <= 9_278_350_515_463_917) idx = 11;
        else if (averageTaxRate <= 11_855_670_103_092_783) idx = 12;
        else if (averageTaxRate <= 15_979_381_443_298_969) idx = 13;
        else if (averageTaxRate <= 22_164_948_453_608_247) idx = 14;
        else if (averageTaxRate <= 29_381_443_298_969_072) idx = 15;
        else if (averageTaxRate <= 38_144_329_896_907_216) idx = 16;
        else if (averageTaxRate <= 49_484_536_082_474_226) idx = 17;
        else if (averageTaxRate <= 63_917_525_773_195_876) idx = 18;
        else if (averageTaxRate <= 83_505_154_639_175_257) idx = 19;
        else if (averageTaxRate <= 109_278_350_515_463_917) idx = 20;
        else if (averageTaxRate <= 144_329_896_907_216_494) idx = 21;
        else if (averageTaxRate <= 185_567_010_309_278_350) idx = 22;
        else if (averageTaxRate <= 237_113_402_061_855_670) idx = 23;
        else if (averageTaxRate <= 309_278_350_515_463_917) idx = 24;
        else if (averageTaxRate <= 402_061_855_670_103_092) idx = 25;
        else if (averageTaxRate <= 520_618_556_701_030_927) idx = 26;
        else if (averageTaxRate <= 680_412_371_134_020_618) idx = 27;
        else if (averageTaxRate <= 886_597_938_144_329_896) idx = 28;
        else idx = 29;

        if (idx >= 14) {
            idx = idx + 1;
            if (idx > 29) idx = 29;
        }

        return idx;
    }

    function isBullMarket(uint256 percentageStaked, uint256 averageTaxRate) public pure returns (bool bull) {
        require(percentageStaked <= 1e18, "Invalid percentage staked");
        uint256 stakedPct = percentageStaked * 100 / 1e18;
        if (stakedPct <= 91) return false;

        uint256 deltaS = 100 - stakedPct;
        uint256 effIdx = _taxRateToEffectiveIndex(averageTaxRate);
        uint256 penalty = deltaS * deltaS * deltaS * effIdx / 20;
        return penalty < 50;
    }

    function getLiquidityParams() external view returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
        uint256 percentageStaked = stake.getPercentageStaked();
        uint256 averageTaxRate = stake.getAverageTaxRate();

        capitalInefficiency = 0;

        if (isBullMarket(percentageStaked, averageTaxRate)) {
            anchorShare = 1e18;
            anchorWidth = 20;
            discoveryDepth = 1e18;
        } else {
            anchorShare = 3e17;
            anchorWidth = 100;
            discoveryDepth = 3e17;
        }
    }
}`;
</script>

<style scoped lang="sass">
.code-addresses
    margin: 32px 0
    padding: 20px
    background: rgba(255, 255, 255, 0.05)
    border-radius: 8px

.code-address-list
    display: flex
    flex-direction: column
    gap: 12px
    margin-top: 12px

.code-address-item
    display: flex
    align-items: center
    gap: 12px
    flex-wrap: wrap
    .code-address-label
        font-weight: 600
        font-size: 14px
        color: #9A9898
        min-width: 80px
    a
        font-family: monospace
        font-size: 13px
        color: #7550AE
        word-break: break-all

.code-block
    margin: 16px 0 32px
    border-radius: 8px
    overflow: hidden
    pre
        background: #0d1117
        padding: 20px
        overflow-x: auto
        margin: 0
        border: 1px solid rgba(255, 255, 255, 0.1)
        border-radius: 8px
        code
            font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace
            font-size: 13px
            line-height: 1.5
            color: #e6edf3
            white-space: pre
            :deep(.sol-keyword)
                color: #c084fc
            :deep(.sol-type)
                color: #7dd3fc
            :deep(.sol-comment)
                color: #6b7280
            :deep(.sol-string)
                color: #a5d6ff
            :deep(.sol-number)
                color: #79c0ff
</style>
