Full Code of alchemix-finance/v3 for AI

master 0aa8f99d9f44 /src cached
148 files
1.8 MB
437.7k tokens
4 requests
Download .txt
Showing preview only (1,891K chars total). Download the full file or copy to clipboard to get everything.
Repository: alchemix-finance/v3
Branch: master
Commit: 0aa8f99d9f44
Subpath: /src
Files: 148
Total size: 1.8 MB

Directory structure:
└── src/
    ├── AlTokenV3.sol
    ├── AlchemistAllocator.sol
    ├── AlchemistCurator.sol
    ├── AlchemistETHVault.sol
    ├── AlchemistGate.sol
    ├── AlchemistStrategyClassifier.sol
    ├── AlchemistTokenVault.sol
    ├── AlchemistV3.sol
    ├── AlchemistV3Position.sol
    ├── AlchemistV3PositionRenderer.sol
    ├── FrxEthEthDualOracleAggregatorAdapter.sol
    ├── MYTStrategy.sol
    ├── PerpetualGauge.sol
    ├── Transmuter.sol
    ├── adapters/
    │   ├── AbstractFeeVault.sol
    │   └── EulerUSDCAdapter.sol
    ├── base/
    │   ├── ErrorMessages.sol
    │   ├── Errors.sol
    │   └── TransmuterErrors.sol
    ├── external/
    │   ├── AlEth.sol
    │   └── interfaces/
    │       ├── IDetailedERC20.sol
    │       ├── ISettlerActions.sol
    │       └── IVelodromePair.sol
    ├── interfaces/
    │   ├── IAlchemicToken.sol
    │   ├── IAlchemistCurator.sol
    │   ├── IAlchemistETHVault.sol
    │   ├── IAlchemistTokenVault.sol
    │   ├── IAlchemistV3.sol
    │   ├── IAlchemistV3Position.sol
    │   ├── IAllocator.sol
    │   ├── IERC20Burnable.sol
    │   ├── IERC20Metadata.sol
    │   ├── IERC20Minimal.sol
    │   ├── IERC20Mintable.sol
    │   ├── IERC721Enumerable.sol
    │   ├── IFeeVault.sol
    │   ├── IMYTStrategy.sol
    │   ├── IMetadataRenderer.sol
    │   ├── IStrategyClassifier.sol
    │   ├── ITokenAdapter.sol
    │   ├── ITransmuter.sol
    │   ├── IWETH.sol
    │   ├── IWhitelist.sol
    │   ├── IWstETHLike.sol
    │   ├── IYearnVaultV2.sol
    │   ├── IYieldToken.sol
    │   └── test/
    │       └── ITestYieldToken.sol
    ├── libraries/
    │   ├── FixedPointMath.sol
    │   ├── NFTMetadataGenerator.sol
    │   ├── SafeCast.sol
    │   ├── SafeERC20.sol
    │   ├── Sets.sol
    │   ├── StakingGraph.sol
    │   └── TokenUtils.sol
    ├── mocks/
    │   ├── ERC20Mock.sol
    │   ├── FixedPointMathOld.sol
    │   ├── Pool.sol
    │   ├── Stake.sol
    │   └── StakingPoolMock.sol
    ├── router/
    │   └── AlchemistRouter.sol
    ├── strategies/
    │   ├── AaveStrategy.sol
    │   ├── ERC4626Strategy.sol
    │   ├── EtherfiEETHStrategy.sol
    │   ├── MoonwellStrategy.sol
    │   ├── OraclePricedSwapStrategy.sol
    │   ├── SFraxETHStrategy.sol
    │   ├── SiUSDStrategy.sol
    │   ├── TokeAutoStrategy.sol
    │   ├── WstETHEthereumStrategy.sol
    │   ├── WstETHL2Strategy.sol
    │   └── interfaces/
    │       └── ITokemac.sol
    ├── test/
    │   ├── AlchemistAllocator.t.sol
    │   ├── AlchemistCurator.t.sol
    │   ├── AlchemistETHVault.t.sol
    │   ├── AlchemistStrategyClassifier.t.sol
    │   ├── AlchemistTokenVault.t.sol
    │   ├── AlchemistV3.t.sol
    │   ├── AlchemistV3_6_decimals.t.sol
    │   ├── BaseStrategyTest.sol
    │   ├── DeploySFraxETHStrategyScript.t.sol
    │   ├── DeploySiUSDStrategiesScript.t.sol
    │   ├── DeployWstETHEthereumStrategyScript.t.sol
    │   ├── DeployWstETHL2StrategyScript.t.sol
    │   ├── DeployYvWETHStrategyScript.t.sol
    │   ├── FrxEthEthDualOracleAggregatorAdapter.t.sol
    │   ├── IntegrationTest.t.sol
    │   ├── Invariants/
    │   │   ├── CrucibleTest.sol
    │   │   ├── FullSystemInvariantsTest.sol
    │   │   ├── HardenedInvariantsTest.sol
    │   │   └── InvariantBaseTest.t.sol
    │   ├── InvariantsTest.t.sol
    │   ├── MYTStrategy.t.sol
    │   ├── MultiStrategyARBETH.invariant.t.sol
    │   ├── MultiStrategyARBUSDC.invariant.t.sol
    │   ├── MultiStrategyETH.invariant.t.sol
    │   ├── MultiStrategyOPETH.invariant.t.sol
    │   ├── MultiStrategyOPUSDC.invariant.t.sol
    │   ├── MultiStrategyUSDC.invariant.t.sol
    │   ├── PerpetualGaugeTest.t.sol
    │   ├── README.md
    │   ├── Transmuter.t.sol
    │   ├── ZeroXSwapVerifier.t.sol
    │   ├── base/
    │   │   ├── BaseStrategyMulti.sol
    │   │   ├── BaseStrategySimple.sol
    │   │   ├── StrategyHandler.sol
    │   │   ├── StrategyOps.sol
    │   │   ├── StrategyRevertUtils.sol
    │   │   ├── StrategySetup.sol
    │   │   └── StrategyTypes.sol
    │   ├── libraries/
    │   │   ├── AlchemistNFTHelper.sol
    │   │   ├── CustomBase64.sol
    │   │   └── MYTTestHelper.sol
    │   ├── mocks/
    │   │   ├── AlchemicTokenV3.sol
    │   │   ├── MockAlchemistAllocator.sol
    │   │   ├── MockAlchemistCurator.sol
    │   │   ├── MockMYTStrategy.sol
    │   │   ├── MockMYTVault.sol
    │   │   ├── MockWETH.sol
    │   │   ├── MockYieldToken.sol
    │   │   ├── TestERC20.sol
    │   │   ├── TestYieldToken.sol
    │   │   └── TokenAdapterMock.sol
    │   ├── router/
    │   │   └── AlchemistRouter.t.sol
    │   └── strategies/
    │       ├── AaveV3ARBUSDCStrategy.t.sol
    │       ├── AaveV3ARBWETHStrategy.t.sol
    │       ├── AaveV3ETHWETHStrategy.t.sol
    │       ├── AaveV3OPUSDCStrategy.t.sol
    │       ├── AaveV3OPWETHStrategy.t.sol
    │       ├── EtherfiEETHStrategy.t.sol
    │       ├── EulerARBUSDCStrategy.t.sol
    │       ├── EulerARBWETHStrategy.t.sol
    │       ├── EulerUSDCStrategy.t.sol
    │       ├── EulerWETHStrategy.t.sol
    │       ├── FluidARBUSDCStrategy.t.sol
    │       ├── SFraxETHStrategy.t.sol
    │       ├── SiUSDStrategy.t.sol
    │       ├── TokeAutoETHStrategy.t.sol
    │       ├── TokeAutoUSDStrategy.t.sol
    │       ├── WstethMainnetStrategy.t.sol
    │       ├── WstethOptimismStrategy.t.sol
    │       ├── YvUSDCStrategy.t.sol
    │       ├── YvWETHStrategy.t.sol
    │       └── utils/
    │           └── offchain/
    │               └── quotes/
    │                   ├── stethToWeth.json
    │                   ├── wethToWsteth.json
    │                   └── wstethToWeth.json
    └── utils/
        ├── PermissionedProxy.sol
        ├── Whitelist.sol
        └── ZeroXSwapVerifier.sol

================================================
FILE CONTENTS
================================================

================================================
FILE: src/AlTokenV3.sol
================================================
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.28;

import {CrossChainCanonicalBase} from "lib/v2-foundry/src/CrossChainCanonicalBase.sol";
import {AlchemicTokenV2Base} from "lib/v2-foundry/src/AlchemicTokenV2Base.sol";
import {IXERC20} from "lib/v2-foundry/src/interfaces/external/connext/IXERC20.sol";

contract CrossChainCanonicalAlchemicTokenV3 is CrossChainCanonicalBase, AlchemicTokenV2Base {

  /// @custom:oz-upgrades-unsafe-allow constructor
  constructor() initializer {}

  function initialize(
      string memory name,
      string memory symbol
  ) external initializer {
    __CrossChainCanonicalBase_init(
      name,
      symbol,
      msg.sender
    );
    __AlchemicTokenV2Base_init();
  }

  function burn(uint256 amount) external returns (bool) {
    // If bridge is registered check limits and update accordingly.
    if (xBridges[msg.sender].burnerParams.maxLimit > 0) {
      uint256 currentLimit = burningCurrentLimitOf(msg.sender);
      if (amount > currentLimit) revert IXERC20.IXERC20_NotHighEnoughLimits();
      _useBurnerLimits(msg.sender, amount);
    }

    _burn(msg.sender, amount);
    return true;
  }

  /// @dev Destroys `amount` tokens from `account`, deducting from the caller's allowance.
  ///
  /// @param account The address the burn tokens from.
  /// @param amount  The amount of tokens to burn.
  function burnFrom(address account, uint256 amount) external returns (bool) {
    if (msg.sender != account) {
      uint256 newAllowance = allowance(account, msg.sender) - amount;
      _approve(account, msg.sender, newAllowance);
    }

    // If bridge is registered check limits and update accordingly.
    if (xBridges[msg.sender].burnerParams.maxLimit > 0) {
      uint256 currentLimit = burningCurrentLimitOf(msg.sender);
      if (amount > currentLimit) revert IXERC20.IXERC20_NotHighEnoughLimits();
      _useBurnerLimits(msg.sender, amount);
    }

    _burn(account, amount);
    return true;
  }


}


================================================
FILE: src/AlchemistAllocator.sol
================================================
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

import {IVaultV2} from "lib/vault-v2/src/interfaces/IVaultV2.sol";
import {PermissionedProxy} from "./utils/PermissionedProxy.sol";
import {IAllocator} from "./interfaces/IAllocator.sol";
import {IMYTStrategy} from "./interfaces/IMYTStrategy.sol";
import {IStrategyClassifier} from "./interfaces/IStrategyClassifier.sol";

/**
 * @title AlchemistAllocator
 * @notice This contract is used to allocate and deallocate funds to and from MYT strategies
 * @notice The MYT is a Morpho V2 Vault, and each strategy is just a vault adapter which interfaces with a third party protocol
 */
contract AlchemistAllocator is PermissionedProxy, IAllocator {
    IVaultV2 immutable public vault;
    IStrategyClassifier immutable public strategyClassifier;

    constructor(address _vault, address _admin, address _operator, address _classifier) PermissionedProxy(_admin, _operator) {
        require(IVaultV2(_vault).asset() != address(0), "IV");
        require(_classifier != address(0), "IC");
        vault = IVaultV2(_vault);
        strategyClassifier = IStrategyClassifier(_classifier);
    }

    /**
    * @notice Allocate (uses ActionType.direct)
    * @param adapter The strategy adapter address
    * @param amount The amount to allocate
     */
    function allocate(address adapter, uint256 amount) external {
        require(msg.sender == admin || operators[msg.sender], "PD");
        _validateCaps(adapter, amount);
        IMYTStrategy.VaultAdapterParams memory params;
        params.action = IMYTStrategy.ActionType.direct;
        bytes memory data = abi.encode(params);
        vault.allocate(adapter, data, amount);

    }

    /**
    * @notice Deallocate (uses ActionType.direct)
    * @param adapter The strategy adapter address
    * @param amount The amount to deallocate
     */

    function deallocate(address adapter, uint256 amount) external {
        require(msg.sender == admin || operators[msg.sender], "PD");

        IMYTStrategy.VaultAdapterParams memory params;
        params.action = IMYTStrategy.ActionType.direct;
        bytes memory data = abi.encode(params);
        vault.deallocate(adapter, data, amount);
    }

    /**
    * @notice Allocate with swap (uses ActionType.swap)
    * @param adapter The strategy adapter address
    * @param amount The amount to allocate
    * @param txData The 0x swap calldata
     */
    function allocateWithSwap(address adapter, uint256 amount, bytes memory txData) external {
        require(msg.sender == admin || operators[msg.sender], "PD");
        _validateCaps(adapter, amount);
        IMYTStrategy.SwapParams memory swapParams = IMYTStrategy.SwapParams({
            txData: txData, 
            minIntermediateOut: 0
        });
        IMYTStrategy.VaultAdapterParams memory params = IMYTStrategy.VaultAdapterParams(
            {action: IMYTStrategy.ActionType.swap, swapParams: swapParams});

        bytes memory data = abi.encode(params);
        vault.allocate(adapter, data, amount);
    }


    /**
    * @notice Deallocate with dex swap (uses ActionType.swap)
    * @param adapter The strategy adapter address
    * @param amount The amount to deallocate
    * @param txData The 0x swap calldata
     */
    function deallocateWithSwap(address adapter, uint256 amount, bytes memory txData) external {
        require(msg.sender == admin || operators[msg.sender], "PD");
        IMYTStrategy.SwapParams memory swapParams = IMYTStrategy.SwapParams({
            txData: txData, 
            minIntermediateOut: 0
        });
        IMYTStrategy.VaultAdapterParams memory params = IMYTStrategy.VaultAdapterParams(
            {action: IMYTStrategy.ActionType.swap, swapParams: swapParams});

        bytes memory data = abi.encode(params);
        vault.deallocate(adapter, data, amount);
    }

    /**
    * @notice Deallocate with unwrap + dex swap (uses ActionType.unwrapAndSwap)
    * @param adapter The strategy adapter address
    * @param amount The amount to deallocate  
    * @param txData The 0x swap calldata
    * @param minIntermediateOut The intermediate asset to produce from unwrap (use quote's sellAmount)
    */
    function deallocateWithUnwrapAndSwap(address adapter, uint256 amount, bytes memory txData, uint256 minIntermediateOut) external {
        require(msg.sender == admin || operators[msg.sender], "PD");
        IMYTStrategy.SwapParams memory swapParams = IMYTStrategy.SwapParams({
            txData: txData, 
            minIntermediateOut: minIntermediateOut
        });
        IMYTStrategy.VaultAdapterParams memory params = IMYTStrategy.VaultAdapterParams(
            {action: IMYTStrategy.ActionType.unwrapAndSwap, swapParams: swapParams});

        bytes memory data = abi.encode(params);
        vault.deallocate(adapter, data, amount);
    }

    /// @notice Set the vault liquidity adapter and calldata used by deposit/withdraw flows.
    function setLiquidityAdapter(address adapter, bytes memory data) external {
        require(msg.sender == admin || operators[msg.sender], "PD");
        vault.setLiquidityAdapterAndData(adapter, data);
    }

    /**
    * @notice Validate the caps for the given adapter and amount
    * @param adapter The strategy adapter address
    * @param amount The amount to validate
     */
    function _validateCaps(address adapter, uint256 amount) internal view {
        bytes32 id = IMYTStrategy(adapter).adapterId();
        uint256 absoluteCap = vault.absoluteCap(id);
        uint256 relativeCap = vault.relativeCap(id);
        
        // get risk caps
        uint256 strategyId = uint256(id);
        uint8 riskLevel = strategyClassifier.getStrategyRiskLevel(strategyId);
        uint256 globalRiskCapPct = strategyClassifier.getGlobalCap(riskLevel);
        uint256 localRiskCapPct = strategyClassifier.getIndividualCap(strategyId);

        // Convert relativeCap (WAD) to absolute value (WEI)
        uint256 totalAssets = vault.totalAssets();
        uint256 absoluteValueOfRelativeCap = (totalAssets * relativeCap) / 1e18;

        // Convert risk caps from WAD percentages to absolute values
        uint256 globalRiskCap = (totalAssets * globalRiskCapPct) / 1e18;
        uint256 localRiskCap = (totalAssets * localRiskCapPct) / 1e18;

        // Calculate limit cap as the minimum of vault caps
        uint256 limit = absoluteCap < absoluteValueOfRelativeCap ? absoluteCap : absoluteValueOfRelativeCap;

        // Enforce global risk cap (aggregate across all strategies in this risk class)
        uint256 currentRiskAllocation = 0;
        uint256 len = vault.adaptersLength();
        for (uint256 i = 0; i < len; i++) {
            address stratAdapter = vault.adapters(i);
            bytes32 stratId = IMYTStrategy(stratAdapter).adapterId();
            
            // Check if the strategy belongs to the same risk level
            if (strategyClassifier.getStrategyRiskLevel(uint256(stratId)) == riskLevel) {
                currentRiskAllocation += vault.allocation(stratId);
            }
        }
        
        // Check if the proposed allocation exceeds the remaining capacity of the global risk cap
        uint256 remainingGlobal = currentRiskAllocation < globalRiskCap ? globalRiskCap - currentRiskAllocation : 0;
        require(amount <= remainingGlobal, EffectiveCap(amount, remainingGlobal));

        // Apply local risk cap constraint for operators
        if (msg.sender != admin) {
            // caller is operator, further constrain by local risk cap
            limit = limit < localRiskCap ? limit : localRiskCap;
        }

        // Ensure the requested amount does not exceed the calculated individual strategy limit
        require(vault.allocation(id) + amount <= limit, EffectiveCap(amount, limit));
    }

    function setMaxRate(uint256 rate) external onlyAdmin {
        vault.setMaxRate(rate);
    }
}


================================================
FILE: src/AlchemistCurator.sol
================================================
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

import {IVaultV2} from "../lib/vault-v2/src/interfaces/IVaultV2.sol";
import {PermissionedProxy} from "./utils/PermissionedProxy.sol";
import {IAlchemistCurator} from "./interfaces/IAlchemistCurator.sol";

/**
 * @title AlchemistCurator
 * @notice This contract is used to update MYT caps and add/remove strategies to the MYT
 * @notice The MYT is a Morpho V2 Vault, and each strategy is just a vault adapter which interfaces with a third party protocol
 */

interface IMYTStrategyMinimal {
    function getIdData() external returns (bytes memory);
}
contract AlchemistCurator is IAlchemistCurator, PermissionedProxy {
    // map of myt adapter(strategy) address to myt address
    mapping(address => address) public adapterToMYT;

    constructor(address _admin, address _operator) PermissionedProxy(_admin, _operator) {
        
    }

    function submitSetStrategy(address adapter, address myt) external onlyOperator {
        require(adapter != address(0), "INVALID_ADDRESS");
        require(myt != address(0), "INVALID_ADDRESS");
        _submitSetStrategy(adapter, myt);
    }

    function setStrategy(address adapter, address myt) external onlyOperator {
        require(adapter != address(0), "INVALID_ADDRESS");
        require(myt != address(0), "INVALID_ADDRESS");
        _addStrategy(adapter, myt);
    }
   function submitRemoveStrategy(address adapter, address myt) external onlyOperator {
        require(adapter != address(0), "INVALID_ADDRESS");
        require(myt != address(0), "INVALID_ADDRESS");
        _submitRemoveStrategy(adapter, myt);
    }
   
    function removeStrategy(address adapter, address myt) external onlyOperator {
        require(adapter != address(0), "INVALID_ADDRESS");
        require(myt != address(0), "INVALID_ADDRESS");
        _removeStrategy(adapter, myt); // remove
    }

    function _submitSetStrategy(address adapter, address myt) internal {
        IVaultV2 vault = IVaultV2(myt);
        bytes memory data = abi.encodeCall(IVaultV2.addAdapter, adapter);
        vault.submit(data);
        emit SubmitSetStrategy(adapter, myt);
    }

    function _submitRemoveStrategy(address adapter, address myt) internal {
        IVaultV2 vault = IVaultV2(myt);
        bytes memory data = abi.encodeCall(IVaultV2.removeAdapter, adapter);
        vault.submit(data);
        emit SubmitRemoveStrategy(adapter, myt);
    }

    function _addStrategy(address adapter, address myt) internal {
        adapterToMYT[adapter] = myt;
        IVaultV2 vault = _vault(adapter);
        vault.addAdapter(adapter);
        emit StrategyAdded(adapter, myt);
    }

    function _removeStrategy(address adapter, address myt) internal {
        IVaultV2 vault = _vault(adapter);
        vault.removeAdapter(adapter);
        delete adapterToMYT[adapter];
        emit StrategyRemoved(adapter, myt);
    }

    function decreaseAbsoluteCap(address adapter, uint256 amount) external onlyAdmin {
        bytes memory id = IMYTStrategyMinimal(adapter).getIdData();
        _decreaseAbsoluteCap(adapter, id, amount);
    }

    function decreaseRelativeCap(address adapter, uint256 amount) external onlyAdmin {
        bytes memory id = IMYTStrategyMinimal(adapter).getIdData();
        _decreaseRelativeCap(adapter, id, amount);
    }


    function _decreaseRelativeCap(address adapter, bytes memory id, uint256 amount) internal {
        IVaultV2 vault = _vault(adapter);
        vault.decreaseRelativeCap(id, amount);
        emit DecreaseRelativeCap(adapter, amount, id);
    }

    function _decreaseAbsoluteCap(address adapter, bytes memory id, uint256 amount) internal {
        IVaultV2 vault = _vault(adapter);
        vault.decreaseAbsoluteCap(id, amount);
        emit DecreaseAbsoluteCap(adapter, amount, id);
    }

    function increaseAbsoluteCap(address adapter, uint256 amount) external onlyAdmin {
        bytes memory id = IMYTStrategyMinimal(adapter).getIdData();
        _increaseAbsoluteCap(adapter, id, amount);
    }

    function increaseRelativeCap(address adapter, uint256 amount) external onlyAdmin {
        bytes memory id = IMYTStrategyMinimal(adapter).getIdData();
        _increaseRelativeCap(adapter, id, amount);
    }

    function submitIncreaseAbsoluteCap(address adapter, uint256 amount) external onlyAdmin {
        bytes memory id = IMYTStrategyMinimal(adapter).getIdData();
        _submitIncreaseAbsoluteCap(adapter, id, amount);
    }

    function submitIncreaseRelativeCap(address adapter, uint256 amount) external onlyAdmin {
        bytes memory id = IMYTStrategyMinimal(adapter).getIdData();
        _submitIncreaseRelativeCap(adapter, id, amount);
    }

    function _increaseAbsoluteCap(address adapter, bytes memory id, uint256 amount) internal {
        IVaultV2 vault = _vault(adapter);
        vault.increaseAbsoluteCap(id, amount);
        emit IncreaseAbsoluteCap(adapter, amount, id);
    }

    function _increaseRelativeCap(address adapter, bytes memory id, uint256 amount) internal {
        IVaultV2 vault = _vault(adapter);
        vault.increaseRelativeCap(id, amount);
        emit IncreaseRelativeCap(adapter, amount, id);
    }

    function _submitIncreaseAbsoluteCap(address adapter, bytes memory id, uint256 amount) internal {
        bytes memory data = abi.encodeCall(IVaultV2.increaseAbsoluteCap, (id, amount));
        _vaultSubmit(adapter, data);
        emit SubmitIncreaseAbsoluteCap(adapter, amount, id);
    }

    function _submitIncreaseRelativeCap(address adapter, bytes memory id, uint256 amount) internal {
        bytes memory data = abi.encodeCall(IVaultV2.increaseRelativeCap, (id, amount));
        _vaultSubmit(adapter, data);
        emit SubmitIncreaseRelativeCap(adapter, amount, id);
    }

    function submitSetAllocator(address myt, address allocator, bool v) external onlyAdmin {
        bytes memory data = abi.encodeCall(IVaultV2.setIsAllocator, (allocator, v));
        IVaultV2(myt).submit(data);
        emit SubmitSetAllocator(allocator, v);
    }

    function submitSetForceDeallocatePenalty(address adapter, address myt, uint256 penalty) external onlyAdmin {
        bytes memory data = abi.encodeCall(IVaultV2.setForceDeallocatePenalty, (adapter, penalty));
        IVaultV2(myt).submit(data);
        emit SubmitSetForceDeallocatePenalty(adapter, myt, penalty);
    }

    function submitSetPerformanceFeeRecipient(address myt, address recipient) external onlyAdmin {
        bytes memory data = abi.encodeCall(IVaultV2.setPerformanceFeeRecipient, (recipient));
        IVaultV2(myt).submit(data);
        emit SubmitSetPerformanceFeeRecipient(myt, recipient);
    }

    function submitSetPerformanceFee(address myt, uint256 fee) external onlyAdmin {
        bytes memory data = abi.encodeCall(IVaultV2.setPerformanceFee, (fee));
        IVaultV2(myt).submit(data);
        emit SubmitSetPerformanceFee(myt, fee);
    }

    function _vaultSubmit(address adapter, bytes memory data) internal {
        IVaultV2 vault = _vault(adapter);
        vault.submit(data);
    }

    function _vault(address adapter) internal view returns (IVaultV2) {
        require(adapterToMYT[adapter] != address(0), "INVALID_ADDRESS");
        return IVaultV2(adapterToMYT[adapter]);
    }
}


================================================
FILE: src/AlchemistETHVault.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {AbstractFeeVault} from "./adapters/AbstractFeeVault.sol";
/**
 * @title AlchemistETHVault
 * @notice A simple vault for ETH/WETH deposits that only allows withdrawals by authorized parties
 * @dev Supports both native ETH and WETH deposits
 */

contract AlchemistETHVault is AbstractFeeVault, ReentrancyGuard {
    using SafeERC20 for IERC20;
    // Error for failed transfers

    error TransferFailed();

    /**
     * @param _weth Address of the WETH contract
     * @param _alchemist Address of the AlchemistV3 contract
     * @param _owner Address of the owner
     */
    constructor(address _weth, address _alchemist, address _owner) AbstractFeeVault(_weth, _alchemist, _owner) {}

    /**
     * @notice Get the total deposits in the vault
     * @return Total deposits
     */
    function totalDeposits() public view override returns (uint256) {
        return address(this).balance;
    }

    /**
     * @notice Deposit ETH into the vault
     */
    function deposit() external payable nonReentrant {
        if (msg.value == 0) revert ZeroAmount();
        _deposit(msg.sender, msg.value);
    }

    /**
     * @notice Receive ETH into the vault
     */
    receive() external payable {}

    /**
     * @notice Deposit WETH into the vault (automatically unwraps to ETH)
     * @param amount Amount of WETH to deposit
     */
    function depositWETH(uint256 amount) external nonReentrant {
        if (amount == 0) revert ZeroAmount();

        // Transfer WETH from sender to this contract
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

        // Unwrap WETH to ETH
        IWETH(token).withdraw(amount);

        // Record the deposit
        _deposit(msg.sender, amount);
    }

    /**
     * @notice Internal deposit logic
     * @param depositor Address of the depositor
     * @param amount Amount deposited
     */
    function _deposit(address depositor, uint256 amount) internal {
        emit Deposited(depositor, amount);
    }

    /**
     * @notice Withdraw funds from the vault to a target address (always sends ETH)
     * @param recipient Address to receive the funds
     * @param amount Amount to withdraw
     */
    function withdraw(address recipient, uint256 amount) external override onlyAuthorized nonReentrant {
        _checkNonZeroAddress(recipient);
        if (amount == 0) revert ZeroAmount();

        // Check if the vault has enough balance
        if (amount > address(this).balance) revert InsufficientBalance();

        // Send as native ETH
        (bool success,) = recipient.call{value: amount}("");
        if (!success) revert TransferFailed();

        emit Withdrawn(recipient, amount);
    }
}

================================================
FILE: src/AlchemistGate.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import "@openzeppelin/contracts/access/Ownable.sol";
contract AlchemistGate is Ownable {
    mapping(address => mapping( address => bool)) public authorized;

    constructor(address _owner) Ownable(_owner) {}

    function setAuthorization(address _vault, address _to, bool value) external onlyOwner {
        authorized[_vault][_to] = value;
    }
}


================================================
FILE: src/AlchemistStrategyClassifier.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {IStrategyClassifier} from "./interfaces/IStrategyClassifier.sol";

/**
 * @title AlchemistStrategyClassifier
 * @notice This contract is used to classify strategies based on their risk level and set the respective caps
 * @notice The MYT is a Morpho V2 Vault, and each strategy is just a vault adapter which interfaces with a third party protocol
 */
contract AlchemistStrategyClassifier is IStrategyClassifier {
    address public admin;
    address public pendingAdmin;

    /**
     * @notice globalCap is the maximum combined allocation for ALL strategies of this risk type, in WAD (1e18 = 100%).
     * @notice localCap is the maximum allocation for a SINGLE strategy in this risk class, in WAD (1e18 = 100%).
     * @dev Example: MEDIUM with globalCap=0.4e18 and localCap=0.25e18 means each MEDIUM strategy is capped at 25%
     *      of totalAssets individually, and all MEDIUM strategies together cannot exceed 40% of totalAssets.
     */
    struct RiskClass {
        uint256 globalCap; // Max combined allocation for all strategies in this class (WAD)
        uint256 localCap; // Max allocation for a single strategy in this class (WAD)
    }

    /// riskLevel => RiskClass data
    mapping(uint8 => RiskClass) public riskClasses;

    /// strategyId => riskLevel
    mapping(uint256 => uint8) public strategyRiskLevel;

    // ===== Constructor =====
    constructor(address _admin) {
        require(_admin != address(0), "IA");
        admin = _admin;

        // Initialize defaults (can be updated by admin later)
        riskClasses[0] = RiskClass(1e18, 1e18); // Low risk
        riskClasses[1] = RiskClass(4e17, 25e16); // Medium risk
        riskClasses[2] = RiskClass(1e17, 1e17); // High risk

        pendingAdmin = address(0);
    }

    // ===== Admin Management =====

    function transferOwnership(address _newAdmin) external {
        require(msg.sender == admin, "PD");
        pendingAdmin = _newAdmin;
    }

    function acceptOwnership() external {
        require(msg.sender == pendingAdmin, "PD");
        admin = pendingAdmin;
        pendingAdmin = address(0);
        emit AdminChanged(admin);
    }

    // ===== Risk Class Management =====

    function setRiskClass(uint8 classId, uint256 globalCap, uint256 localCap) external {
        require(msg.sender == admin, "PD");
        riskClasses[classId] = RiskClass(globalCap, localCap);
        emit RiskClassModified(classId, globalCap, localCap);
    }

    function assignStrategyRiskLevel(uint256 strategyId, uint8 riskLevel) external {
        require(msg.sender == admin, "PD");
        strategyRiskLevel[strategyId] = riskLevel;
    }

    // ===== IStrategyClassifier Interface Implementation =====

    /// @notice Returns the maximum allowed allocation for a single strategy (WAD percentage)
    function getIndividualCap(uint256 strategyId) external view override returns (uint256) {
        uint8 riskLevel = strategyRiskLevel[strategyId];
        return riskClasses[riskLevel].localCap;
    }

    /// @notice Returns the maximum allowed combined allocation for all strategies in a risk class (WAD percentage)
    function getGlobalCap(uint8 riskLevel) external view override returns (uint256) {
        return riskClasses[riskLevel].globalCap;
    }

    /// @notice Returns the risk level of a given strategy
    function getStrategyRiskLevel(uint256 strategyId) external view override returns (uint8) {
        return strategyRiskLevel[strategyId];
    }
}


================================================
FILE: src/AlchemistTokenVault.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./base/Errors.sol";
import "./adapters/AbstractFeeVault.sol";

/**
 * @title AlchemistTokenVault
 * @notice A vault that holds a specific ERC20 token, allowing anyone to deposit
 * but only authorized parties to withdraw.
 */
contract AlchemistTokenVault is AbstractFeeVault {
    using SafeERC20 for IERC20;
    /**
     * @notice Constructor initializes the token vault
     * @param _token The ERC20 token managed by this vault
     * @param _alchemist The Alchemist contract address that will be authorized
     * @param _owner The owner of the vault
     */
    constructor(address _token, address _alchemist, address _owner) AbstractFeeVault(_token, _alchemist, _owner) {}

    /**
     * @notice Allows anyone to deposit tokens into the vault
     * @param amount The amount of tokens to deposit
     */
    function deposit(uint256 amount) external {
        _checkNonZeroAmount(amount);
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        emit Deposited(msg.sender, amount);
    }

    /**
     * @notice Allows only authorized accounts to withdraw tokens
     * @param recipient The address to receive the tokens
     * @param amount The amount of tokens to withdraw
     */
    function withdraw(address recipient, uint256 amount) external override onlyAuthorized {
        _checkNonZeroAddress(recipient);
        _checkNonZeroAmount(amount);
        IERC20(token).safeTransfer(recipient, amount);
        emit Withdrawn(recipient, amount);
    }

    /**
     * @notice Get the total deposits in the vault
     * @return Total deposits
     */
    function totalDeposits() public view override returns (uint256) {
        return IERC20(token).balanceOf(address(this));
    }
}


================================================
FILE: src/AlchemistV3.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import "./interfaces/IAlchemistV3.sol";
import {ITransmuter} from "./interfaces/ITransmuter.sol";
import {IAlchemistV3Position} from "./interfaces/IAlchemistV3Position.sol";
import {IFeeVault} from "./interfaces/IFeeVault.sol";
import {TokenUtils} from "./libraries/TokenUtils.sol";
import {Initializable} from "../lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {Unauthorized, IllegalArgument, IllegalState, MissingInputData} from "./base/Errors.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IVaultV2} from "../lib/vault-v2/src/interfaces/IVaultV2.sol";
import {FixedPointMath} from "./libraries/FixedPointMath.sol";

/// @title  AlchemistV3
/// @author Alchemix Finance
///
/// For Juris, Graham, and Marcus
contract AlchemistV3 is IAlchemistV3, Initializable {
    uint256 public constant BPS = 10_000;
    uint256 public constant FIXED_POINT_SCALAR = 1e18;

    uint256 public constant ONE_Q128 = uint256(1) << 128;

    /// @inheritdoc IAlchemistV3Immutables
    string public constant version = "3.0.0";

    /// @inheritdoc IAlchemistV3State
    address public admin;

    /// @inheritdoc IAlchemistV3State
    address public alchemistFeeVault;

    /// @inheritdoc IAlchemistV3Immutables
    address public debtToken;

    /// @inheritdoc IAlchemistV3State
    address public myt;

    /// @inheritdoc IAlchemistV3State
    uint256 public underlyingConversionFactor;

    /// @inheritdoc IAlchemistV3State
    uint256 public cumulativeEarmarked;

    /// @inheritdoc IAlchemistV3State
    uint256 public depositCap;

    /// @inheritdoc IAlchemistV3State
    uint256 public lastEarmarkBlock;

    /// @inheritdoc IAlchemistV3State
    uint256 public lastRedemptionBlock;

    /// @inheritdoc IAlchemistV3State
    uint256 public lastTransmuterTokenBalance;

    /// @inheritdoc IAlchemistV3State
    uint256 public minimumCollateralization;

    /// @inheritdoc IAlchemistV3State
    uint256 public collateralizationLowerBound;

    /// @inheritdoc IAlchemistV3State
    uint256 public globalMinimumCollateralization;

    /// @inheritdoc IAlchemistV3State
    uint256 public liquidationTargetCollateralization;

    /// @inheritdoc IAlchemistV3State
    uint256 public totalDebt;

    /// @inheritdoc IAlchemistV3State
    uint256 public totalSyntheticsIssued;

    /// @inheritdoc IAlchemistV3State
    uint256 public protocolFee;

    /// @inheritdoc IAlchemistV3State
    uint256 public liquidatorFee;

    /// @inheritdoc IAlchemistV3State
    uint256 public repaymentFee;

    /// @inheritdoc IAlchemistV3State
    address public alchemistPositionNFT;

    /// @inheritdoc IAlchemistV3State
    address public protocolFeeReceiver;

    /// @inheritdoc IAlchemistV3State
    address public underlyingToken;

    /// @inheritdoc IAlchemistV3State
    address public tokenAdapter;

    /// @inheritdoc IAlchemistV3State
    address public transmuter;

    /// @inheritdoc IAlchemistV3State
    address public pendingAdmin;

    /// @inheritdoc IAlchemistV3State
    bool public depositsPaused;

    /// @inheritdoc IAlchemistV3State
    bool public loansPaused;

    /// @inheritdoc IAlchemistV3State
    mapping(address => bool) public guardians;

    /// @dev Total debt retired through Transmuter-driven redemptions.
    uint256 private _totalRedeemedDebt;

    /// @dev Total MYT shares removed from global accounting by redemptions and redemption fees.
    uint256 private _totalRedeemedSharesOut;

    /// @dev Packed earmark weight used to track how much live unearmarked debt survives each earmark step.
    uint256 private _earmarkWeight;

    /// @dev Packed redemption weight used to track survival of earmarked debt across redemptions.
    uint256 private _redemptionWeight;

    /// @dev Accumulator used to reconstruct earmarked debt survival across redemption windows.
    uint256 private _survivalAccumulator;

    /// @dev Total MYT shares currently assigned to open positions.
    /// This intentionally excludes any unrelated MYT balance the contract may temporarily hold.
    uint256 private _mytSharesDeposited;

    /// @dev Transmuter MYT balance increases not yet applied as cover in `_earmark()`.
    uint256 private _pendingCoverShares;

    /// @dev User accounts
    mapping(uint256 => Account) private _accounts;

    /// @dev Redemption weight snapshot at the start of each earmark epoch.
    mapping(uint256 => uint256) private _earmarkEpochStartRedemptionWeight;

    /// @dev Survival accumulator snapshot at the start of each earmark epoch.
    mapping(uint256 => uint256) private _earmarkEpochStartSurvivalAccumulator;
    
    uint256 private constant _REDEMPTION_INDEX_BITS = 129;
    uint256 private constant _REDEMPTION_INDEX_MASK = (uint256(1) << _REDEMPTION_INDEX_BITS) - 1;

    uint256 private constant _EARMARK_INDEX_BITS = 129;
    uint256 private constant _EARMARK_INDEX_MASK = (uint256(1) << _EARMARK_INDEX_BITS) - 1;

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

    modifier onlyAdminOrGuardian() {
        _onlyAdminOrGuardian();
        _;
    }

    modifier onlyTransmuter() {
        _onlyTransmuter();
        _;
    }

    constructor() initializer {}

    function initialize(AlchemistInitializationParams calldata params) external initializer {
        _checkArgument(params.protocolFee <= BPS);
        _checkArgument(params.liquidatorFee <= BPS);
        _checkArgument(params.repaymentFee <= BPS);
        _checkArgument(params.liquidationTargetCollateralization >= params.minimumCollateralization);
        
        debtToken = params.debtToken;
        underlyingToken = params.underlyingToken;
        underlyingConversionFactor = 10 ** (TokenUtils.expectDecimals(params.debtToken) - TokenUtils.expectDecimals(params.underlyingToken));
        depositCap = params.depositCap;
        minimumCollateralization = params.minimumCollateralization;
        globalMinimumCollateralization = params.globalMinimumCollateralization;
        collateralizationLowerBound = params.collateralizationLowerBound;
        liquidationTargetCollateralization = params.liquidationTargetCollateralization;
        admin = params.admin;
        transmuter = params.transmuter;
        protocolFee = params.protocolFee;
        protocolFeeReceiver = params.protocolFeeReceiver;
        liquidatorFee = params.liquidatorFee;
        repaymentFee = params.repaymentFee;
        lastEarmarkBlock = block.number;
        lastRedemptionBlock = block.number;
        myt = params.myt;

        // Initialize packed weights at full survival.
        _redemptionWeight = ONE_Q128;
        _earmarkWeight = ONE_Q128;

        // Initialize epoch checkpoints.
        _earmarkEpochStartRedemptionWeight[0] = _redemptionWeight;
        _earmarkEpochStartSurvivalAccumulator[0] = _survivalAccumulator;
    }

    /// @notice Sets the NFT position token, callable by admin.
    function setAlchemistPositionNFT(address nft) external onlyAdmin {
        if (nft == address(0)) {
            revert AlchemistV3NFTZeroAddressError();
        }

        if (alchemistPositionNFT != address(0)) {
            revert AlchemistV3NFTAlreadySetError();
        }

        alchemistPositionNFT = nft;
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setAlchemistFeeVault(address value) external onlyAdmin {
        if (IFeeVault(value).token() != underlyingToken) {
            revert AlchemistVaultTokenMismatchError();
        }
        alchemistFeeVault = value;
        emit AlchemistFeeVaultUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setPendingAdmin(address value) external onlyAdmin {
        pendingAdmin = value;

        emit PendingAdminUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function acceptAdmin() external {
        _checkState(pendingAdmin != address(0));

        if (msg.sender != pendingAdmin) {
            revert Unauthorized();
        }

        admin = pendingAdmin;
        pendingAdmin = address(0);

        emit AdminUpdated(admin);
        emit PendingAdminUpdated(address(0));
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setDepositCap(uint256 value) external onlyAdmin {
        _checkArgument(value >= IERC20(myt).balanceOf(address(this)));

        depositCap = value;
        emit DepositCapUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setProtocolFeeReceiver(address value) external onlyAdmin {
        _checkArgument(value != address(0));

        protocolFeeReceiver = value;
        emit ProtocolFeeReceiverUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setProtocolFee(uint256 fee) external onlyAdmin {
        _checkArgument(fee <= BPS);

        protocolFee = fee;
        emit ProtocolFeeUpdated(fee);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setLiquidatorFee(uint256 fee) external onlyAdmin {
        _checkArgument(fee <= BPS);

        liquidatorFee = fee;
        emit LiquidatorFeeUpdated(fee);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setRepaymentFee(uint256 fee) external onlyAdmin {
        _checkArgument(fee <= BPS);

        repaymentFee = fee;
        emit RepaymentFeeUpdated(fee);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setTokenAdapter(address value) external onlyAdmin {
        _checkArgument(value != address(0));

        tokenAdapter = value;
        emit TokenAdapterUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setGuardian(address guardian, bool isActive) external onlyAdmin {
        _checkArgument(guardian != address(0));

        guardians[guardian] = isActive;
        emit GuardianSet(guardian, isActive);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setMinimumCollateralization(uint256 value) external onlyAdmin {
        _checkArgument(value >= FIXED_POINT_SCALAR);

        // cannot exceed global minimum
        minimumCollateralization = value > globalMinimumCollateralization ? globalMinimumCollateralization : value;

        // cannot exceed liquidation target
        if (minimumCollateralization > liquidationTargetCollateralization) {
            minimumCollateralization = liquidationTargetCollateralization;
        }
        emit MinimumCollateralizationUpdated(minimumCollateralization);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setGlobalMinimumCollateralization(uint256 value) external onlyAdmin {
        _checkArgument(value >= minimumCollateralization);
        globalMinimumCollateralization = value;
        emit GlobalMinimumCollateralizationUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setCollateralizationLowerBound(uint256 value) external onlyAdmin {
        _checkArgument(value < minimumCollateralization);
        _checkArgument(value >= FIXED_POINT_SCALAR);
        collateralizationLowerBound = value;
        emit CollateralizationLowerBoundUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function setLiquidationTargetCollateralization(uint256 value) external onlyAdmin {
        _checkArgument(value > FIXED_POINT_SCALAR);
        _checkArgument(value >= minimumCollateralization);
        _checkArgument(value > collateralizationLowerBound);
        _checkArgument(value <= 2 * FIXED_POINT_SCALAR);
        liquidationTargetCollateralization = value;
        emit LiquidationTargetCollateralizationUpdated(value);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function pauseDeposits(bool isPaused) external onlyAdminOrGuardian {
        depositsPaused = isPaused;
        emit DepositsPaused(isPaused);
    }

    /// @inheritdoc IAlchemistV3AdminActions
    function pauseLoans(bool isPaused) external onlyAdminOrGuardian {
        loansPaused = isPaused;
        emit LoansPaused(isPaused);
    }

    /// @inheritdoc IAlchemistV3State
    function getCDP(uint256 tokenId) external view returns (uint256, uint256, uint256) {
        (uint256 debt, uint256 earmarked, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
        return (collateral, debt, earmarked);
    }

    /// @inheritdoc IAlchemistV3State
    function getTotalDeposited() external view returns (uint256) {
        return _mytSharesDeposited;
    }

    /// @inheritdoc IAlchemistV3State
    function getMaxBorrowable(uint256 tokenId) external view returns (uint256) {
        (uint256 debt,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
        uint256 debtValueOfCollateral = convertYieldTokensToDebt(collateral);
        uint256 capacity = (debtValueOfCollateral * FIXED_POINT_SCALAR / minimumCollateralization);
        return debt > capacity  ? 0 : capacity - debt;
    }

    /// @inheritdoc IAlchemistV3State
    function getMaxWithdrawable(uint256 tokenId) external view returns (uint256) {
        (uint256 debt,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);

        uint256 lockedCollateral = 0;
        if (debt != 0) {
            uint256 debtShares = convertDebtTokensToYield(debt);
            lockedCollateral = FixedPointMath.mulDivUp(debtShares, minimumCollateralization, FIXED_POINT_SCALAR);
        }

        uint256 positionFree = collateral > lockedCollateral ? collateral - lockedCollateral : 0;
        uint256 required = _requiredLockedShares();
        uint256 globalFree = _mytSharesDeposited > required ? _mytSharesDeposited - required : 0;

        return positionFree < globalFree ? positionFree : globalFree;
    }

    /// @inheritdoc IAlchemistV3State
    function mintAllowance(uint256 ownerTokenId, address spender) external view returns (uint256) {
        Account storage account = _accounts[ownerTokenId];
        return account.mintAllowances[account.allowancesVersion][spender];
    }

    /// @inheritdoc IAlchemistV3State
    function getTotalUnderlyingValue() external view returns (uint256) {
        return _getTotalUnderlyingValue();
    }

    
    /// @inheritdoc IAlchemistV3State
    function getTotalLockedUnderlyingValue() external view returns (uint256) {
        return _getTotalLockedUnderlyingValue();
    }

    /// @inheritdoc IAlchemistV3State
    function totalValue(uint256 tokenId) public view returns (uint256) {
        return _totalCollateralValue(tokenId, true);
    }

    /// @notice Returns cumulative earmarked debt including one simulated pending earmark window.
    function getUnrealizedCumulativeEarmarked() external view returns (uint256) {
        if (totalDebt == 0) return 0;
        (, uint256 effectiveEarmarked) = _simulateUnrealizedEarmark();
        return cumulativeEarmarked + effectiveEarmarked;
    }

    /// @inheritdoc IAlchemistV3Actions
    function deposit(uint256 amount, address recipient, uint256 tokenId) external returns (uint256, uint256) {
        _checkArgument(recipient != address(0));
        _checkArgument(amount > 0);
        _checkState(!depositsPaused);
        _checkState(!_isProtocolInBadDebt());
        _checkState(_mytSharesDeposited + amount <= depositCap);

        // Only mint a new position if the id is 0
        if (tokenId == 0) {
            tokenId = IAlchemistV3Position(alchemistPositionNFT).mint(recipient);
            emit AlchemistV3PositionNFTMinted(recipient, tokenId);
        } else {
            _checkForValidAccountId(tokenId);
            _earmark();
            _sync(tokenId);
        }

        _accounts[tokenId].collateralBalance += amount;

        // Transfer tokens from msg.sender now that the internal storage updates have been committed.
        TokenUtils.safeTransferFrom(myt, msg.sender, address(this), amount);
        _mytSharesDeposited += amount;

        emit Deposit(amount, tokenId);

        return (tokenId, convertYieldTokensToDebt(amount));
    }

    /// @inheritdoc IAlchemistV3Actions
    function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) {
        _checkArgument(recipient != address(0));
        _checkForValidAccountId(tokenId);
        _checkArgument(amount > 0);
        _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
        _earmark();
        _sync(tokenId);

        if (_accounts[tokenId].collateralBalance > _mytSharesDeposited) {
            _accounts[tokenId].collateralBalance = _mytSharesDeposited;
        }

        uint256 debtShares = convertDebtTokensToYield(_accounts[tokenId].debt);
        uint256 lockedCollateral = FixedPointMath.mulDivUp(debtShares, minimumCollateralization, FIXED_POINT_SCALAR);
        _checkArgument(_accounts[tokenId].collateralBalance - lockedCollateral >= amount);
        uint256 transferred = _subCollateralBalance(amount, tokenId);

        // Assure that the collateralization invariant is still held.
        _validate(tokenId);

        // Transfer the yield tokens to msg.sender
        TokenUtils.safeTransfer(myt, recipient, transferred);

        emit Withdraw(transferred, tokenId, recipient);

        return transferred;
    }

    /// @inheritdoc IAlchemistV3Actions
    function mint(uint256 tokenId, uint256 amount, address recipient) external {
        _checkArgument(recipient != address(0));
        _checkForValidAccountId(tokenId);
        _checkArgument(amount > 0);
        _checkState(!loansPaused);
        _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);

        // Query transmuter and earmark global debt
        _earmark();
        _checkState(!_isProtocolInBadDebt());
        // Sync current user debt before more is taken
        _sync(tokenId);

        // Mint tokens to recipient
        _mint(tokenId, amount, recipient);
    }

    /// @inheritdoc IAlchemistV3Actions
    function mintFrom(uint256 tokenId, uint256 amount, address recipient) external {
        _checkArgument(amount > 0);
        _checkForValidAccountId(tokenId);
        _checkArgument(recipient != address(0));
        _checkState(!loansPaused);
        // Preemptively try and decrease the minting allowance. This will save gas when the allowance is not sufficient.
        _decreaseMintAllowance(tokenId, msg.sender, amount);

        // Query transmuter and earmark global debt
        _earmark();
        _checkState(!_isProtocolInBadDebt());
        // Sync current user debt before more is taken
        _sync(tokenId);

        // Mint tokens from the tokenId's account to the recipient.
        _mint(tokenId, amount, recipient);
    }

    /// @inheritdoc IAlchemistV3Actions
    function burn(uint256 amount, uint256 recipientId) external returns (uint256) {
        _checkArgument(amount > 0);
        _checkForValidAccountId(recipientId);
        // Block same-block mint -> burn round trips.
        if (block.number == _accounts[recipientId].lastMintBlock) revert CannotRepayOnMintBlock();

        // Query transmuter and earmark global debt
        _earmark();

        // Sync current user debt before more is taken
        _sync(recipientId);

        uint256 debt;
        // Debt-token burns can only repay the unearmarked portion.
        _checkState((debt = _accounts[recipientId].debt - _accounts[recipientId].earmarked) > 0);

        uint256 credit = _capDebtCredit(amount, debt);
        if (credit == 0) return 0;

        // Must only burn enough tokens that the transmuter positions can still be fulfilled
        if (credit > totalSyntheticsIssued - ITransmuter(transmuter).totalLocked()) {
            revert BurnLimitExceeded(credit, totalSyntheticsIssued - ITransmuter(transmuter).totalLocked());
        }

        // Burn the debt tokens from the caller.
        TokenUtils.safeBurnFrom(debtToken, msg.sender, credit);

        // Apply the repayment to the target account.
        _subDebt(recipientId, credit);
        _accounts[recipientId].lastRepayBlock = block.number;

        totalSyntheticsIssued -= credit;

        // Assure that the collateralization invariant is still held.
        _validate(recipientId);

        emit Burn(msg.sender, credit, recipientId);

        return credit;
    }

    /// @inheritdoc IAlchemistV3Actions
    function repay(uint256 amount, uint256 recipientTokenId) external returns (uint256) {
        _checkArgument(amount > 0);
        _checkForValidAccountId(recipientTokenId);
        Account storage account = _accounts[recipientTokenId];
        // Block same-block mint -> repay round trips.
        if (block.number == account.lastMintBlock) revert CannotRepayOnMintBlock();

        // Query transmuter and earmark global debt
        _earmark();

        // Sync current user debt before deciding how much is available to be repaid
        _sync(recipientTokenId);

        uint256 debt;

        // MYT repayments can extinguish both earmarked and unearmarked debt.
        _checkState((debt = account.debt) > 0);

        uint256 yieldToDebt = convertYieldTokensToDebt(amount);
        uint256 credit = _capDebtCredit(yieldToDebt, debt);
        if (credit == 0) return 0;

        // Repay earmarked debt first so protocol fees are charged on the earmarked portion only.
        uint256 earmarkedRepaid = _subEarmarkedDebt(credit, recipientTokenId);


        uint256 creditToYield = convertDebtTokensToYield(credit);
        uint256 earmarkedRepaidToYield = convertDebtTokensToYield(earmarkedRepaid);


        // Protocol fee only applies to the earmarked portion of the repayment.
        uint256 feeAmount = earmarkedRepaidToYield * protocolFee / BPS;
        if (feeAmount > account.collateralBalance) {
            revert IllegalState();
        } else {
            _subCollateralBalance(feeAmount, recipientTokenId);
        }

        _subDebt(recipientTokenId, credit);
        account.lastRepayBlock = block.number;

        // Forward the repaid MYT to the transmuter.
        TokenUtils.safeTransferFrom(myt, msg.sender, transmuter, creditToYield);
        _syncEarmarkedTransmuterTransfer(creditToYield, earmarkedRepaidToYield);
        if (feeAmount > 0) {
            TokenUtils.safeTransfer(myt, protocolFeeReceiver, feeAmount);
        }
        emit Repay(msg.sender, amount, recipientTokenId, creditToYield);

        return creditToYield;
    }

    /// @inheritdoc IAlchemistV3Actions
    function liquidate(uint256 accountId) external override returns (uint256 yieldAmount, uint256 feeInYield, uint256 feeInUnderlying) {
        _checkForValidAccountId(accountId);
        uint256 debtBefore = _accounts[accountId].debt;
        (yieldAmount, feeInYield, feeInUnderlying) = _liquidate(accountId);
        if (yieldAmount > 0 || feeInYield > 0 || feeInUnderlying > 0 || _accounts[accountId].debt < debtBefore) {
            return (yieldAmount, feeInYield, feeInUnderlying);
        } else {
            // No collateral or fee movement occurred and debt did not decrease.
            revert LiquidationError();
        }
    }

    /// @inheritdoc IAlchemistV3Actions
    function batchLiquidate(uint256[] calldata accountIds)
        external
        returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying)
    {
        if (accountIds.length == 0) {
            revert MissingInputData();
        }

        bool anyProgress = false;
        for (uint256 i = 0; i < accountIds.length; i++) {
            uint256 accountId = accountIds[i];
            if (accountId == 0 || !_tokenExists(alchemistPositionNFT, accountId)) {
                continue;
            }
            uint256 debtBefore = _accounts[accountId].debt;
            (uint256 underlyingAmount, uint256 feeInYield, uint256 feeInUnderlying) = _liquidate(accountId);
            totalAmountLiquidated += underlyingAmount;
            totalFeesInYield += feeInYield;
            totalFeesInUnderlying += feeInUnderlying;
            if (
                underlyingAmount > 0 || feeInYield > 0 || feeInUnderlying > 0 || _accounts[accountId].debt < debtBefore
            ) {
                anyProgress = true;
            }
        }

        if (anyProgress) {
            return (totalAmountLiquidated, totalFeesInYield, totalFeesInUnderlying);
        } else {
            // None of the requested accounts made progress.
            revert LiquidationError();
        }
    }

    /// @inheritdoc IAlchemistV3Actions
    function redeem(uint256 amount) external onlyTransmuter returns (uint256 sharesSent) {
        _earmark();

        uint256 liveEarmarked = cumulativeEarmarked;
        if (amount > liveEarmarked) amount = liveEarmarked;

        uint256 effectiveRedeemed = 0;

        if (liveEarmarked != 0 && amount != 0) {
            // ratioWanted = (liveEarmarked - amount) / liveEarmarked in Q128.128
            uint256 ratioWanted = (amount == liveEarmarked) ? 0 : FixedPointMath.divQ128(liveEarmarked - amount, liveEarmarked);

            // Snapshot old packed
            uint256 packedOld = _redemptionWeight;
            uint256 oldEpoch  = _redEpoch(packedOld);
            uint256 oldIndex  = _redIndex(packedOld);

            // Normalize uninitialized / zero index
            if (packedOld == 0) {
                oldEpoch = 0;
                oldIndex = ONE_Q128;
            }
            if (oldIndex == 0) {
                oldEpoch += 1;
                oldIndex = ONE_Q128;
            }

            // Compute new packed
            uint256 newEpoch = oldEpoch;
            uint256 newIndex;

            if (ratioWanted == 0) {
                newEpoch += 1;
                newIndex = ONE_Q128;
            } else {
                newIndex = FixedPointMath.mulQ128(oldIndex, ratioWanted);
            }

            _redemptionWeight = _packRed(newEpoch, newIndex);

            // ratioApplied is what accounts will actually see via _redemptionSurvivalRatio()
            // epoch advance => full wipe => 0 survival
            uint256 ratioApplied = (newEpoch > oldEpoch) ? 0 : FixedPointMath.divQ128(newIndex, oldIndex);

            // Apply survival using the APPLIED ratio
            _survivalAccumulator = FixedPointMath.mulQ128(_survivalAccumulator, ratioApplied);

            // Derive effective redeemed amount using the SAME applied ratio
            uint256 remainingEarmarked = FixedPointMath.mulQ128(liveEarmarked, ratioApplied);
            effectiveRedeemed = liveEarmarked - remainingEarmarked;

            cumulativeEarmarked = remainingEarmarked;
            totalDebt -= effectiveRedeemed;
        }

        lastRedemptionBlock = block.number;

        // Use the effective redeemed amount everywhere downstream
        uint256 collRedeemed  = convertDebtTokensToYield(effectiveRedeemed);
        uint256 feeCollateral = collRedeemed * protocolFee / BPS;

        _totalRedeemedDebt += effectiveRedeemed;
        _totalRedeemedSharesOut += collRedeemed;

        TokenUtils.safeTransfer(myt, transmuter, collRedeemed);
        _mytSharesDeposited -= collRedeemed;

        // If the remaining tracked MYT cannot fully cover the protocol fee, skip the fee entirely.
        if (feeCollateral <= _mytSharesDeposited) {
            TokenUtils.safeTransfer(myt, protocolFeeReceiver, feeCollateral);
            _mytSharesDeposited -= feeCollateral;
            _totalRedeemedSharesOut += feeCollateral;
        }

        emit Redemption(effectiveRedeemed);
        return collRedeemed;
    }

    /// @inheritdoc IAlchemistV3Actions
    function selfLiquidate(uint256 accountId, address recipient) external returns (uint256 amountLiquidated) {
        _checkArgument(recipient != address(0));
        _checkForValidAccountId(accountId);
        _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(accountId), msg.sender);
        _poke(accountId);
        _checkState(_accounts[accountId].debt > 0);
        if (!_isAccountHealthy(accountId, false)) {
            // Unhealthy accounts must go through the regular liquidation path.
           revert AccountNotHealthy();
        }
        Account storage account = _accounts[accountId];

        // First clear any earmarked debt from the account's own collateral.
        uint256 repaidEarmarkedDebtInYield = _forceRepay(accountId, account.earmarked, true);
    
        uint256 debt = account.debt;

        // Then clear whatever debt remains.
        _subDebt(accountId, debt);

        // Remove the collateral that backed the remaining debt.
        uint256 repaidDebtInYield = _subCollateralBalance(convertDebtTokensToYield(debt), accountId);

        // Sweep any residual collateral out of the account.
        uint256 remainingCollateral = _subCollateralBalance(account.collateralBalance, accountId);

        if(repaidDebtInYield > 0) {
            // Forward repaid collateral to the transmuter.
            TokenUtils.safeTransfer(myt, transmuter, repaidDebtInYield);
        }

        if(remainingCollateral > 0) {
            // Return leftover collateral to the recipient.
            TokenUtils.safeTransfer(myt, recipient, remainingCollateral);
        }   
        emit SelfLiquidated(accountId, repaidEarmarkedDebtInYield + repaidDebtInYield);
        return repaidEarmarkedDebtInYield + repaidDebtInYield;
    }

    /// @inheritdoc IAlchemistV3State
    function calculateLiquidation(
        uint256 collateral,
        uint256 debt,
        uint256 targetCollateralization,
        uint256 alchemistCurrentCollateralization,
        uint256 alchemistMinimumCollateralization,
        uint256 feeBps
    ) public pure returns (uint256 grossCollateralToSeize, uint256 debtToBurn, uint256 fee, uint256 outsourcedFee) {
        if (debt >= collateral) {
            outsourcedFee = (debt * feeBps) / BPS;
            // fully liquidate debt if debt is greater than collateral
            return (collateral, debt, 0, outsourcedFee);
        }

        if (alchemistCurrentCollateralization < alchemistMinimumCollateralization) {
            outsourcedFee = (debt * feeBps) / BPS;
            // fully liquidate debt in high ltv global environment
            return (debt, debt, 0, outsourcedFee);
        }

        // fee is taken from surplus = collateral - debt
        uint256 surplus = collateral - debt;

        fee = (surplus * feeBps) / BPS;

        // collateral remaining for margin‐restore calc
        uint256 adjCollat = collateral - fee;
        // compute m*d  (both plain units)
        uint256 md = (targetCollateralization * debt) / FIXED_POINT_SCALAR;
        // if md <= adjCollat, nothing to liquidate
        if (md <= adjCollat) {
            return (0, 0, 0, 0);
        }

        // numerator = md - adjCollat
        uint256 num = md - adjCollat;

        // denom = m - 1  =>  (targetCollateralization - FIXED_POINT_SCALAR)/FIXED_POINT_SCALAR
        uint256 denom = targetCollateralization - FIXED_POINT_SCALAR;

        debtToBurn = (num * FIXED_POINT_SCALAR) / denom;

        // gross collateral seize = net + fee
        grossCollateralToSeize = debtToBurn + fee;
    }

    ///@inheritdoc IAlchemistV3Actions
    function reduceSyntheticsIssued(uint256 amount) external onlyTransmuter {
        totalSyntheticsIssued -= amount;
    }

    ///@inheritdoc IAlchemistV3Actions
    function setTransmuterTokenBalance(uint256 amount) external onlyTransmuter {
        uint256 last = lastTransmuterTokenBalance;

        // If balance went down, assume cover could have been spent and reduce it conservatively.
        if (amount < last) {
            uint256 spent = last - amount;
            uint256 cover = _pendingCoverShares;

            if (spent >= cover) {
                _pendingCoverShares = 0;
            } else {
                _pendingCoverShares = cover - spent;
            }
        }

        // Always keep cover <= actual transmuter balance.
        if (_pendingCoverShares > amount) {
            _pendingCoverShares = amount;
        }

        // Update baseline
        lastTransmuterTokenBalance = amount;
    }

    /// @dev Keeps already-earmarked transfers from being re-counted as future cover.
    function _syncEarmarkedTransmuterTransfer(uint256 sharesSent, uint256 earmarkedShares) internal {
        if (earmarkedShares == 0) return;

        // Only the portion that satisfied an existing earmark should bypass cover accounting.
        if (sharesSent > earmarkedShares) sharesSent = earmarkedShares;
        lastTransmuterTokenBalance += sharesSent;
    }

    /// @inheritdoc IAlchemistV3Actions
    function poke(uint256 tokenId) external {
        _checkForValidAccountId(tokenId);
        _poke(tokenId);
    }


    /// @dev Pokes the account owned by `tokenId` to sync the state.
    /// @param tokenId The tokenId of the account to poke.
    function _poke(uint256 tokenId) internal {
        _earmark();
        _sync(tokenId);
    }

    /// @inheritdoc IAlchemistV3Actions
    function approveMint(uint256 tokenId, address spender, uint256 amount) external {
        _checkAccountOwnership(IAlchemistV3Position(alchemistPositionNFT).ownerOf(tokenId), msg.sender);
        _approveMint(tokenId, spender, amount);
    }

    /// @inheritdoc IAlchemistV3Actions
    function resetMintAllowances(uint256 tokenId) external {
        // Allow calls from either the token owner or the NFT contract
        if (msg.sender != address(alchemistPositionNFT)) {
            // Direct call - verify caller is current owner
            address tokenOwner = IERC721(alchemistPositionNFT).ownerOf(tokenId);
            if (msg.sender != tokenOwner) {
                revert Unauthorized();
            }
        }
        // increment version to start the mapping from a fresh state
        _accounts[tokenId].allowancesVersion += 1;
        // Emit event to notify allowance clearing
        emit MintAllowancesReset(tokenId);
    }

    /// @inheritdoc IAlchemistV3State
    function convertYieldTokensToDebt(uint256 amount) public view returns (uint256) {
        return normalizeUnderlyingTokensToDebt(convertYieldTokensToUnderlying(amount));
    }

    /// @inheritdoc IAlchemistV3State
    function convertDebtTokensToYield(uint256 amount) public view returns (uint256) {
        return convertUnderlyingTokensToYield(normalizeDebtTokensToUnderlying(amount));
    }

    /// @inheritdoc IAlchemistV3State
    function convertYieldTokensToUnderlying(uint256 amount) public view returns (uint256) {
        return IVaultV2(myt).convertToAssets(amount);
    }

    /// @inheritdoc IAlchemistV3State
    function convertUnderlyingTokensToYield(uint256 amount) public view returns (uint256) {
        return IVaultV2(myt).convertToShares(amount);
    }

    /// @inheritdoc IAlchemistV3State
    function normalizeUnderlyingTokensToDebt(uint256 amount) public view returns (uint256) {
        return amount * underlyingConversionFactor;
    }

    /// @inheritdoc IAlchemistV3State
    function normalizeDebtTokensToUnderlying(uint256 amount) public view returns (uint256) {
        return amount / underlyingConversionFactor;
    }

    /// @dev Mints debt tokens to `recipient` using the account owned by `tokenId`.
    /// @param tokenId     The tokenId of the account to mint from.
    /// @param amount    The amount to mint.
    /// @param recipient The recipient of the minted debt tokens.
    function _mint(uint256 tokenId, uint256 amount, address recipient) internal {
        if (block.number == _accounts[tokenId].lastRepayBlock) revert CannotMintOnRepayBlock();
        _addDebt(tokenId, amount);

        totalSyntheticsIssued += amount;

        // Validate the tokenId's account to assure that the collateralization invariant is still held.
        _validate(tokenId);

        _accounts[tokenId].lastMintBlock = block.number;

        // Mint the debt tokens to the recipient.
        TokenUtils.safeMint(debtToken, recipient, amount);

        emit Mint(tokenId, amount, recipient);
    }

    /**
     * @notice Uses the account's own collateral to retire debt and the associated protocol fee.
     * @param accountId The token id of the account to repay from.
     * @param amount The requested debt repayment amount, denominated in debt tokens.
     * @param skipPoke Whether to skip syncing the account before repayment.
     * @return creditToYield The MYT shares routed to the transmuter for the retired debt.
     */
     function _forceRepay(uint256 accountId, uint256 amount, bool skipPoke) internal returns (uint256) {
        if (amount == 0) {
            return 0;
        }
        _checkForValidAccountId(accountId);
        if (!skipPoke) {
            _poke(accountId);
        }
        Account storage account = _accounts[accountId];
        uint256 debt;

        // Retiring debt from collateral can cover any debt bucket.
        _checkState((debt = account.debt) > 0);

        // Clamp requested repayment against live account/global debt.
        uint256 credit = _capDebtCredit(amount, debt);
        if (credit == 0) return 0;
        // Reduce earmarked debt first, then total debt.
        uint256 earmarkedRepaid = _subEarmarkedDebt(credit, accountId);
        _subDebt(accountId, credit);
        
        // Remove the collateral that backs the retired debt.
        uint256 creditToYield = _subCollateralBalance(convertDebtTokensToYield(credit), accountId);

        // Collect as much protocol fee as the remaining collateral can support.
        uint256 targetProtocolFee = creditToYield * protocolFee / BPS;
        uint256 protocolFeeTotal = _subCollateralBalance(targetProtocolFee, accountId);


        emit ForceRepay(accountId, amount, creditToYield, protocolFeeTotal);

        if (creditToYield > 0) {
            // Route the retired collateral to the transmuter.
            TokenUtils.safeTransfer(myt, address(transmuter), creditToYield);
            _syncEarmarkedTransmuterTransfer(creditToYield, convertDebtTokensToYield(earmarkedRepaid));
        }

        if (protocolFeeTotal > 0) {
            // Forward the realized fee to the protocol fee receiver.
            TokenUtils.safeTransfer(myt, protocolFeeReceiver, protocolFeeTotal);
        }

        return creditToYield;
    }

    /// @dev Subtracts the earmarked debt by `amount` for the account owned by `accountId`.
    /// @param amountInDebtTokens The amount of debt tokens to subtract from the earmarked debt.
    /// @param accountId The tokenId of the account to subtract the earmarked debt from.
    /// @return The amount of debt tokens subtracted from the earmarked debt.
    function _subEarmarkedDebt(uint256 amountInDebtTokens, uint256 accountId) internal returns (uint256) {
        Account storage account = _accounts[accountId];

        uint256 debt = account.debt;
        uint256 earmarkedDebt = account.earmarked;

        uint256 credit = amountInDebtTokens > debt ? debt : amountInDebtTokens;
        uint256 earmarkToRemove = credit > earmarkedDebt ? earmarkedDebt : credit;

        // Always reduce local earmark by the full local repay amount.
        account.earmarked = earmarkedDebt - earmarkToRemove;

        // Global can lag local by rounding; clamp only the global subtraction.
        uint256 remove = earmarkToRemove > cumulativeEarmarked ? cumulativeEarmarked : earmarkToRemove;
        cumulativeEarmarked -= remove;

        return earmarkToRemove;
    }


    /// @dev Subtracts the collateral balance by `amount` for the account owned by `accountId`.
    /// @param amountInYieldTokens The amount of yield tokens to subtract from the collateral balance.
    /// @param accountId The tokenId of the account to subtract the collateral balance from.
    /// @return The amount of yield tokens subtracted from the collateral balance.
    function _subCollateralBalance(uint256 amountInYieldTokens, uint256 accountId) internal returns (uint256) {
        Account storage account = _accounts[accountId];
        uint256 collateralBalance = account.collateralBalance;

        // Reconcile local collateral against global tracked shares before subtraction.
        // This prevents underflow if rounding/drift made local storage exceed global storage.
        if (collateralBalance > _mytSharesDeposited) {
            collateralBalance = _mytSharesDeposited;
            account.collateralBalance = collateralBalance;
        }

        uint256 amountToRemove = amountInYieldTokens > collateralBalance ? collateralBalance : amountInYieldTokens;
        account.collateralBalance = collateralBalance - amountToRemove;
        _mytSharesDeposited -= amountToRemove;
        return amountToRemove;
    }

    /// @dev Syncs the account, attempts earmarked debt repayment, and liquidates collateral if still unhealthy.
    /// @param accountId The token id of the account to liquidate.
    /// @return amountLiquidated The amount of MYT seized from the account.
    /// @return feeInYield The MYT fee paid to the liquidator.
    /// @return feeInUnderlying The underlying-denominated fee paid from the fee vault, if any.
    function _liquidate(uint256 accountId) internal returns (uint256 amountLiquidated, uint256 feeInYield, uint256 feeInUnderlying) {
        // Query transmuter and earmark global debt
        _earmark();
        // Sync current user debt before deciding how much needs to be liquidated
        _sync(accountId);

        Account storage account = _accounts[accountId];

        // If one share is worth zero underlying, liquidation math is undefined and should noop.
        if (IVaultV2(myt).convertToAssets(1e18) == 0) {
            return (0, 0, 0);
        }
       
        if (_isAccountHealthy(accountId, false)) {
            return (0, 0, 0);
        }

        // First try to clear earmarked debt from the account's own collateral.
        uint256 repaidAmountInYield = 0;
        if (account.earmarked > 0) {
            repaidAmountInYield = _forceRepay(accountId, account.earmarked, false);
            feeInYield = _calculateRepaymentFee(repaidAmountInYield);
            // Final safety check after all deductions
            if (account.collateralBalance == 0 && account.debt > 0) {
                uint256 debtToClear = _clearableDebt(account.debt);
                if (debtToClear > 0) {
                    _subDebt(accountId, debtToClear);
                }
            }
        }

        // Recalculate ratio after any repayment to determine if further liquidation is needed
        if (_isAccountHealthy(accountId, false)) {
            if (feeInYield > 0) {
                uint256 targetFeeInYield = feeInYield;
                uint256 maxSafeFeeInYield = _maxRepaymentFeeInYield(accountId);
                // Use a single fee source: either the account can safely cover the full fee in MYT,
                // or the entire fee is outsourced to the fee vault.
                if (maxSafeFeeInYield < targetFeeInYield) {
                    feeInYield = 0;
                    feeInUnderlying = convertYieldTokensToUnderlying(targetFeeInYield);
                }
            }

            if (feeInYield > 0) {
                feeInYield = _subCollateralBalance(feeInYield, accountId); // clamps to available balance
                TokenUtils.safeTransfer(myt, msg.sender, feeInYield);
            } else if (feeInUnderlying > 0) {
                feeInUnderlying = _payWithFeeVault(feeInUnderlying);
            }
            emit RepaymentFee(accountId, msg.sender, feeInYield, feeInUnderlying);
            return (repaidAmountInYield, feeInYield, feeInUnderlying);
        } else {
            // Do actual liquidation
            return _doLiquidation(accountId);
        }

    }

    /// @dev Pays the fee to msg.sender in underlying tokens using the fee vault
    /// @param amountInUnderlying The amount of underlying tokens to pay
    /// @return actual amount paid based on the vault balance
    function _payWithFeeVault(uint256 amountInUnderlying) internal returns (uint256) {
        if (amountInUnderlying == 0) return 0;
        if (alchemistFeeVault == address(0)) {
            emit FeeShortfall(msg.sender, amountInUnderlying, 0);
            return 0;
        }
        uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
        if (vaultBalance > 0) {
            uint256 adjustedAmount = amountInUnderlying > vaultBalance ? vaultBalance : amountInUnderlying;
            IFeeVault(alchemistFeeVault).withdraw(msg.sender, adjustedAmount);
            if (adjustedAmount < amountInUnderlying) {
                emit FeeShortfall(msg.sender, amountInUnderlying, adjustedAmount);
            }
            return adjustedAmount;
        }
        emit FeeShortfall(msg.sender, amountInUnderlying, 0);
        return 0;
    }

    /// @dev Checks if the account is healthy
    /// @dev An account is healthy if its collateralization ratio is greater than the collateralization lower bound
    /// @dev An account is healthy if it has no debt
    /// @param accountId The tokenId of the account to check.
    /// @param refresh Whether to refresh the account's collateral value by including unrealized debt.
    /// @return true if the account is healthy, false otherwise.
    function _isAccountHealthy(uint256 accountId, bool refresh) internal view returns (bool) {
        if (_accounts[accountId].debt == 0) {
            return true;
        }
        uint256 collateralInUnderlying = _totalCollateralValue(accountId, refresh);
        uint256 collateralizationRatio = collateralInUnderlying * FIXED_POINT_SCALAR / _accounts[accountId].debt;
        return collateralizationRatio > collateralizationLowerBound;
    }

    /// @dev Performs the collateral-seizure phase of liquidation once the account must be liquidated.
    /// @param accountId The token id of the account to liquidate.
    /// @return amountLiquidated The amount of MYT seized from the account.
    /// @return feeInYield The MYT fee paid to the liquidator.
    /// @return feeInUnderlying The underlying-denominated fee paid from the fee vault.
    function _doLiquidation(uint256 accountId)
        internal
        returns (uint256 amountLiquidated, uint256 feeInYield, uint256 feeInUnderlying)
    {
        Account storage account = _accounts[accountId];
        uint256 debt = account.debt;
        uint256 collateralInUnderlying = totalValue(accountId);
        (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee, uint256 outsourcedFee) = calculateLiquidation(
            collateralInUnderlying,
            debt,
            liquidationTargetCollateralization,
            normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt,
            globalMinimumCollateralization,
            liquidatorFee
        );

        if (liquidationAmount == 0) {
            // Debt-only closeout path: account can be insolvent with no remaining collateral to seize.
            if (debtToBurn > 0) {
                uint256 burnableDebt = _capDebtCredit(debtToBurn, account.debt);
                if (burnableDebt > 0) {
                    _subDebt(accountId, burnableDebt);
                }
            }

            uint256 feeRequestInUnderlying = normalizeDebtTokensToUnderlying(outsourcedFee);
            if (feeRequestInUnderlying > 0) {
                feeInUnderlying = _payWithFeeVault(feeRequestInUnderlying);
            }

            if (account.debt < debt || feeInUnderlying > 0) {
                emit Liquidated(accountId, msg.sender, 0, 0, feeInUnderlying);
                return (0, 0, feeInUnderlying);
            }

            return (0, 0, 0);
        }

        uint256 requestedLiquidationInYield = convertDebtTokensToYield(liquidationAmount);
        amountLiquidated = _subCollateralBalance(requestedLiquidationInYield, accountId);
        if (amountLiquidated == 0) return (0, 0, 0);

        // Fee and debt burn are derived from idealized liquidation math, so clamp them to what was
        // actually realized after collateral capping to avoid underflow and over-burning debt.
        uint256 requestedFeeInYield = convertDebtTokensToYield(baseFee);
        feeInYield = requestedFeeInYield > amountLiquidated ? amountLiquidated : requestedFeeInYield;

        uint256 netToTransmuter = amountLiquidated - feeInYield;
        uint256 maxDebtByRealized = convertYieldTokensToDebt(netToTransmuter);
        uint256 maxDebtByStorage = account.debt < totalDebt ? account.debt : totalDebt;

        if (debtToBurn > maxDebtByRealized) debtToBurn = maxDebtByRealized;
        if (debtToBurn > maxDebtByStorage) debtToBurn = maxDebtByStorage;

        // Apply the realized debt reduction.
        if (debtToBurn > 0) {
            _subDebt(accountId, debtToBurn);
        }

        // If liquidation still leaves the account unhealthy, force-close the residual:
        // sweep all remaining collateral and clear any debt that cannot be backed anymore.
        if (account.debt > 0 && !_isAccountHealthy(accountId, false)) {
            uint256 remainingShares = account.collateralBalance;
            if (remainingShares > 0) {
                uint256 removedShares = _subCollateralBalance(remainingShares, accountId);
                netToTransmuter += removedShares;

                uint256 extraDebtBurn = _capDebtCredit(convertYieldTokensToDebt(removedShares), account.debt);
                if (extraDebtBurn > 0) {
                    _subDebt(accountId, extraDebtBurn);
                }
            }

            if (account.collateralBalance == 0 && account.debt > 0) {
                uint256 debtToClear = _clearableDebt(account.debt);
                if (debtToClear > 0) {
                    _subDebt(accountId, debtToClear);
                }
            }
        }

        // Route seized collateral net of liquidator fee to the transmuter.
        TokenUtils.safeTransfer(myt, transmuter, netToTransmuter);

        // Pay the liquidator from MYT if available, otherwise fall back to the fee vault.
        if (feeInYield > 0) {
            TokenUtils.safeTransfer(myt, msg.sender, feeInYield);
        } else if (normalizeDebtTokensToUnderlying(outsourcedFee) > 0) {
            feeInUnderlying = _payWithFeeVault(normalizeDebtTokensToUnderlying(outsourcedFee));
        }
        emit Liquidated(accountId, msg.sender, amountLiquidated, feeInYield, feeInUnderlying);
        return (amountLiquidated, feeInYield, feeInUnderlying);
    }

 
    /// @dev Handles repayment fee calculation.
    /// @param repaidAmountInYield The amount of debt repaid in yield tokens.
    /// @return feeInYield The fee in yield tokens to be sent to the liquidator.
    function _calculateRepaymentFee(uint256 repaidAmountInYield) internal view returns (uint256 feeInYield) {
        return repaidAmountInYield * repaymentFee / BPS;
    }

    /// @dev Returns max yield-fee removable while remaining strictly healthy (> lower bound).
    /// @param accountId The tokenId of the account to compute the max repayment fee for.
    /// @return The max repayment fee in yield tokens.
    function _maxRepaymentFeeInYield(uint256 accountId) internal view returns (uint256) {
        Account storage account = _accounts[accountId];
        uint256 debt = account.debt;
        if (debt == 0) {
            return account.collateralBalance;
        }

        uint256 collateralInDebt = convertYieldTokensToDebt(account.collateralBalance);
        uint256 minimumByLowerBound = FixedPointMath.mulDivUp(debt, collateralizationLowerBound, FIXED_POINT_SCALAR);
        if (minimumByLowerBound == type(uint256).max) {
            return 0;
        }

        // _isAccountHealthy uses a strict ">" check, so retain one debt-unit of margin.
        uint256 minRequiredPostFee = minimumByLowerBound + 1;
        if (collateralInDebt <= minRequiredPostFee) {
            return 0;
        }

        uint256 removableInDebt = collateralInDebt - minRequiredPostFee;
        return convertDebtTokensToYield(removableInDebt);
    }

    /// @dev Increases the debt by `amount` for the account owned by `tokenId`.
    ///
    /// @param tokenId   The account owned by tokenId.
    /// @param amount  The amount to increase the debt by.
    function _addDebt(uint256 tokenId, uint256 amount) internal {
        Account storage account = _accounts[tokenId];

        uint256 newDebt = account.debt + amount;

        // After _sync(tokenId), you can use the current collateralBalance (no simulation needed)
        uint256 collateralValue = _totalCollateralValue(tokenId, false);

        uint256 required = FixedPointMath.mulDivUp(newDebt, minimumCollateralization, FIXED_POINT_SCALAR);
        if (collateralValue < required) revert Undercollateralized();

        account.debt = newDebt;
        totalDebt += amount;
    }

    /// @dev Subtracts the debt by `amount` for the account owned by `tokenId`.
    ///
    /// @param tokenId   The account owned by tokenId.
    /// @param amount  The amount to decrease the debt by.
    function _subDebt(uint256 tokenId, uint256 amount) internal {
        Account storage account = _accounts[tokenId];

        account.debt -= amount;
        totalDebt -= amount;

        if (cumulativeEarmarked > totalDebt) {
            cumulativeEarmarked = totalDebt;
        }
    }

    /// @dev Caps a debt-denominated credit against account debt and global debt.
    function _capDebtCredit(uint256 requested, uint256 accountDebt) internal view returns (uint256) {
        uint256 credit = requested > accountDebt ? accountDebt : requested;
        if (credit > totalDebt) credit = totalDebt;
        return credit;
    }

    /// @dev Returns debt that can be safely cleared against global debt accounting.
    function _clearableDebt(uint256 accountDebt) internal view returns (uint256) {
        return accountDebt > totalDebt ? totalDebt : accountDebt;
    }

    /// @dev Set the mint allowance for `spender` to `amount` for the account owned by `tokenId`.
    ///
    /// @param ownerTokenId   The id of the account granting approval.
    /// @param spender The address of the spender.
    /// @param amount  The amount of debt tokens to set the mint allowance to.
    function _approveMint(uint256 ownerTokenId, address spender, uint256 amount) internal {
        Account storage account = _accounts[ownerTokenId];
        account.mintAllowances[account.allowancesVersion][spender] = amount;
        emit ApproveMint(ownerTokenId, spender, amount);
    }

    /// @dev Decrease the mint allowance for `spender` by `amount` for the account owned by `ownerTokenId`.
    ///
    /// @param ownerTokenId The id of the account owner.
    /// @param spender The address of the spender.
    /// @param amount  The amount of debt tokens to decrease the mint allowance by.
    function _decreaseMintAllowance(uint256 ownerTokenId, address spender, uint256 amount) internal {
        Account storage account = _accounts[ownerTokenId];
        account.mintAllowances[account.allowancesVersion][spender] -= amount;
    }

    /// @dev Checks an expression and reverts with an {IllegalArgument} error if the expression is {false}.
    ///
    /// @param expression The expression to check.
    function _checkArgument(bool expression) internal pure {
        if (!expression) {
            revert IllegalArgument();
        }
    }

    /// @dev Checks if owner == sender and reverts with an {UnauthorizedAccountAccessError} error if the result is {false}.
    ///
    /// @param owner The address of the owner of an account.
    /// @param user The address of the user attempting to access an account.
    function _checkAccountOwnership(address owner, address user) internal pure {
        if (owner != user) {
            revert UnauthorizedAccountAccessError();
        }
    }

    /// @dev reverts {UnknownAccountOwnerIDError} error by if no owner exists.
    ///
    /// @param tokenId The id of an account.
    function _checkForValidAccountId(uint256 tokenId) internal view {
        if (!_tokenExists(alchemistPositionNFT, tokenId)) {
            revert UnknownAccountOwnerIDError();
        }
    }

    function _onlyAdmin() internal view {
        if (msg.sender != admin) {
            revert Unauthorized();
        }
    }

    function _onlyAdminOrGuardian() internal view {
        if (msg.sender != admin && !guardians[msg.sender]) {
            revert Unauthorized();
        }
    }

    function _onlyTransmuter() internal view {
        if (msg.sender != transmuter) {
            revert Unauthorized();
        }
    }

    /**
     * @notice Checks whether a token id is linked to an owner. Non blocking / no reverts.
     * @param nft The address of the ERC721 based contract.
     * @param tokenId The token id to check.
     * @return exists A boolean that is true if the token exists.
     */
    function _tokenExists(address nft, uint256 tokenId) internal view returns (bool exists) {
        if (tokenId == 0) {
            // token ids start from 1
            return false;
        }
        try IERC721(nft).ownerOf(tokenId) {
            // If the call succeeds, the token exists.
            exists = true;
        } catch {
            // If the call fails, then the token does not exist.
            exists = false;
        }
    }

    /// @dev Checks an expression and reverts with an {IllegalState} error if the expression is {false}.
    ///
    /// @param expression The expression to check.
    function _checkState(bool expression) internal pure {
        if (!expression) {
            revert IllegalState();
        }
    }

    /// @dev Checks that the account owned by `tokenId` is properly collateralized.
    /// @dev If the account is undercollateralized then this will revert with an {Undercollateralized} error.
    ///
    /// @param tokenId The id of the account owner.
    function _validate(uint256 tokenId) internal view {
        if (_isUnderCollateralized(tokenId)) revert Undercollateralized();
    }

    /// @dev Calculate the total collateral value of the account in debt tokens.
    /// @param tokenId The id of the account owner.
    /// @return The total collateral value of the account in debt tokens.
    function _totalCollateralValue(uint256 tokenId, bool includeUnrealizedDebt) internal view returns (uint256) {
        uint256 totalUnderlying;
        if (includeUnrealizedDebt) {
            (,, uint256 collateral) = _calculateUnrealizedDebt(tokenId);
            if (collateral > 0) totalUnderlying += convertYieldTokensToUnderlying(collateral);
        } else {
            totalUnderlying = convertYieldTokensToUnderlying(_accounts[tokenId].collateralBalance);
        }
        return normalizeUnderlyingTokensToDebt(totalUnderlying);
    }

    /// @dev Update the user's earmarked and redeemed debt amounts.
    function _sync(uint256 tokenId) internal {
        Account storage account = _accounts[tokenId];
        (uint256 newDebt, uint256 newEarmarked, uint256 redeemedTotal) =
            _computeUnrealizedAccount(account, _earmarkWeight, _redemptionWeight, _survivalAccumulator);

        // Calculate collateral to remove
        uint256 globalDebtDelta = _totalRedeemedDebt - account.lastTotalRedeemedDebt;
        if (globalDebtDelta != 0 && redeemedTotal != 0) {
            uint256 globalSharesDelta = _totalRedeemedSharesOut - account.lastTotalRedeemedSharesOut;

            // sharesToDebit = redeemedTotal * globalSharesDelta / globalDebtDelta
            uint256 sharesToDebit = FixedPointMath.mulDivUp(redeemedTotal, globalSharesDelta, globalDebtDelta);

            if (sharesToDebit > account.collateralBalance) sharesToDebit = account.collateralBalance;
            account.collateralBalance -= sharesToDebit;
        }

        // advance checkpoints even if redeemedTotal==0
        account.lastTotalRedeemedDebt = _totalRedeemedDebt;
        account.lastTotalRedeemedSharesOut = _totalRedeemedSharesOut;

        account.earmarked = newEarmarked;
        account.debt = newDebt;

        // Advance account checkpoint
        account.lastAccruedEarmarkWeight = _earmarkWeight;
        account.lastAccruedRedemptionWeight = _redemptionWeight;

        // Snapshot G for this account
        account.lastSurvivalAccumulator = _survivalAccumulator;
    }

    /// @dev Computes the account debt/earmark state at a given global weight snapshot.
    /// @return newDebt The debt after applying earmark + redemption.
    /// @return newEarmarked The earmarked portion after applying survival and new earmarks.
    /// @return redeemedDebt Realized redeemed debt for this step.
    function _computeUnrealizedAccount(
        Account storage account,
        uint256 earmarkWeightCurrent,
        uint256 redemptionWeightCurrent,
        uint256 survivalAccumulatorCurrent
    ) internal view returns (uint256 newDebt, uint256 newEarmarked, uint256 redeemedDebt) {
        // Survival during current sync window
        uint256 survivalRatio = 
            _redemptionSurvivalRatio(account.lastAccruedRedemptionWeight, redemptionWeightCurrent);

        // User exposure at last sync used to calculate newly earmarked debt pre redemption
        uint256 userExposure = account.debt > account.earmarked ? account.debt - account.earmarked : 0;
        uint256 unearmarkSurvivalRatio = 
            _earmarkSurvivalRatio(account.lastAccruedEarmarkWeight, earmarkWeightCurrent);

        // amount that stayed unearmarked from userExposure
        uint256 unearmarkedRemaining = FixedPointMath.mulQ128(userExposure, unearmarkSurvivalRatio);

        // amount newly earmarked since last sync
        uint256 earmarkRaw = userExposure - unearmarkedRemaining;

        // No redemption in this sync window -> debt cannot decrease.
        if (survivalRatio == ONE_Q128) {
            newDebt = account.debt;
            newEarmarked = account.earmarked + earmarkRaw;
            if (newEarmarked > newDebt) newEarmarked = newDebt;
            redeemedDebt = 0;
            return (newDebt, newEarmarked, redeemedDebt);
        }

        // Unwind via the survival accumulator.
        uint256 earmarkSurvival = _redIndex(account.lastAccruedEarmarkWeight);
        if (earmarkSurvival == 0) earmarkSurvival = ONE_Q128;

        // Default path for accounts that stayed inside the same earmark epoch.
        uint256 decayedRedeemed = FixedPointMath.mulQ128(account.lastSurvivalAccumulator, survivalRatio);
        uint256 survivalDiff = survivalAccumulatorCurrent > decayedRedeemed ? survivalAccumulatorCurrent - decayedRedeemed : 0;
        if (survivalDiff > earmarkSurvival) survivalDiff = earmarkSurvival;
        uint256 unredeemedRatio = FixedPointMath.divQ128(survivalDiff, earmarkSurvival);
        uint256 earmarkedUnredeemed = FixedPointMath.mulQ128(userExposure, unredeemedRatio);

        uint256 oldEarEpoch = account.lastAccruedEarmarkWeight >> _EARMARK_INDEX_BITS;
        uint256 newEarEpoch = earmarkWeightCurrent >> _EARMARK_INDEX_BITS;
        if (newEarEpoch == oldEarEpoch) {
            uint256 currentEarmarkIndex = earmarkWeightCurrent & _EARMARK_INDEX_MASK;
            uint256 telescopedEarmarkDrop = earmarkSurvival > currentEarmarkIndex ? earmarkSurvival - currentEarmarkIndex : 0;

            // When the current sync window stayed inside one earmark epoch and the accumulator
            // collapses to the telescoped earmark drop, there were no interleaved pre-claim
            // redemptions that require per-step weighting. Using the user-sized formula here
            // avoids amplifying index-scale ceil rounding by exposure / earmarkSurvival.
            if (survivalDiff == FixedPointMath.mulQ128(telescopedEarmarkDrop, survivalRatio)) {
                earmarkedUnredeemed = FixedPointMath.mulQ128(earmarkRaw, survivalRatio);
            }
        }

        // If the account crossed an earmark epoch, split math at the first boundary:
        // - pre-boundary via accumulator diff at epoch boundary,
        // - post-boundary via redemption survival only.
        // This avoids both over-redemption (pre-boundary redemptions applied twice)
        // and under-redemption (post-boundary accumulator contamination).
        if (newEarEpoch > oldEarEpoch) {
            uint256 boundaryEpoch = oldEarEpoch + 1;
            uint256 boundaryRedemptionWeight = _earmarkEpochStartRedemptionWeight[boundaryEpoch];
            uint256 boundarySurvivalAccumulator = _earmarkEpochStartSurvivalAccumulator[boundaryEpoch];

            if (boundaryRedemptionWeight != 0) {
                uint256 preBoundarySurvival =
                    _redemptionSurvivalRatio(account.lastAccruedRedemptionWeight, boundaryRedemptionWeight);
                uint256 decayedAtBoundary = FixedPointMath.mulQ128(account.lastSurvivalAccumulator, preBoundarySurvival);

                uint256 boundaryDiff =
                    boundarySurvivalAccumulator > decayedAtBoundary ? boundarySurvivalAccumulator - decayedAtBoundary : 0;
                if (boundaryDiff > earmarkSurvival) boundaryDiff = earmarkSurvival;

                uint256 unredeemedAtBoundaryRatio = FixedPointMath.divQ128(boundaryDiff, earmarkSurvival);
                uint256 unredeemedAtBoundary = FixedPointMath.mulQ128(userExposure, unredeemedAtBoundaryRatio);

                uint256 postBoundarySurvival =
                    _redemptionSurvivalRatio(boundaryRedemptionWeight, redemptionWeightCurrent);

                earmarkedUnredeemed = FixedPointMath.mulQ128(unredeemedAtBoundary, postBoundarySurvival);
            } else {
                // Backward-compatibility fallback for old state without boundary checkpoints.
                earmarkedUnredeemed = FixedPointMath.mulQ128(earmarkRaw, survivalRatio);
            }
        }

        if (earmarkedUnredeemed > earmarkRaw) earmarkedUnredeemed = earmarkRaw;

        // Old earmarks that survived redemptions in the current sync window
        uint256 exposureSurvival = FixedPointMath.mulQ128(account.earmarked, survivalRatio);
        // What was redeemed from the newly earmark between last sync and now
        uint256 redeemedFromEarmarked = earmarkRaw - earmarkedUnredeemed;
        // Total overall earmarked to adjust user debt
        uint256 redeemedTotal = (account.earmarked - exposureSurvival) + redeemedFromEarmarked;

        newDebt = account.debt >= redeemedTotal ? account.debt - redeemedTotal : 0;
        redeemedDebt = account.debt - newDebt;
        newEarmarked = exposureSurvival + earmarkedUnredeemed;
        if (newEarmarked > newDebt) newEarmarked = newDebt;
    }

    /// @dev Earmarks the debt for redemption.
    function _earmark() internal {
        if (totalDebt == 0) return;
        if (block.number <= lastEarmarkBlock) return;

        // update pending cover shares based on transmuter balance delta
        uint256 transmuterBalance = TokenUtils.safeBalanceOf(myt, address(transmuter));

        if (transmuterBalance > lastTransmuterTokenBalance) {
            _pendingCoverShares += (transmuterBalance - lastTransmuterTokenBalance);
        }

        lastTransmuterTokenBalance = transmuterBalance;

        // how to earmark this window
        uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);

        // apply cover
        uint256 coverInDebt = convertYieldTokensToDebt(_pendingCoverShares);
        if (amount != 0 && coverInDebt != 0) {
            uint256 usedDebt = amount > coverInDebt ? coverInDebt : amount;
            amount -= usedDebt;

            // consume the corresponding portion of pending cover shares so we can't reuse it
            uint256 sharesUsed = FixedPointMath.mulDivUp(_pendingCoverShares, usedDebt, coverInDebt);
            if (sharesUsed > _pendingCoverShares) sharesUsed = _pendingCoverShares;
            _pendingCoverShares -= sharesUsed;
        }

        uint256 liveUnearmarked = totalDebt - cumulativeEarmarked;
        if (amount > liveUnearmarked) amount = liveUnearmarked;

        if (amount > 0 && liveUnearmarked != 0) {
            // ratioWanted = (liveUnearmarked - amount) / liveUnearmarked
            uint256 ratioWanted =
                (amount == liveUnearmarked) ? 0 : FixedPointMath.divQ128(liveUnearmarked - amount, liveUnearmarked);

            uint256 packedOld = _earmarkWeight;
            (uint256 packedNew, uint256 ratioApplied, uint256 oldIndex, uint256 newEpoch, bool epochAdvanced) =
                _simulateEarmarkPackedUpdate(packedOld, ratioWanted);

            _earmarkWeight = packedNew;

            // Survival increment uses the APPLIED earmark fraction
            uint256 earmarkedFraction = ONE_Q128 - ratioApplied;
            _survivalAccumulator += FixedPointMath.mulQ128(oldIndex, earmarkedFraction);

            if (epochAdvanced) {
                _earmarkEpochStartRedemptionWeight[newEpoch] = _redemptionWeight;
                _earmarkEpochStartSurvivalAccumulator[newEpoch] = _survivalAccumulator;
            }

            // Bump cumulativeEarmarked by the effective amount implied by ratioApplied
            uint256 newUnearmarked = FixedPointMath.mulQ128(liveUnearmarked, ratioApplied);
            uint256 effectiveEarmarked = liveUnearmarked - newUnearmarked;

            cumulativeEarmarked += effectiveEarmarked;
        }

        lastEarmarkBlock = block.number;
    }

    /// @dev Projects an account through one sync, including one simulated pending earmark window.
    ///
    /// @param tokenId The account id to project.
    ///
    /// @return The projected debt after sync.
    /// @return The projected earmarked debt after sync.
    /// @return The projected collateral remaining after realized redemptions are applied.
    function _calculateUnrealizedDebt(uint256 tokenId)
        internal
        view
        returns (uint256, uint256, uint256)
    {
        Account storage account = _accounts[tokenId];

        // Simulate one uncommitted earmark window and use its simulated weight.
        (uint256 earmarkWeightCopy,) = _simulateUnrealizedEarmark();

        // First, compute account state against committed globals only.
        (uint256 newDebt, uint256 newEarmarked, uint256 redeemedTotalSim) =
            _computeUnrealizedAccount(account, _earmarkWeight, _redemptionWeight, _survivalAccumulator);

        // Then, apply the simulated earmark-only delta.
        // Important: this prospective earmark happens "now", so historical redemptions must not
        // reduce debt again through this simulated step.
        if (earmarkWeightCopy != _earmarkWeight) {
            uint256 exposure = newDebt > newEarmarked ? newDebt - newEarmarked : 0;
            if (exposure != 0) {
                uint256 unearmarkedRatio = _earmarkSurvivalRatio(_earmarkWeight, earmarkWeightCopy);
                uint256 unearmarkedRemaining = FixedPointMath.mulQ128(exposure, unearmarkedRatio);
                uint256 newlyEarmarked = exposure - unearmarkedRemaining;
                newEarmarked += newlyEarmarked;
                if (newEarmarked > newDebt) newEarmarked = newDebt;
            }
        }

        // Calculate collateral to remove from fees and redemptions
        uint256 collateralBalanceCopy = account.collateralBalance;
        uint256 globalDebtDelta = _totalRedeemedDebt - account.lastTotalRedeemedDebt;
        if (globalDebtDelta != 0 && redeemedTotalSim != 0) {
            uint256 globalSharesDelta = _totalRedeemedSharesOut - account.lastTotalRedeemedSharesOut;
            uint256 sharesToDebit = FixedPointMath.mulDivUp(redeemedTotalSim, globalSharesDelta, globalDebtDelta);
            if (sharesToDebit > collateralBalanceCopy) sharesToDebit = collateralBalanceCopy;
            collateralBalanceCopy -= sharesToDebit;
        }

        return (newDebt, newEarmarked, collateralBalanceCopy);
    }

    /// @dev Checks that the account owned by `tokenId` is properly collateralized.
    /// @dev Returns true only if the account is undercollateralized
    ///
    /// @param tokenId The id of the account owner.
    function _isUnderCollateralized(uint256 tokenId) internal view returns (bool) {
        uint256 debt = _accounts[tokenId].debt;
        if (debt == 0) return false;

        // totalValue(tokenId) is already denominated in debt-token units
        uint256 collateralValue = totalValue(tokenId);

        // Required collateral value = ceil(debt * minCollat / 1e18)
        uint256 required = FixedPointMath.mulDivUp(debt, minimumCollateralization, FIXED_POINT_SCALAR);

        return collateralValue < required;
    }

    /// @dev Converts tracked MYT shares into underlying units.
    /// @return totalUnderlyingValue The underlying value represented by `_mytSharesDeposited`.
    function _getTotalUnderlyingValue() internal view returns (uint256 totalUnderlyingValue) {
        uint256 yieldTokenTVLInUnderlying = convertYieldTokensToUnderlying(_mytSharesDeposited);
        totalUnderlyingValue = yieldTokenTVLInUnderlying;
    }

    /// @dev Returns the underlying value that must remain locked to support live debt, capped by tracked MYT.
    function _getTotalLockedUnderlyingValue() internal view returns (uint256) {
        uint256 required = _requiredLockedShares();

        // Cap by actual shares held in the Alchemist
        uint256 held = _mytSharesDeposited;

        uint256 lockedShares = required > held ? held : required;
        return convertYieldTokensToUnderlying(lockedShares);
    }
    /// @dev Returns true if issued synthetics exceed global backing.
    ///      Backing mirrors Transmuter claim math:
    ///      locked collateral in Alchemist + MYT shares currently held by Transmuter.
    function _isProtocolInBadDebt() internal view returns (bool) {
        if (totalSyntheticsIssued == 0) return false;

        uint256 transmuterShares = TokenUtils.safeBalanceOf(myt, address(transmuter));
        uint256 backingUnderlying = _getTotalLockedUnderlyingValue() + convertYieldTokensToUnderlying(transmuterShares);
        uint256 backingDebt = normalizeUnderlyingTokensToDebt(backingUnderlying);

        return totalSyntheticsIssued > backingDebt;
    }

    /// @dev Calculates locked collateral based on share price
    function _requiredLockedShares() internal view returns (uint256) {
        if (totalDebt == 0) return 0;

        uint256 debtShares = convertDebtTokensToYield(totalDebt);
        return FixedPointMath.mulDivUp(debtShares, minimumCollateralization, FIXED_POINT_SCALAR);
    }

    // Survival ratio of *unearmarked* exposure between two packed earmark states.
    function _earmarkSurvivalRatio(uint256 oldPacked, uint256 newPacked) internal pure returns (uint256) {
        if (newPacked == oldPacked) return ONE_Q128;
        if (oldPacked == 0) return ONE_Q128; // uninitialized snapshot => assume "no change"

        uint256 oldEpoch = oldPacked >> _EARMARK_INDEX_BITS;
        uint256 newEpoch = newPacked >> _EARMARK_INDEX_BITS;

        // Epoch advanced => old unearmarked was fully earmarked at some point.
        if (newEpoch > oldEpoch) return 0;

        uint256 oldIdx = oldPacked & _EARMARK_INDEX_MASK;
        uint256 newIdx = newPacked & _EARMARK_INDEX_MASK;

        if (oldIdx == 0) return 0;

        return FixedPointMath.divQ128(newIdx, oldIdx);
    }

    /// @dev Computes redemption survival ratio between two redemption weights.
    ///      Uses division when survivals are representable, and falls back to delta-weight
    ///      when SurvivalFromWeight() underflows to 0 for both endpoints.
    function _redemptionSurvivalRatio(uint256 oldPacked, uint256 newPacked) internal pure returns (uint256) {
        if (newPacked == oldPacked) return ONE_Q128;
        if (oldPacked == 0) return ONE_Q128;

        uint256 oldEpoch = oldPacked >> _REDEMPTION_INDEX_BITS;
        uint256 newEpoch = newPacked >> _REDEMPTION_INDEX_BITS;

        // If epoch advances, there was a full wipe at some point
        if (newEpoch > oldEpoch) return 0;

        uint256 oldIndex = oldPacked & _REDEMPTION_INDEX_MASK;
        uint256 newIndex = newPacked & _REDEMPTION_INDEX_MASK;

        // If oldIndex is 0, treat as fully redeemed.
        if (oldIndex == 0) return 0;

        // ratio = newIndex / oldIndex
        return FixedPointMath.divQ128(newIndex, oldIndex);
    }

    /// @dev Simulates one uncommitted earmark window using current on-chain state.
    /// @return earmarkWeightCopy Simulated earmark packed weight after the window.
    /// @return effectiveEarmarked The additional earmarked debt from this simulated window.
    function _simulateUnrealizedEarmark() internal view returns (uint256 earmarkWeightCopy, uint256 effectiveEarmarked) {
        earmarkWeightCopy = _earmarkWeight;
        if (block.number <= lastEarmarkBlock || totalDebt == 0) return (earmarkWeightCopy, 0);

        uint256 transmuterBalance = TokenUtils.safeBalanceOf(myt, address(transmuter));

        // simulate pending cover
        uint256 pendingCover = _pendingCoverShares;
        if (transmuterBalance > lastTransmuterTokenBalance) {
            pendingCover += (transmuterBalance - lastTransmuterTokenBalance);
        }

        // simulate earmark amount for this window
        uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);

        // apply cover the same way
        uint256 coverInDebt = convertYieldTokensToDebt(pendingCover);
        if (amount != 0 && coverInDebt != 0) {
            uint256 usedDebt = amount > coverInDebt ? coverInDebt : amount;
            amount -= usedDebt;
        }

        uint256 liveUnearmarked = totalDebt - cumulativeEarmarked;
        if (amount > liveUnearmarked) amount = liveUnearmarked;
        if (amount == 0 || liveUnearmarked == 0) return (earmarkWeightCopy, 0);

        // ratioWanted = (liveUnearmarked - amount) / liveUnearmarked
        uint256 ratioWanted =
            (amount == liveUnearmarked) ? 0 : FixedPointMath.divQ128(liveUnearmarked - amount, liveUnearmarked);

        (uint256 packedNew, uint256 ratioApplied,,,) = _simulateEarmarkPackedUpdate(earmarkWeightCopy, ratioWanted);
        earmarkWeightCopy = packedNew;

        uint256 newUnearmarked = FixedPointMath.mulQ128(liveUnearmarked, ratioApplied);
        effectiveEarmarked = liveUnearmarked - newUnearmarked;
    }

    /// @dev Simulates the packed earmark update and returns the applied survival ratio.
    /// @param packedOld Existing packed earmark weight.
    /// @param ratioWanted Wanted survival ratio for unearmarked debt in this step.
    /// @return packedNew The new packed earmark weight.
    /// @return ratioApplied The effective survival ratio that will be observed by accounts.
    /// @return oldIndex The normalized previous index.
    /// @return newEpoch The resulting epoch after this step.
    /// @return epochAdvanced Whether the update crossed an epoch boundary.
    function _simulateEarmarkPackedUpdate(uint256 packedOld, uint256 ratioWanted)
        internal
        pure
        returns (uint256 packedNew, uint256 ratioApplied, uint256 oldIndex, uint256 newEpoch, bool epochAdvanced)
    {
        uint256 oldEpoch = packedOld >> _EARMARK_INDEX_BITS;
        oldIndex = packedOld & _EARMARK_INDEX_MASK;

        if (packedOld == 0) {
            oldEpoch = 0;
            oldIndex = ONE_Q128;
        }
        if (oldIndex == 0) {
            oldEpoch += 1;
            oldIndex = ONE_Q128;
        }

        newEpoch = oldEpoch;
        uint256 newIndex;

        if (ratioWanted == 0) {
            newEpoch += 1;
            newIndex = ONE_Q128;
        } else {
            newIndex = FixedPointMath.mulQ128(oldIndex, ratioWanted);
        }

        epochAdvanced = newEpoch > oldEpoch;
        packedNew = _packRed(newEpoch, newIndex);
        ratioApplied = epochAdvanced ? 0 : FixedPointMath.divQ128(newIndex, oldIndex);
    }

    // Bitwise helpers
    function _redEpoch(uint256 packed) private pure returns (uint256) {
        return packed >> _REDEMPTION_INDEX_BITS;
    }

    function _redIndex(uint256 packed) private pure returns (uint256) {
        return packed & _REDEMPTION_INDEX_MASK;
    }

    function _packRed(uint256 epoch, uint256 index) private pure returns (uint256) {
        return (epoch << _REDEMPTION_INDEX_BITS) | index;
    }

}







================================================
FILE: src/AlchemistV3Position.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {IAlchemistV3} from "./interfaces/IAlchemistV3.sol";
import {IMetadataRenderer} from "./interfaces/IMetadataRenderer.sol";

/**
 * @title AlchemistV3Position
 * @notice ERC721 position token for AlchemistV3, where only the AlchemistV3 contract
 *         is allowed to mint and burn tokens. Minting returns a unique token id.
 */
contract AlchemistV3Position is ERC721Enumerable {
    using Strings for uint256;

    /// @notice The only address allowed to mint and burn position tokens.
    address public alchemist;

    /// @notice The admin of the NFT contract, allowed to update the metadata renderer.
    address public admin;

    /// @notice Counter used for generating unique token ids.
    uint256 private _currentTokenId;

    /// @notice The external contract that generates tokenURI metadata.
    address public metadataRenderer;

    /// @notice An error which is used to indicate that the function call failed because the caller is not the alchemist
    error CallerNotAlchemist();

    /// @notice An error which is used to indicate that the function call failed because the caller is not the admin
    error CallerNotAdmin();

    /// @notice An error which is used to indicate that Alchemist set is the zero address
    error AlchemistZeroAddressError();

    /// @notice An error which is used to indicate that address minted to is the zero address
    error MintToZeroAddressError();

    /// @notice An error which is used to indicate that the metadata renderer is not set
    error MetadataRendererNotSet();

    /// @dev Modifier to restrict calls to only the authorized AlchemistV3 contract.
    modifier onlyAlchemist() {
        if (msg.sender != alchemist) {
            revert CallerNotAlchemist();
        }

        _;
    }

    /// @dev Modifier to restrict calls to only the admin.
    modifier onlyAdmin() {
        if (msg.sender != admin) {
            revert CallerNotAdmin();
        }

        _;
    }

    /**
     * @notice Constructor that sets the Alchemist address, admin, and initializes the ERC721 token.
     * @param alchemist_ The address of the Alchemist contract.
     * @param admin_ The address of the admin allowed to update the metadata renderer.
     */
    constructor(address alchemist_, address admin_) ERC721("AlchemistV3Position", "ALCV3") {
        if (alchemist_ == address(0)) {
            revert AlchemistZeroAddressError();
        }
        alchemist = alchemist_;
        admin = admin_;
    }

    /// @notice Sets or updates the metadata renderer. Only callable by the admin.
    /// @param renderer The address of the new metadata renderer contract.
    function setMetadataRenderer(address renderer) external onlyAdmin {
        metadataRenderer = renderer;
    }

    /// @notice Transfers admin rights to a new address. Only callable by the current admin.
    /// @param newAdmin The address of the new admin.
    function setAdmin(address newAdmin) external onlyAdmin {
        admin = newAdmin;
    }

    /**
     * @notice Mints a new position NFT to `to`.
     * @dev Only callable by the AlchemistV3 contract.
     * @param to The recipient address for the new position.
     * @return tokenId The unique token id minted.
     */
    function mint(address to) external onlyAlchemist returns (uint256) {
        if (to == address(0)) {
            revert MintToZeroAddressError();
        }
        _currentTokenId++;
        uint256 tokenId = _currentTokenId;
        _mint(to, tokenId);
        return tokenId;
    }

    function burn(uint256 tokenId) public onlyAlchemist {
        _burn(tokenId);
    }

    /**
     * @notice Returns the token URI with embedded SVG
     * @param tokenId The token ID
     * @return The full token URI with data
     */
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        // revert if the token does not exist
        ERC721(address(this)).ownerOf(tokenId);
        if (metadataRenderer == address(0)) {
            revert MetadataRendererNotSet();
        }
        return IMetadataRenderer(metadataRenderer).tokenURI(tokenId);
    }

    /**
     * @notice Override supportsInterface to resolve inheritance conflicts.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Enumerable) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    /**
     * @notice Hook that is called before any token transfer
     */
    function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
        address from = _ownerOf(tokenId);
        // Reset mint allowances before the transfer completes
        if (from != address(0)) {
            // Skip during minting
            IAlchemistV3(alchemist).resetMintAllowances(tokenId);
        }
        // Call parent implementation first
        return super._update(to, tokenId, auth);
    }
}


================================================
FILE: src/AlchemistV3PositionRenderer.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {IMetadataRenderer} from "./interfaces/IMetadataRenderer.sol";
import {NFTMetadataGenerator} from "./libraries/NFTMetadataGenerator.sol";

/// @title AlchemistV3PositionRenderer
/// @notice Default metadata renderer for AlchemistV3Position NFTs.
contract AlchemistV3PositionRenderer is IMetadataRenderer {
    /// @inheritdoc IMetadataRenderer
    function tokenURI(uint256 tokenId) external pure override returns (string memory) {
        return NFTMetadataGenerator.generateTokenURI(tokenId, "Alchemist V3 Position");
    }
}


================================================
FILE: src/FrxEthEthDualOracleAggregatorAdapter.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IFrxEthEthDualOracle {
    function getPrices() external view returns (bool isBadData, uint256 priceLow, uint256 priceHigh);
}

/// @notice Adapts the Frax dual-oracle frxETH/ETH source to a Chainlink-style reader.
/// @dev The Frax dual oracle does not expose a publication timestamp. This adapter therefore
///      returns the current block timestamp for `startedAt` and `updatedAt`, which satisfies
///      the AggregatorV3Interface shape but does not provide an underlying freshness signal.
contract FrxEthEthDualOracleAggregatorAdapter {
    IFrxEthEthDualOracle public immutable dualOracle;

    constructor(address _dualOracle) {
        require(_dualOracle != address(0), "Zero dual oracle address");
        dualOracle = IFrxEthEthDualOracle(_dualOracle);
    }

    function decimals() external pure returns (uint8) {
        return 18;
    }

    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
    {
        (bool isBadData, uint256 priceLow, uint256 priceHigh) = dualOracle.getPrices();
        require(!isBadData, "Bad dual oracle data");

        uint256 averagePrice = (priceLow + priceHigh) / 2;
        require(averagePrice > 0, "Invalid dual oracle price");

        // The dual oracle exposes no round ids or publication time, so these fields are synthesized.
        return (uint80(block.number), int256(averagePrice), block.timestamp, block.timestamp, uint80(block.number));
    }
}


================================================
FILE: src/MYTStrategy.sol
================================================
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IMYTStrategy} from "./interfaces/IMYTStrategy.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "./libraries/SafeERC20.sol";
import {TokenUtils} from "./libraries/TokenUtils.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

interface IERC721Tiny {
    function ownerOf(uint256 tokenId) external view returns (address);
}

/**
 * @title MYTStrategy
 * @notice The MYT is a Morpho V2 Vault, and each strategy is just a vault adapter which interfaces with a third party protocol
 * @notice This contract should be inherited by all strategies
 */
contract MYTStrategy is IMYTStrategy, Ownable {
    using Math for uint256;

    IVaultV2 public immutable MYT;
   // address public immutable receiptToken;
    address public allowanceHolder; // 0x Allowance holder
    uint256 public constant SECONDS_PER_YEAR = 365 days;
    uint256 public constant FIXED_POINT_SCALAR = 1e18;
    bytes4 public constant FORCE_DEALLOCATE_SELECTOR = 0xe4d38cd8;
    
    IMYTStrategy.StrategyParams public params;
    bytes32 public immutable adapterId;
    
    /// @notice This value is true when the underlying protocol is known to
    /// experience issues or security incidents. In this case the allocation step is simply
    /// bypassed without reverts (to keep external allocators from reverting).
    bool public killSwitch;

    modifier onlyVault() {
        require(msg.sender == address(MYT), "PD");
        _;
    }

    /* ========== CONSTRUCTOR ========== */

    /**
     * @notice Constructor for the MYTStrategy contract
     * @param _myt The address of the MYT vault
     * @param _params The parameters for the strategy
     */
    constructor(address _myt, StrategyParams memory _params) Ownable(_params.owner) {
        require(_params.owner != address(0));
        require(_myt != address(0));
        require(_params.slippageBPS < 5000);
        MYT = IVaultV2(_myt);
        params = _params;
        adapterId = keccak256(abi.encode("this", address(this)));
        allowanceHolder = 0x0000000000001fF3684f28c67538d4D072C22734;
    }

    /* ========== CORE ADAPTER FUNCTIONS ========== */

    /// @notice See Morpho V2 vault spec
    function allocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
        external
        onlyVault
        returns (bytes32[] memory strategyIds, int256 change)
    {
        if (killSwitch) revert StrategyAllocationPaused(address(this));
        if (assets == 0) revert InvalidAmount(1, 0);

        VaultAdapterParams memory adapterParams = abi.decode(data, (VaultAdapterParams));
        uint256 amountAllocated;

        if (adapterParams.action == ActionType.direct) {
            amountAllocated = _allocate(assets);
        } else if (adapterParams.action == ActionType.swap) {
            amountAllocated = _allocate(assets, adapterParams.swapParams.txData);
        } else {
            revert ActionNotSupported();
        }
        
        uint256 oldAllocation = allocation();
        uint256 newAllocation = _totalValue();
        emit Allocate(amountAllocated, address(this));
        return (ids(), int256(newAllocation) - int256(oldAllocation));
    }

    /// @notice See Morpho V2 vault spec
    function deallocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
        external
        onlyVault
        returns (bytes32[] memory strategyIds, int256 change)
    {
        if (assets == 0) revert InvalidAmount(1, 0);

        uint256 oldAllocation = allocation();
        uint256 totalValueBefore = _totalValue();

        VaultAdapterParams memory adapterParams = abi.decode(data, (VaultAdapterParams));
        _validateDeallocateAction(adapterParams.action, selector);
        uint256 amountDeallocated;

        if (adapterParams.action == ActionType.direct) {
            amountDeallocated = _deallocate(assets);
        } else if (adapterParams.action == ActionType.swap) {
            amountDeallocated = _deallocate(assets, adapterParams.swapParams.txData);
        } else if (adapterParams.action == ActionType.unwrapAndSwap) {
            amountDeallocated = _deallocate(assets, adapterParams.swapParams.txData, adapterParams.swapParams.minIntermediateOut);
        } else {
            revert ActionNotSupported();
        }

        uint256 totalValueAfter = _totalValue();
        require(totalValueAfter >= assets, "inconsistent totalValue");
        uint256 newAllocation = totalValueAfter - assets;
        emit Deallocate(amountDeallocated, address(this));
        return (ids(), int256(newAllocation) - int256(oldAllocation));
    }

    /* ========== INTERNAL HELPER FUNCTIONS ========== */

    /// @dev Helper to swap tokens via 0x
    function dexSwap(address to, address from, uint256 amount, uint256 minAmountOut, bytes memory callData) internal returns (uint256) {
        SafeERC20.safeApprove(from, allowanceHolder, amount);
        uint256 targetBalanceBefore = IERC20(to).balanceOf(address(this));
        (bool success, ) = allowanceHolder.call(callData);
        if (!success) revert CounterfeitSettler(allowanceHolder);
        SafeERC20.safeApprove(from, allowanceHolder, 0);
        
        uint256 amountReceived = IERC20(to).balanceOf(address(this)) - targetBalanceBefore;
        if (amountReceived < minAmountOut) revert InvalidAmount(minAmountOut, amountReceived);
        return amountReceived;
    }

    /// @dev Helper to check if strategy holds enough idle assets
    function _ensureIdleBalance(address asset, uint256 amount) internal view {
        uint256 balance = TokenUtils.safeBalanceOf(asset, address(this));
        if (balance < amount) revert InsufficientBalance(amount, balance);
    }

    /// @dev Force deallocations are limited to direct withdrawals.
    function _validateDeallocateAction(ActionType action, bytes4 selector) internal pure {
        if (selector == FORCE_DEALLOCATE_SELECTOR && action != ActionType.direct) {
            revert ForceDeallocateSwapNotAllowed();
        }
    }

    /* ========== VIEW FUNCTIONS ========== */

    /// @notice helper function to estimate the correct amount that can be fully
    /// withdrawn from a strategy, accounting for losses
    /// due to slippage, protocol fees, and rounding differences
    function previewAdjustedWithdraw(uint256 amount) external view returns (uint256) {
        if (amount == 0) revert InvalidAmount(1, 0);
        return _previewAdjustedWithdraw(amount);
    }

    /// @notice call this function to handle strategies with withdrawal queue NFT
    function claimWithdrawalQueue(uint256 positionId) public virtual onlyOwner returns (uint256 ret) {
        return _claimWithdrawalQueue(positionId);
    }

    /// @notice call this function to claim all available rewards from the respective
    /// protocol of this strategy
    function claimRewards(address token, bytes memory quote, uint256 minAmountOut) public onlyOwner virtual returns (uint256) {
        require(!killSwitch, "emergency");
        return _claimRewards(token, quote, minAmountOut);
    }

    /// @notice withdraw any leftover assets back to the vault
    function withdrawToVault() public virtual onlyOwner returns (uint256) {
        uint256 leftover = IERC20(MYT.asset()).balanceOf(address(this));
        SafeERC20.safeTransfer(MYT.asset(), address(MYT), leftover);
        emit WithdrawToVault(leftover);
        return leftover;
    }

    /// @notice Rescue arbitrary ERC20 tokens sent to this contract by mistake
    /// @param token The token to rescue
    /// @param to The recipient address
    /// @param amount The amount to rescue
    function rescueTokens(address token, address to, uint256 amount) external onlyOwner {
        require(to != address(0), "Invalid recipient");
        require(!_isProtectedToken(token), "Protected token");
        uint256 balance = IERC20(token).balanceOf(address(this));
        require(amount <= balance, "Insufficient balance");
        IERC20(token).transfer(to, amount);
        emit TokensRescued(token, to, amount);
    }

    /* ========== ADMIN FUNCTIONS ========== */

    /// @notice recategorize this strategy to a different risk class
    function setRiskClass(RiskClass newClass) public onlyOwner {
        params.riskClass = newClass;
        emit RiskClassUpdated(newClass);
    }

    /// @dev some protocols may pay yield in baby tokens
    /// so we need to manually collect them
    function setAdditionalIncentives(bool newValue) public onlyOwner {
        params.additionalIncentives = newValue;
        emit IncentivesUpdated(newValue);
    }

    /// @notice enter/exit emergency mode for this strategy
    function setKillSwitch(bool val) public onlyOwner {
        killSwitch = val;
        emit Emergency(val);
    }

    function setAllowanceHolder(address _new) public onlyOwner {
        require(_new != address(0));
        allowanceHolder = _new;
    }

    /// @notice Update the slippage tolerance for this strategy
    function setSlippageBPS(uint256 newSlippageBPS) public onlyOwner {
        require(newSlippageBPS < 9999, "Slippage too high");
        params.slippageBPS = newSlippageBPS;
        emit SlippageBPSUpdated(newSlippageBPS);
    }

    /* ========== GETTERS ========== */

    /// @notice get the current snapshotted estimated yield for this strategy.
    /// This call does not guarantee the latest up-to-date yield and there might
    /// be discrepancies from the respective protocols numbers.
    function getEstimatedYield() public view returns (uint256) {
        return params.estimatedYield;
    }

    function getCap() external view returns (uint256) {
        return params.cap;
    }

    function getGlobalCap() external view returns (uint256) {
        return params.globalCap;
    }

    function ids() public view returns (bytes32[] memory) {
        bytes32[] memory ids_ = new bytes32[](1);
        ids_[0] = adapterId;
        return ids_;
    }

    /// @notice Returns the vault's current allocation tracking for this adapter
    function allocation() public view returns (uint256) {
        return MYT.allocation(adapterId);
    }

    function getIdData() external view returns (bytes memory) {
        return abi.encode("this", address(this));
    }

    /// @notice External function per IAdapter spec - returns total underlying value
    function realAssets() external view virtual returns (uint256) {
        return _totalValue();
    }

    /* ========== VIRTUAL FUNCTIONS ========== */

    /// @dev Check if a token is protected and cannot be rescued.
    /// Override this function in child contracts to add protocol-specific protected tokens
    /// (e.g., receipt tokens, aTokens, mTokens, staking tokens).
    /// @param token The token to check
    /// @return True if the token is protected
    function _isProtectedToken(address token) internal view virtual returns (bool) {
        return token == MYT.asset();
    }

    /// @dev override this function to handle wrapping/allocation/moving funds to
    /// the respective protocol of this strategy
    /// @notice uint56 amount returned should be equal to the amount parameter passed in
    /// @notice should attempt to log any loss due to rounding
    function _allocate(uint256 amount) internal virtual returns (uint256) {
        revert ActionNotSupported();
    }

    /// @dev override this function to handle wrapping/allocation/moving funds to
    /// the respective protocol of this strategy
    /// @notice uint56 amount returned should be equal to the amount parameter passed in
    /// @notice should attempt to log any loss due to rounding
    function _allocate(uint256 amount, bytes memory callData) internal virtual returns (uint256) {
        revert ActionNotSupported();
    }

    /// @dev override this function to handle unwrapping/deallocation/moving funds from
    /// the respective protocol of this strategy
    /// @notice uint56 amount returned must be equal to the amount parameter passed in
    /// @notice due to how MorphoVaultV2 internally handles deallocations,
    /// strategies must have atleast >= amount available at the end of this function call
    /// if not, the strategy will revert
    /// @notice amount of asset must be approved to the vault (i.e. msg.sender)
    function _deallocate(uint256 amount) internal virtual returns (uint256) {
        revert ActionNotSupported();
    }

    /// @dev override this function to handle unwrapping/deallocation/moving funds from
    /// the respective protocol of this strategy
    /// @notice uint56 amount returned must be equal to the amount parameter passed in
    /// @notice due to how MorphoVaultV2 internally handles deallocations,
    /// strategies must have atleast >= amount available at the end of this function call
    /// if not, the strategy will revert
    /// @notice amount of asset must be approved to the vault (i.e. msg.sender)
    function _deallocate(uint256 amount, bytes memory callData) internal virtual returns (uint256) {
        revert ActionNotSupported();
    }

    /// @dev override this function to handle unwrapping/deallocation/moving funds from
    /// the strategy to the vault using a swap with specified in calldata 
    /// @param amount the WETH amount expected to be returned to the vault
    /// @param callData the 0x swap calldata
    /// @param minIntermediateOutAmount the minimum amount of intermediate token expected to be received from the unwrap
    function _deallocate(uint256 amount, bytes memory callData, uint256 minIntermediateOutAmount) internal virtual returns (uint256) {
        revert ActionNotSupported();
    }

    /// @dev override this function to handle preview withdraw with slippage
    /// @notice this function should be used to estimate the correct amount that can be fully withdrawn, accounting for losses
    /// due to slippage, protocol fees, and rounding differences
    function _previewAdjustedWithdraw(uint256 amount) internal view virtual returns (uint256) {}

    /// @dev override this function to handle strategies with withdrawal queue NFT
    function _claimWithdrawalQueue(uint256 positionId) internal virtual returns (uint256) {}

    /// @dev override this function to claim all available rewards from the respective
    /// protocol of this strategy in the form of a specific token
    /// this ERC20 reward must then be converted to the MYT's asset
    function _claimRewards(address token, bytes memory quote, uint256 minAmountOut) internal virtual returns (uint256) {}

    /// @dev override this function to return the total underlying value of the strategy
    /// @dev must return the total underling value of the strategy's position (i.e. in vault asset e.g. USDC or WETH)
    function _totalValue() internal view virtual returns (uint256) {}

    function _idleAssets() internal view virtual returns (uint256) {}
}


================================================
FILE: src/PerpetualGauge.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IStrategyClassifier } from "./interfaces/IStrategyClassifier.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IAllocatorProxy {
    function allocate(uint256 strategyId, uint256 amount) external;
}

contract PerpetualGauge is ReentrancyGuard {
    event VoteUpdated(address indexed voter, uint256 ytId, uint256[] strategyIds, uint256[] weights, uint256 expiry);
    event AllocationExecuted(uint256 ytId, uint256[] strategyIds, uint256[] amounts);
    event VoterCleared(address indexed voter, uint256 ytId);

    struct Vote {
        uint256[] strategyIds;
        uint256[] weights;
        uint256 expiry;
    }

    IStrategyClassifier public stratClassifier;
    IAllocatorProxy public allocatorProxy;
    IERC20 public votingToken;

    uint256 public constant MAX_VOTE_DURATION = 365 days;
    uint256 public constant MIN_RESET_DURATION = 30 days;

    mapping(uint256 => mapping(address => Vote)) public votes;
    mapping(uint256 => uint256) public lastStrategyAddedAt;

    mapping(uint256 => address[]) private voters;
    mapping(uint256 => mapping(address => uint256)) private voterIndex;

    // Aggregate weighted votes per MYT + strategy
    mapping(uint256 => mapping(uint256 => uint256)) private aggStrategyWeight;

    constructor(address _stratClassifier, address _allocatorProxy, address _votingToken) {
        require(_stratClassifier != address(0) && _allocatorProxy != address(0) && _votingToken != address(0), "Bad address");
        stratClassifier = IStrategyClassifier(_stratClassifier);
        allocatorProxy = IAllocatorProxy(_allocatorProxy);
        votingToken = IERC20(_votingToken);
    }

    function vote(uint256 ytId, uint256[] calldata strategyIds, uint256[] calldata weights) external nonReentrant {
        require(strategyIds.length == weights.length && strategyIds.length > 0, "Invalid input");

        uint256 lastAdded = lastStrategyAddedAt[ytId];
        Vote storage existing = votes[ytId][msg.sender];
        uint256 expiry;

        if (existing.expiry > block.timestamp) {
            uint256 timeLeft = existing.expiry - block.timestamp;
            if (lastAdded > 0 && block.timestamp - lastAdded < MIN_RESET_DURATION && timeLeft < MIN_RESET_DURATION) {
                expiry = existing.expiry;
            } else {
                expiry = block.timestamp + MAX_VOTE_DURATION;
            }
        } else {
            expiry = block.timestamp + MAX_VOTE_DURATION;
        }

        uint256 power = votingToken.balanceOf(msg.sender);

        // 1. Remove old vote contribution from aggregate
        if (existing.strategyIds.length > 0 && existing.expiry > block.timestamp) {
            for (uint256 i = 0; i < existing.strategyIds.length; i++) {
                uint256 sid = existing.strategyIds[i];
                uint256 prevWeighted = existing.weights[i] * power;
                aggStrategyWeight[ytId][sid] -= prevWeighted;
            }
        }

        // 2. Store new vote
        votes[ytId][msg.sender] = Vote({ strategyIds: strategyIds, weights: weights, expiry: expiry });

        // 3. Add new contribution
        for (uint256 i = 0; i < strategyIds.length; i++) {
            uint256 sid = strategyIds[i];
            uint256 newWeighted = weights[i] * power;
            aggStrategyWeight[ytId][sid] += newWeighted;
        }

        // 4. Track voter in registry
        if (voterIndex[ytId][msg.sender] == 0) {
            voters[ytId].push(msg.sender);
            voterIndex[ytId][msg.sender] = voters[ytId].length; // 1-based
        }

        emit VoteUpdated(msg.sender, ytId, strategyIds, weights, expiry);
    }

    function clearVote(uint256 ytId) external nonReentrant {
        Vote storage v = votes[ytId][msg.sender];
        require(v.strategyIds.length > 0, "No vote");

        uint256 power = votingToken.balanceOf(msg.sender);

        for (uint256 i = 0; i < v.strategyIds.length; i++) {
            uint256 sid = v.strategyIds[i];
            uint256 weighted = v.weights[i] * power;
            aggStrategyWeight[ytId][sid] -= weighted;
        }

        delete votes[ytId][msg.sender];
        emit VoterCleared(msg.sender, ytId);
    }

    function registerNewStrategy(uint256 ytId, uint256 strategyId) external nonReentrant {
        lastStrategyAddedAt[ytId] = block.timestamp;
        // TODO
    }

    function getCurrentAllocations(uint256 ytId) public view
        returns (uint256[] memory strategyIds, uint256[] memory normalizedWeights)
    {
        uint256 n = strategyList[ytId].length;
        strategyIds = new uint256[](n);
        normalizedWeights = new uint256[](n);

        uint256 total;
        for (uint256 i; i < n; i++) {
            uint256 sid = strategyList[ytId][i];
            strategyIds[i] = sid;
            uint256 w = aggStrategyWeight[ytId][sid];
            normalizedWeights[i] = w;
            total += w;
        }

        for (uint256 i; i < n; i++) {
            if (total > 0) {
                normalizedWeights[i] = (normalizedWeights[i] * 1e18) / total;
            }
        }
    }

    function executeAllocation(uint256 ytId, uint256 totalIdleAssets) external nonReentrant {
        (uint256[] memory sIds, uint256[] memory weights) = getCurrentAllocations(ytId);
        require(sIds.length > 0, "No allocations");

        uint256 totalRiskAllocated;
        uint256[] memory allocatedAmounts = new uint256[](sIds.length);

        for (uint256 i = 0; i < sIds.length; i++) {
            uint8 risk = stratClassifier.getStrategyRiskLevel(sIds[i]);
            uint256 indivCap = stratClassifier.getIndividualCap(sIds[i]);
            uint256 globalCap = stratClassifier.getGlobalCap(risk);

            uint256 target = (weights[i] * totalIdleAssets) / 1e18;

            // Individual cap
            uint256 capIndiv = (indivCap * totalIdleAssets) / 1e4;
            if (target > capIndiv) target = capIndiv;

            // Global cap for risk group
            if (risk > 0) {
                uint256 capGlobalLeft = (globalCap * totalIdleAssets) / 1e4 - totalRiskAllocated;
                if (target > capGlobalLeft) target = capGlobalLeft;
                totalRiskAllocated += target;
            }

            if (target > 0) {
                // TODO double-check limits here?
                allocatorProxy.allocate(sIds[i], target);
            }

            allocatedAmounts[i] = target;
        }

        emit AllocationExecuted(ytId, sIds, allocatedAmounts);
    }

    // to keep track of strategies per ytId for getCurrentAllocations
    mapping(uint256 => uint256[]) public strategyList;
}


================================================
FILE: src/Transmuter.sol
================================================
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

import {IAlchemistV3} from "./interfaces/IAlchemistV3.sol";
import {ITransmuter} from "./interfaces/ITransmuter.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import {NFTMetadataGenerator} from "./libraries/NFTMetadataGenerator.sol";
import {SafeCast} from "./libraries/SafeCast.sol";
import {StakingGraph} from "./libraries/StakingGraph.sol";
import {TokenUtils} from "./libraries/TokenUtils.sol";
import {FixedPointMath} from "./libraries/FixedPointMath.sol";

import {Unauthorized, IllegalArgument, IllegalState} from "./base/Errors.sol";
import "./base/TransmuterErrors.sol";

/// @title AlchemixV3 Transmuter
///
/// @notice A contract which facilitates the exchange of alAssets to yield bearing assets.
contract Transmuter is ITransmuter, ERC721Enumerable {
    using StakingGraph for StakingGraph.Graph;
    using SafeCast for int256;
    using SafeCast for uint256;

    uint256 public constant BPS = 10_000;
    uint256 public constant FIXED_POINT_SCALAR = 1e18;
    int256 public constant BLOCK_SCALING_FACTOR = 1e8;
    
    /// @inheritdoc ITransmuter
    string public constant version = "3.0.0";

    /// @inheritdoc ITransmuter
    uint256 public depositCap;

    /// @inheritdoc ITransmuter
    uint256 public exitFee;

    /// @inheritdoc ITransmuter
    uint256 public transmutationFee;

    /// @inheritdoc ITransmuter
    uint256 public timeToTransmute;

    /// @inheritdoc ITransmuter
    uint256 public totalLocked;

    /// @inheritdoc ITransmuter
    uint256 public totalActiveLocked; 

    /// @inheritdoc ITransmuter
    address public admin;

    /// @inheritdoc ITransmuter
    address public pendingAdmin;

    /// @inheritdoc ITransmuter
    address public protocolFeeReceiver;

    /// @inheritdoc ITransmuter
    address public syntheticToken;

    /// @inheritdoc ITransmuter
    IAlchemistV3 public alchemist;

    /// @dev Map of user positions data.
    mapping(uint256 => StakingPosition) private _positions;

    /// @dev Map of users whos position counts towards deposit cap.
    mapping(uint256 => bool) private _countsTowardCap;

    /// @dev Graph of transmuter positions.
    StakingGraph.Graph private _stakingGraph;

    /// @dev Nonce data used for minting of new nft positions.
    uint256 private _nonce;

    modifier onlyAdmin() {
        _checkArgument(msg.sender == admin);
        _;
    }

    constructor(ITransmuter.TransmuterInitializationParams memory params) ERC721("Alchemix V3 Transmuter", "TRNSMTR") {
        _checkArgument(params.feeReceiver != address(0));
        _checkArgument(params.timeToTransmute != 0);
        _checkArgument(params.timeToTransmute <= type(int256).max.toUint256());
        _checkArgument(params.transmutationFee <= BPS);
        _checkArgument(params.exitFee <= BPS);

        syntheticToken = params.syntheticToken;
        timeToTransmute = params.timeToTransmute;
        transmutationFee = params.transmutationFee;
        exitFee = params.exitFee;
        protocolFeeReceiver = params.feeReceiver;
        admin = msg.sender;
    }

    /// @inheritdoc ITransmuter
    function setPendingAdmin(address value) external onlyAdmin {
        pendingAdmin = value;

        emit PendingAdminUpdated(value);
    }

    /// @inheritdoc ITransmuter
    function acceptAdmin() external {
        _checkState(pendingAdmin != address(0));

        if (msg.sender != pendingAdmin) {
            revert Unauthorized();
        }

        admin = pendingAdmin;
        pendingAdmin = address(0);

        emit AdminUpdated(admin);
        emit PendingAdminUpdated(address(0));
    }

    /// @inheritdoc ITransmuter
    function setAlchemist(address value) external onlyAdmin {
        alchemist = IAlchemistV3(value);

        emit AlchemistUpdated(value);
    }

    /// @inheritdoc ITransmuter
    function setDepositCap(uint256 cap) external onlyAdmin {
        _checkArgument(cap <= type(int256).max.toUint256());

        depositCap = cap;
        emit DepositCapUpdated(cap);
    }

    /// @inheritdoc ITransmuter
    function setTransmutationFee(uint256 fee) external onlyAdmin {
        _checkArgument(fee <= BPS);

        transmutationFee = fee;
        emit TransmutationFeeUpdated(fee);
    }

    /// @inheritdoc ITransmuter
    function setExitFee(uint256 fee) external onlyAdmin {
        _checkArgument(fee <= BPS);

        exitFee = fee;
        emit ExitFeeUpdated(fee);
    }

    /// @inheritdoc ITransmuter
    function setTransmutationTime(uint256 time) external onlyAdmin {
        _checkArgument(time != 0);
        _checkArgument(time <= type(int256).max.toUint256());
        timeToTransmute = time;

        emit TransmutationTimeUpdated(time);
    }

    /// @inheritdoc ITransmuter
    function setProtocolFeeReceiver(address value) external onlyAdmin {
        _checkArgument(value != address(0));
        protocolFeeReceiver = value;
        emit ProtocolFeeReceiverUpdated(value);
    }

    function tokenURI(uint256 id) public view override returns (string memory) {
        return NFTMetadataGenerator.generateTokenURI(id, "Transmuter V3 Position");
    }

    /// @inheritdoc ITransmuter
    function getPosition(uint256 id) external view returns (StakingPosition memory) {
        return _positions[id];
    }

    /// @inheritdoc ITransmuter
    function createRedemption(uint256 syntheticDepositAmount, address recipient) external {
        if (syntheticDepositAmount == 0) {
            revert DepositZeroAmount();
        }

        _checkArgument(recipient != address(0));

        if (totalActiveLocked + syntheticDepositAmount > depositCap) {
            revert DepositCapReached();
        }

        if (totalLocked + syntheticDepositAmount > alchemist.totalSyntheticsIssued()) {
            revert DepositCapReached();
        }

        TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), syntheticDepositAmount);

        _positions[++_nonce] = StakingPosition(syntheticDepositAmount, block.number, block.number + timeToTransmute);

        // Update Fenwick Tree
        _updateStakingGraph(syntheticDepositAmount.toInt256() * BLOCK_SCALING_FACTOR / timeToTransmute.toInt256(), timeToTransmute);

        totalLocked += syntheticDepositAmount;

        totalActiveLocked += syntheticDepositAmount;
        _countsTowardCap[_nonce] = true;

        _mint(recipient, _nonce);

        emit PositionCreated(msg.sender, syntheticDepositAmount, _nonce);
    }

    /// @inheritdoc ITransmuter
    function claimRedemption(uint256 id) external returns (uint256 claimYield, uint256 feeYield, uint256 syntheticReturned, uint256 syntheticFee) {
        StakingPosition storage position = _positions[id];

        if (position.maturationBlock == 0) {
            revert PositionNotFound();
        }

        if (position.startBlock == block.number) {
            revert PrematureClaim();
        }

        uint256 transmutationTime = position.maturationBlock - position.startBlock;
        uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
        uint256 amountNottransmuted = blocksLeft > 0 ? FixedPointMath.mulDivUp(position.amount, blocksLeft, transmutationTime) : 0;
        uint256 amountTransmuted = position.amount - amountNottransmuted;

        if (_requireOwned(id) != msg.sender) {
            revert CallerNotOwner();
        }

        // Burn position NFT
        _burn(id);
        
        // Ratio of total synthetics issued by the alchemist / underlingying value of collateral stored in the alchemist
        // If the system experiences bad debt we use this ratio to scale back the value of yield tokens that are transmuted
        address myt = alchemist.myt();
        uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(myt, address(this));

        // Avoid divide by 0
        uint256 backingUnderlying = alchemist.getTotalLockedUnderlyingValue() + alchemist.convertYieldTokensToUnderlying(yieldTokenBalance);
        uint256 denominator = backingUnderlying > 0 ? backingUnderlying : 1;

        // Round up so badDebtRatio is never understated.
        // Understating badDebtRatio makes scaledTransmuted slightly too large, which can cause alchemist.redeem(amountToRedeem) to revert due to share rounding.
        uint256 badDebtRatio = FixedPointMath.mulDivUp(alchemist.totalSyntheticsIssued(), 10 ** TokenUtils.expectDecimals(alchemist.underlyingToken()), denominator);

        uint256 scaledTransmuted = amountTransmuted;

        if (badDebtRatio > 1e18) {
            scaledTransmuted = amountTransmuted * FIXED_POINT_SCALAR / badDebtRatio;
        }

        // If the contract has a balance of yield tokens from alchemist repayments then we only need to redeem partial or none from Alchemist earmarked
        uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
        uint256 amountToRedeem = scaledTransmuted > debtValue ? scaledTransmuted - debtValue : 0;

        uint256 redeemedShares = 0;
        if (amountToRedeem > 0) {
            redeemedShares = alchemist.redeem(amountToRedeem);
        }

        uint256 sharesAvailable = yieldTokenBalance + redeemedShares;
        uint256 totalYield = alchemist.convertDebtTokensToYield(scaledTransmuted);
        uint256 distributable = totalYield <= sharesAvailable ? totalYield : sharesAvailable;

        // Split distributable amount. Round fee down. claimant gets the remainder.
        feeYield = distributable * transmutationFee / BPS;
        claimYield = distributable - feeYield;

        uint256 debtPaid = alchemist.convertYieldTokensToDebt(distributable);
        if (debtPaid > scaledTransmuted) {
            debtPaid = scaledTransmuted;
        }

        // Shortfall debt to offset if some amount of MYT cannot be paid to the user
        // We will return some synthetics instead of burning them all if this is the case
        uint256 shortfallDebt = scaledTransmuted > debtPaid ? scaledTransmuted - debtPaid : 0;

        syntheticFee = amountNottransmuted * exitFee / BPS;
        syntheticReturned = (amountNottransmuted - syntheticFee) + shortfallDebt;

        uint256 burnAmountDebt = position.amount - (syntheticReturned + syntheticFee);

        // Remove untransmuted amount from the staking graph
        if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);

        TokenUtils.safeTransfer(myt, msg.sender, claimYield);
        TokenUtils.safeTransfer(myt, protocolFeeReceiver, feeYield);

        TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
        TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);

        if (burnAmountDebt > 0) {
            TokenUtils.safeBurn(syntheticToken, burnAmountDebt);
            alchemist.reduceSyntheticsIssued(burnAmountDebt);
        }

        alchemist.setTransmuterTokenBalance(TokenUtils.safeBalanceOf(myt, address(this)));

        // If this position still counted toward cap, remove it now.
        if (_countsTowardCap[id]) {
            _countsTowardCap[id] = false;
            totalActiveLocked -= position.amount;
        }

        totalLocked -= position.amount;

        emit PositionClaimed(msg.sender, claimYield, syntheticReturned);

        delete _positions[id];
    }

    /// @inheritdoc ITransmuter
    function queryGraph(uint256 startBlock, uint256 endBlock) external view returns (uint256) {
        if (endBlock < startBlock) return 0;

        int256 queried = _stakingGraph.queryStake(startBlock, endBlock);
        if (queried == 0) return 0;

        return FixedPointMath.mulDivUp(queried.toUint256(), 1, uint256(BLOCK_SCALING_FACTOR));
    }

    /// @inheritdoc ITransmuter
    function pokeMatured(uint256 id) external {
        StakingPosition storage position = _positions[id];

        if (position.maturationBlock == 0) revert PositionNotFound();

        // must be fully matured
        if (block.number < position.maturationBlock) {
            revert PositionNotMatured(id, position.maturationBlock, block.number);
        }

        if (!_countsTowardCap[id]) revert PositionAlreadyPoked(id);

        _countsTowardCap[id] = false;
        totalActiveLocked -= position.amount;

        emit PositionPoked(id, position.amount);
    }

    /// @dev Updates staking graphs
    function _updateStakingGraph(int256 amount, uint256 blocks) private {
        _stakingGraph.addStake(amount, block.number, blocks);
    }

    /// @dev Checks an expression and reverts with an {IllegalArgument} error if the expression is {false}.
    ///
    /// @param expression The expression to check.
    function _checkArgument(bool expression) internal pure {
        if (!expression) {
            revert IllegalArgument();
        }
    }

    /// @dev Checks an expression and reverts with an {IllegalState} error if the expression is {false}.
    ///
    /// @param expression The expression to check.
    function _checkState(bool expression) internal pure {
        if (!expression) {
            revert IllegalState();
        }
    }
}


================================================
FILE: src/adapters/AbstractFeeVault.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import "@openzeppelin/contracts/access/Ownable.sol";
import {IFeeVault} from "../interfaces/IFeeVault.sol";

/**
 * @title AbstractVault
 * @notice Abstract base class for Alchemist vaults that handles authorization logic
 * @dev Extend this to implement ETH or ERC20 token vaults
 */
abstract contract AbstractFeeVault is IFeeVault, Ownable {
    address public immutable token;
    // Custom errors

    error Unauthorized();
    error ZeroAddress();
    error ZeroAmount();
    error InsufficientBalance();

    // Mapping of addresses authorized to withdraw
    mapping(address => bool) public authorized;

    // Events
    event Deposited(address indexed from, uint256 amount);
    event Withdrawn(address indexed to, uint256 amount);
    event AuthorizationUpdated(address indexed account, bool status);

    /**
     * @dev Modifier to restrict access to authorized accounts
     */
    modifier onlyAuthorized() {
        if (!authorized[msg.sender]) revert Unauthorized();
        _;
    }

    /**
     * @notice Constructor to initialize the vault
     * @param _token The ERC20 token managed by this vault
     * @param _alchemist The Alchemist contract address
     * @param _owner The vault owner address
     */
    constructor(address _token, address _alchemist, address _owner) Ownable(_owner) {
        _checkNonZeroAddress(_token);
        _checkNonZeroAddress(_alchemist);
        token = _token;
        authorized[_alchemist] = true;
        authorized[_owner] = true;
        emit AuthorizationUpdated(_alchemist, true);
    }

    /**
     * @notice Sets the authorization status of an account
     * @param account The address to authorize/deauthorize
     * @param status True to authorize, false to deauthorize
     */
    function setAuthorization(address account, bool status) external onlyOwner {
        _checkNonZeroAddress(account);
        authorized[account] = status;
        emit AuthorizationUpdated(account, status);
    }

    /**
     * @notice Validates that an address is not the zero address
     * @param account The address to validate
     */
    function _checkNonZeroAddress(address account) internal pure {
        if (account == address(0)) revert ZeroAddress();
    }

    /**
     * @notice Validates that an amount is greater than zero
     * @param amount The amount to validate
     */
    function _checkNonZeroAmount(uint256 amount) internal pure {
        if (amount == 0) revert ZeroAmount();
    }

    /**
     * @notice Abstract function to withdraw assets from the vault
     * @param recipient Address to receive the assets
     * @param amount Amount to withdraw
     */
    function withdraw(address recipient, uint256 amount) external virtual override;

    /**
     * @notice Abstract function to get total deposits in the vault
     * @return Total deposits in the vault
     */
    function totalDeposits() external view virtual override returns (uint256);
}


================================================
FILE: src/adapters/EulerUSDCAdapter.sol
================================================
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

import "../libraries/TokenUtils.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import '../interfaces/ITokenAdapter.sol';
import "@openzeppelin/contracts/interfaces/IERC4626.sol";

/// @title Euler Adapter
contract EulerUSDCAdapter is ITokenAdapter {
    string public constant version = "1.0.0";

    address public immutable token;
    
    address public immutable underlyingToken;

    constructor(address _token, address _underlyingToken) {
        token = _token;
        underlyingToken = _underlyingToken;
    }

    function price() external view returns (uint256) {
        return IERC4626(token).convertToAssets(10**TokenUtils.expectDecimals(token));
    }
}

================================================
FILE: src/base/ErrorMessages.sol
================================================
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.4;

/// @notice An error used to indicate that an argument passed to a function is illegal or
///         inappropriate.
///
/// @param message The error message.
error IllegalArgument(string message);

/// @notice An error used to indicate that a function has encountered an unrecoverable state.
///
/// @param message The error message.
error IllegalState(string message);

/// @notice An error used to indicate that an operation is unsupported.
///
/// @param message The error message.
error UnsupportedOperation(string message);

/// @notice An error used to indicate that a message sender tried to execute a privileged function.
///
/// @param message The error message.
error Unauthorized(string message);


================================================
FILE: src/base/Errors.sol
================================================
pragma solidity ^0.8.23;

/// @notice An error used to indicate that an action could not be completed because either the `msg.sender` or
///         `msg.origin` is not authorized.
error Unauthorized();

/// @notice An error used to indicate that an action could not be completed because the contract either already existed
///         or entered an illegal condition which is not recoverable from.
error IllegalState();

/// @notice An error used to indicate that an action could not be completed because of an illegal argument was passed
///         to the function.
error IllegalArgument();

/// @notice An error used to indicate that an action could not be completed because the required amount of allowance has not
///         been approved.
error InsufficientAllowance();

/// @notice An error used to indicate that the function input data is missing
error MissingInputData();

/// @notice An error used to indicate that the function input data is missing
error ZeroAmount();

/// @notice An error used to indicate that the function input data is missing
error ZeroAddress();


================================================
FILE: src/base/TransmuterErrors.sol
================================================
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.28;

error NotRegisteredAlchemist();

error AlchemistDuplicateEntry();

error DepositCapReached();

error DepositZeroAmount();

error PositionNotFound();

error PrematureClaim();

error DepositTooLarge();

error CallerNotOwner();

error PositionNotMatured(uint256 id, uint256 maturationBlock, uint256 currentBlock);

error PositionAlreadyPoked(uint256 id);

================================================
FILE: src/external/AlEth.sol
================================================
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.28;
pragma experimental ABIEncoderV2;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IDetailedERC20} from "./interfaces/IDetailedERC20.sol";

/// @title AlToken
///
/// @dev This is the contract for the Alchemix utillity token usd.
/// @notice This version is modified for Alchemix V3 invariant testing.
///
/// Initially, the contract deployer is given both the admin and minter role. This allows them to pre-mine tokens,
/// transfer admin to a timelock contract, and lastly, grant the staking pools the minter role. After this is done,
/// the deployer must revoke their admin role and minter role.
contract AlEth is ERC20("Alchemix ETH", "alETH") {
    using SafeERC20 for ERC20;

    /// @dev The identifier of the role which maintains other roles.
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN");

    /// @dev The identifier of the role which allows accounts to mint tokens.
    bytes32 public constant SENTINEL_ROLE = keccak256("SENTINEL");

    /// @dev addresses whitelisted for minting new tokens
    mapping(address => bool) public whiteList;

    /// @dev addresses paused for minting new tokens
    mapping(address => bool) public paused;

    /// @dev ceiling per address for minting new tokens
    mapping(address => uint256) public ceiling;

    /// @dev already minted amount per address to track the ceiling
    mapping(address => uint256) public hasMinted;

    event AlchemistPaused(address alchemistAddress, bool isPaused);

    constructor() {}

    /// @dev A modifier which checks if whitelisted for minting.
    modifier onlyWhitelisted() {
        require(whiteList[msg.sender], "AlETH: Alchemist is not whitelisted");
        _;
    }

    /// @dev Mints tokens to a recipient.
    ///
    /// This function reverts if the caller does not have the minter role.
    ///
    /// @param _recipient the account to mint tokens to.
    /// @param _amount    the amount of tokens to mint.
    function mint(address _recipient, uint256 _amount) external onlyWhitelisted {
        require(!paused[msg.sender], "AlETH: Alchemist is currently paused.");
        hasMinted[msg.sender] = hasMinted[msg.sender] + _amount;
        _mint(_recipient, _amount);
    }
    /// This function reverts if the caller does not have the admin role.
    ///
    /// @param _toWhitelist the account to mint tokens to.
    /// @param _state the whitelist state.

    function setWhitelist(address _toWhitelist, bool _state) external {
        whiteList[_toWhitelist] = _state;
    }
    /// This function reverts if the caller does not have the admin role.
    ///
    /// @param _newSentinel the account to set as sentinel.

    /// This function reverts if the caller does not have the sentinel role.
    function pauseAlchemist(address _toPause, bool _state) external {
        paused[_toPause] = _state;
    }
    /// This function reverts if the caller does not have the admin role.
    ///
    /// @param _toSetCeiling the account set the ceiling off.
    /// @param _ceiling the max amount of tokens the account is allowed to mint.

    function setCeiling(address _toSetCeiling, uint256 _ceiling) external {
        ceiling[_toSetCeiling] = _ceiling;
    }

    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 amount) public virtual {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, deducting from the caller's
     * allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `amount`.
     */
    function burnFrom(address account, uint256 amount) public virtual {
        uint256 decreasedAllowance = allowance(account, _msgSender()) - amount;

        _approve(account, _msgSender(), decreasedAllowance);
        _burn(account, amount);
    }

    /**
     * @dev lowers hasminted from the caller's allocation
     *
     */
    function lowerHasMinted(uint256 amount) public onlyWhitelisted {
        if (amount >= hasMinted[msg.sender]) {
            hasMinted[msg.sender] = 0;
        } else {
            hasMinted[msg.sender] = hasMinted[msg.sender] - amount;
        }
    }
}


================================================
FILE: src/external/interfaces/IDetailedERC20.sol
================================================
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IDetailedERC20 is IERC20 {
  function name() external returns (string memory);
  function symbol() external returns (string memory);
  function decimals() external returns (uint8);
}

================================================
FILE: src/external/interfaces/ISettlerActions.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {ISignatureTransfer} from "../../../lib/permit2/src/interfaces/ISignatureTransfer.sol";

interface ISettlerActions {
    /// @dev Transfer funds from msg.sender Permit2.
    function TRANSFER_FROM(address recipient, ISignatureTransfer.PermitTransferFrom memory permit, bytes memory sig)
        external;

    // @dev msgValue is interpreted as an upper bound on the expected msg.value, not as an exact specification
    function NATIVE_CHECK(uint256 deadline, uint256 msgValue) external;

    /// @dev Transfer funds from metatransaction requestor into the Settler contract using Permit2. Only for use in `Settler.executeMetaTxn` where the signature is provided as calldata
    function METATXN_TRANSFER_FROM(address recipient, ISignatureTransfer.PermitTransferFrom memory permit) external;

    /// @dev Settle an RfqOrder between maker and taker transfering funds directly between the parties
    // Post-req: Payout if recipient != taker
    function RFQ_VIP(
        address recipient,
        ISignatureTransfer.PermitTransferFrom memory makerPermit,
        address maker,
        bytes memory makerSig,
        ISignatureTransfer.PermitTransferFrom memory takerPermit,
        bytes memory takerSig
    ) external;

    /// @dev Settle an RfqOrder between maker and taker transfering funds directly between the parties for the entire amount
    function METATXN_RFQ_VIP(
        address recipient,
        ISignatureTransfer.PermitTransferFrom memory makerPermit,
        address maker,
        bytes memory makerSig,
        ISignatureTransfer.PermitTransferFrom memory takerPermit
    ) external;

    /// @dev Settle an RfqOrder between Maker and Settler. Transfering funds from the Settler contract to maker.
    /// Retaining funds in the settler contract.
    // Pre-req: Funded
    // Post-req: Payout
    function RFQ(
        address recipient,
        ISignatureTransfer.PermitTransferFrom memory permit,
        address maker,
        bytes memory makerSig,
        address takerToken,
        uint256 maxTakerAmount
    ) external;

    function UNISWAPV4(
        address recipient,
        address sellToken,
        uint256 bps,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        uint256 amountOutMin
    ) external;
    function UNISWAPV4_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        bytes memory sig,
        uint256 amountOutMin
    ) external;
    function METATXN_UNISWAPV4_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        uint256 amountOutMin
    ) external;

    function BALANCERV3(
        address recipient,
        address sellToken,
        uint256 bps,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        uint256 amountOutMin
    ) external;
    function BALANCERV3_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        bytes memory sig,
        uint256 amountOutMin
    ) external;
    function METATXN_BALANCERV3_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        uint256 amountOutMin
    ) external;

    function PANCAKE_INFINITY(
        address recipient,
        address sellToken,
        uint256 bps,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        uint256 amountOutMin
    ) external;
    function PANCAKE_INFINITY_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        bytes memory sig,
        uint256 amountOutMin
    ) external;
    function METATXN_PANCAKE_INFINITY_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        uint256 amountOutMin
    ) external;

    /// @dev Trades against UniswapV3 using the contracts balance for funding
    // Pre-req: Funded
    // Post-req: Payout
    function UNISWAPV3(address recipient, uint256 bps, bytes memory path, uint256 amountOutMin) external;
    /// @dev Trades against UniswapV3 using user funds via Permit2 for funding
    function UNISWAPV3_VIP(
        address recipient,
        bytes memory path,
        ISignatureTransfer.PermitTransferFrom memory permit,
        bytes memory sig,
        uint256 amountOutMin
    ) external;
    /// @dev Trades against UniswapV3 using user funds via Permit2 for funding. Metatransaction variant. Signature is over all actions.
    function METATXN_UNISWAPV3_VIP(
        address recipient,
        bytes memory path,
        ISignatureTransfer.PermitTransferFrom memory permit,
        uint256 amountOutMin
    ) external;

    function MAKERPSM(address recipient, uint256 bps, bool buyGem, uint256 amountOutMin, address psm, address dai)
        external;

    function CURVE_TRICRYPTO_VIP(
        address recipient,
        uint80 poolInfo,
        ISignatureTransfer.PermitTransferFrom memory permit,
        bytes memory sig,
        uint256 minBuyAmount
    ) external;
    function METATXN_CURVE_TRICRYPTO_VIP(
        address recipient,
        uint80 poolInfo,
        ISignatureTransfer.PermitTransferFrom memory permit,
        uint256 minBuyAmount
    ) external;

    function DODOV1(address sellToken, uint256 bps, address pool, bool quoteForBase, uint256 minBuyAmount) external;
    function DODOV2(
        address recipient,
        address sellToken,
        uint256 bps,
        address pool,
        bool quoteForBase,
        uint256 minBuyAmount
    ) external;

    function VELODROME(address recipient, uint256 bps, address pool, uint24 swapInfo, uint256 minBuyAmount) external;

    /// @dev Trades against MaverickV2 using the contracts balance for funding
    /// This action does not use the MaverickV2 callback, so it takes an arbitrary pool address to make calls against.
    /// Passing `tokenAIn` as a parameter actually saves gas relative to introspecting the pool's `tokenA()` accessor.
    function MAVERICKV2(
        address recipient,
        address sellToken,
        uint256 bps,
        address pool,
        bool tokenAIn,
        uint256 minBuyAmount
    ) external;
    /// @dev Trades against MaverickV2, spending the taker's coupon inside the callback
    /// This action requires the use of the MaverickV2 callback, so we take the MaverickV2 CREATE2 salt as an argument to derive the pool address from the trusted factory and inithash.
    /// @param salt is formed as `keccak256(abi.encode(feeAIn, feeBIn, tickSpacing, lookback, tokenA, tokenB, kinds, address(0)))`
    function MAVERICKV2_VIP(
        address recipient,
        bytes32 salt,
        bool tokenAIn,
        ISignatureTransfer.PermitTransferFrom memory permit,
        bytes memory sig,
        uint256 minBuyAmount
    ) external;
    /// @dev Trades against MaverickV2, spending the taker's coupon inside the callback; metatransaction variant
    function METATXN_MAVERICKV2_VIP(
        address recipient,
        bytes32 salt,
        bool tokenAIn,
        ISignatureTransfer.PermitTransferFrom memory permit,
        uint256 minBuyAmount
    ) external;

    /// @dev Trades against UniswapV2 using the contracts balance for funding
    /// @param swapInfo is encoded as the upper 16 bits as the fee of the pool in bps, the second
    ///                 lowest bit as "sell token has transfer fee", and the lowest bit as the
    ///                 "token0 for token1" flag.
    function UNISWAPV2(
        address recipient,
        address sellToken,
        uint256 bps,
        address pool,
        uint24 swapInfo,
        uint256 amountOutMin
    ) external;

    function POSITIVE_SLIPPAGE(address payable recipient, address token, uint256 expectedAmount, uint256 maxBps)
        external;

    /// @dev Trades against a basic AMM which follows the approval, transferFrom(msg.sender) interaction
    // Pre-req: Funded
    // Post-req: Payout
    function BASIC(address sellToken, uint256 bps, address pool, uint256 offset, bytes calldata data) external;

    function EKUBO(
        address recipient,
        address sellToken,
        uint256 bps,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        uint256 amountOutMin
    ) external;

    function EKUBO_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        bytes memory sig,
        uint256 amountOutMin
    ) external;

    function METATXN_EKUBO_VIP(
        address recipient,
        bool feeOnTransfer,
        uint256 hashMul,
        uint256 hashMod,
        bytes memory fills,
        ISignatureTransfer.PermitTransferFrom memory permit,
        uint256 amountOutMin
    ) external;

    function EULERSWAP(
        address recipient,
        address sellToken,
        uint256 bps,
        address pool,
        bool zeroForOne,
        uint256 amountOutMin
    ) external;
}


================================================
FILE: src/external/interfaces/IVelodromePair.sol
================================================
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;
import {IERC20} from "../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

interface IVelodromePair {
    function metadata()
        external
        view
        returns (
            uint256 basis0,
            uint256 basis1,
            uint256 reserve0,
            uint256 reserve1,
            bool stable,
            IERC20 token0,
            IERC20 token1
        );
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
}


================================================
FILE: src/interfaces/IAlchemicToken.sol
================================================
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.5.0;

import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

/// @title  IAlchemicToken
/// @author Alchemix Finance
interface IAlchemicToken is IERC20 {
  /// @notice Gets the total amount of minted tokens for an account.
  ///
  /// @param account The address of the account.
  ///
  /// @return The total minted.
  function hasMinted(address account) external view returns (uint256);

  /// @notice Lowers the number of tokens which the `msg.sender` has minted.
  ///
  /// This reverts if the `msg.sender` is not whitelisted.
  ///
  /// @param amount The amount to lower the minted amount by.
  function lowerHasMinted(uint256 amount) external;

  /// @notice Sets the mint allowance for a given account'
  ///
  /// This reverts if the `msg.sender` is not admin
  ///
  /// @param toSetCeiling The account whos allowance to update
  /// @param ceiling      The amount of tokens allowed to mint
  function setCeiling(address toSetCeiling, uint256 ceiling) external;

  /// @notice Updates the state of an address in the whitelist map
  ///
  /// This reverts if msg.sender is not admin
  ///
  /// @param toWhitelist the address whos state is being updated
  /// @param state the boolean state of the whitelist
  function setWhitelist(address toWhitelist, bool state) external;

  function mint(address recipient, uint256 amount) external;

  function burn(uint256 amount) external;
}

================================================
FILE: src/interfaces/IAlchemistCurator.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IAlchemistCurator {
    function increaseAbsoluteCap(address adapter, uint256 amount) external;
    function increaseRelativeCap(address adapter, uint256 amount) external;
    function submitIncreaseAbsoluteCap(address adapter, uint256 amount) external;
    function submitIncreaseRelativeCap(address adapter, uint256 amount) external;

    event IncreaseAbsoluteCap(address indexed strategy, uint256 amount, bytes indexed id);
    event SubmitIncreaseAbsoluteCap(address indexed strategy, uint256 amount, bytes indexed id);
    event IncreaseRelativeCap(address indexed strategy, uint256 amount, bytes indexed id);
    event SubmitIncreaseRelativeCap(address indexed strategy, uint256 amount, bytes indexed id);

    function decreaseAbsoluteCap(address adapter, uint256 amount) external;

    event DecreaseAbsoluteCap(address indexed strategy, uint256 amount, bytes indexed id);
    event SubmitDecreaseAbsoluteCap(address indexed strategy, uint256 amount, bytes indexed id);

    function decreaseRelativeCap(address adapter, uint256 amount) external;

    function submitSetAllocator(address myt, address allocator, bool v) external;
    event SubmitSetAllocator(address indexed allocator, bool indexed v);
    event DecreaseRelativeCap(address indexed strategy, uint256 amount, bytes indexed id);
    event SubmitDecreaseRelativeCap(address indexed strategy, uint256 amount, bytes indexed id);
    event StrategyAdded(address indexed strategy, address indexed myt);
    event StrategyRemoved(address indexed strategy, address indexed myt);
    event SubmitSetStrategy(address indexed strategy, address indexed myt);
    event SubmitRemoveStrategy(address indexed strategy, address indexed myt);

    function submitSetForceDeallocatePenalty(address adapter, address myt, uint256 penalty) external;
    event SubmitSetForceDeallocatePenalty(address indexed adapter, address indexed myt, uint256 penalty);

    function submitSetPerformanceFeeRecipient(address myt, address recipient) external;
    event SubmitSetPerformanceFeeRecipient(address indexed myt, address indexed recipient);

    function submitSetPerformanceFee(address myt, uint256 fee) external;
    event SubmitSetPerformanceFee(address indexed myt, uint256 fee);
}


================================================
FILE: src/interfaces/IAlchemistETHVault.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/**
 * @title IAlchemistETHVault
 * @notice Interface for the AlchemistETHVault which handles ETH/WETH deposits
 * @dev Only authorized addresses (AlchemistV3 or admin) can withdraw funds
 */
interface IAlchemistETHVault {
    /**
     * @notice Get the ERC20 token managed by this vault
     * @return The WETH token address
     */
    function token() external view returns (address);

    /**
     * @notice Deposit WETH into the vault
     * @param amount Amount of WETH to deposit
     */
    function depositWETH(uint256 amount) external;

    /**
     * @notice Withdraw funds from the vault to a target address
     * @param recipient Address to receive the funds
     * @param amount Amount to withdraw
     */
    function withdraw(address recipient, uint256 amount) external;

    /**
     * @notice Update the AlchemistV3 contract address
     * @param _alchemistV3 New AlchemistV3 address
     */
    function setAlchemist(address _alchemistV3) external;

    /**
     * @notice Get the balance of a user
     * @param user Address of the user
     * @return User's balance in the vault
     */
    function balanceOf(address user) external view returns (uint256);

    /**
     * @notice Get the WETH contract address
     * @return Address of the WETH contract
     */
    function weth() external view returns (address);

    /**
     * @notice Get the AlchemistV3 contract address
     * @return Address of the AlchemistV3 contract
     */
    function alchemist() external view returns (address);

    /**
     * @notice Get the total amount of deposits in the vault
     * @return Total deposits
     */
    function totalDeposits() external view returns (uint256);

    /**
     * @notice Event emitted when funds are deposited
     * @param depositor Address that deposited funds
     * @param amount Amount deposited
     */
    event Deposited(address indexed depositor, uint256 amount);

    /**
     * @notice Event emitted when funds are withdrawn
     * @param recipient Address that received funds
     * @param amount Amount withdrawn
     */
    event Withdrawn(address indexed recipient, uint256 amount);

    /**
     * @notice Event emitted when the AlchemistV3 address is updated
     * @param newAlchemist New AlchemistV3 address
     */
    event AlchemistV3Updated(address indexed newAlchemist);
}


================================================
FILE: src/interfaces/IAlchemistTokenVault.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title IAlchemistTokenVault
 * @notice Interface for the AlchemistTokenVault contract
 */
interface IAlchemistTokenVault {
    /**
     * @notice Get the ERC20 token managed by this vault
     * @return The ERC20 token address
     */
    function token() external view returns (address);

    /**
     * @notice Get the address of the Alchemist contract
     * @return The Alchemist contract address
     */
    function alchemist() external view returns (address);

    /**
     * @notice Check if an address is authorized to withdraw
     * @param withdrawer The address to check
     * @return Whether the address is authorized
     */
    function authorizedWithdrawers(address withdrawer) external view returns (bool);

    /**
     * @notice Allows anyone to deposit tokens into the vault
     * @param amount The amount of tokens to deposit
     */
    function deposit(uint256 amount) external;

    /**
     * @notice Allows only the Alchemist or authorized withdrawers to withdraw tokens
     * @param to The address to receive the tokens
     * @param amount The amount of tokens to withdraw
     */
    function withdraw(address to, uint256 amount) external;

    /**
     * @notice Sets the authorized status of a withdrawer
     * @param withdrawer The address to authorize/deauthorize
     * @param status True to authorize, false to deauthorize
     */
    function setAuthorizedWithdrawer(address withdrawer, bool status) external;

    /**
     * @notice Updates the Alchemist address
     * @param _alchemist The new Alchemist address
     */
    function setAlchemist(address _alchemist) external;

    /**
     * @notice Emitted when tokens are deposited
     */
    event Deposited(address indexed from, uint256 amount);

    /**
     * @notice Emitted when tokens are withdrawn
     */
    event Withdrawn(address indexed to, uint256 amount);

    /**
     * @notice Emitted when an authorized withdrawer status changes
     */
    event AuthorizedWithdrawerSet(address indexed withdrawer, bool status);

    /**
     * @notice Emitted when the Alchemist address is updated
     */
    event AlchemistUpdated(address indexed newAlchemist);
}


================================================
FILE: src/interfaces/IAlchemistV3.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/// @notice Contract initialization parameters.
struct AlchemistInitializationParams {
    // The initial admin account.
    address admin;
    // The ERC20 token used to represent debt. i.e. the alAsset.
    address debtToken;
    // The ERC20 token used to represent the underlying token of the yield token.
    address underlyingToken;
    // The global maximum amount of deposited collateral.
    uint256 depositCap;
    // The minimum collateralization between 0 and 1 exclusive
    uint256 minimumCollateralization;
    // The global minimum collateralization, >= minimumCollateralization.
    uint256 globalMinimumCollateralization;
    // The minimum collateralization for liquidation eligibility. between 1 and minimumCollateralization inclusive.
    uint256 collateralizationLowerBound;
    // The target collateralization ratio to restore accounts to after liquidation. Must be >= minimumCollateralization.
    uint256 liquidationTargetCollateralization;
    // The initial transmuter or transmuter buffer.
    address transmuter;
    // The fee on user debt paid to the protocol.
    uint256 protocolFee;
    // The address that receives protocol fees.
    address protocolFeeReceiver;
    // Fee paid to liquidators.
    uint256 liquidatorFee;
    // Fee paid to liquidators forcing an account earmarked debt repayment.
    uint256 repaymentFee;
    // The address of the morpho v2 vault.
    address myt;
}

/// @notice A user account.
/// @notice This account struct is included in the main contract, AlchemistV3.sol, to aid readability.
struct Account {
    /// @notice User's collateral.
    uint256 collateralBalance;
    /// @notice User's debt.
    uint256 debt;
    /// @notice User debt earmarked for redemption.
    uint256 earmarked;
    /// @notice The amount of unlocked collateral.
    uint256 freeCollateral;
    /// @notice Last weight of earmark from most recent account sync.
    uint256 lastAccruedEarmarkWeight;
    /// @notice Last weight of redemption from most recent account sync.
    uint256 lastAccruedRedemptionWeight;
    /// @notice Last weight of collateral from most recent account sync.
    uint256 lastCollateralWeight;
    /// @notice Block of the most recent mint
    uint256 lastMintBlock;
    /// @notice Block of the most recent repay
    uint256 lastRepayBlock;
    /// @notice Last stored survival accumulator
    uint256 lastSurvivalAccumulator;
    /// @notice Total debt redeemed at last sync
    uint256 lastTotalRedeemedDebt;
    /// @notice Total debt paid out from redemptions at last sync
    uint256 lastTotalRedeemedSharesOut;
    /// @notice allowances for minting alAssets, per version.
    mapping(uint256 => mapping(address => uint256)) mintAllowances;
    /// @notice id used in the mintAllowances map which is incremented on reset.
    uint256 allowancesVersion;
}

/// @notice Information associated with a redemption.
/// @notice This redemption struct is included in the main contract, AlchemistV3.sol, to aid in calculating user debt from historic redemptions.
struct RedemptionInfo {
    uint256 earmarked;
    uint256 debt;
    uint256 earmarkWeight;
}

interface IAlchemistV3Actions {
    /// @notice Approve `spender` to mint `amount` debt tokens.
    ///
    /// @param tokenId The tokenId of account granting approval.
    /// @param spender The address that will be approved to mint.
    /// @param amount  The amount of tokens that `spender` will be allowed to mint.

    function approveMint(uint256 tokenId, address spender, uint256 amount) external;

    /// @notice Synchronizes the state of the account owned by `owner`.
    ///
    /// @param tokenId   The tokenId of account
    function poke(uint256 tokenId) external;

    /// @notice Deposit a yield token into a user's account.
    /// @notice Create a new position by using zero (0) for the `recipientId`.
    /// @notice Users may create as many positions as they want.
    ///
    /// @notice An approval must be set for `yieldToken` which is greater than `amount`.
    ///
    /// @notice `recipient` must be non-zero or this call will revert with an {IllegalArgument} error.
    /// @notice `amount` must be greater than zero or the call will revert with an {IllegalArgument} error.
    ///
    /// @notice Emits a {Deposit} event.
    ///
    /// @notice **_NOTE:_** When depositing, the `AlchemistV3` contract must have **allowance()** to spend funds on behalf of **msg.sender** for at least **amount** of the **yieldToken** being deposited.  This can be done via the standard `ERC20.approve()` method.
    ///
    /// @notice **Example:**
    /// @notice ```
    /// @notice address ydai = 0xdA816459F1AB5631232FE5e97a05BBBb94970c95;
    /// @notice uint256 amount = 50000;
    /// @notice IERC20(ydai).approve(alchemistAddress, amount);
    /// @notice (uint256 tokenId, uint256 debtValue) = AlchemistV3(alchemistAddress).deposit(amount, msg.sender, 0);
    /// @notice ```
    ///
    /// @param amount     The amount of yield tokens to deposit.
    /// @param recipient  The owner of the account that will receive the resulting shares.
    /// @param recipientId The id of account.
    /// @return tokenId The id of the account.
    /// @return debtValue The value of deposited tokens normalized to debt token value.
    function deposit(uint256 amount, address recipient, uint256 recipientId) external returns (uint256 tokenId, uint256 debtValue);

    /// @notice Withdraw `amount` yield tokens to `recipient`.
    ///
    /// @notice `recipient` must be non-zero or this call will revert with an {IllegalArgument} error.
    ///
    /// @notice Emits a {Withdraw} event.
    ///
    /// @notice **_NOTE:_** When withdrawing, th amount withdrawn must not put user over allowed LTV ratio.
    ///
    /// @notice **Example:**
    /// @notice ```
    /// @notice address ydai = 0xdA816459F1AB5631232FE5e97a05BBBb94970c95;
    /// @notice (uint256 LTV, ) = AlchemistV3(alchemistAddress).getLoanTerms(msg.sender);
    /// @notice (uint256 yieldTokens, ) = AlchemistV3(alchemistAddress).getCDP(tokenId);
    /// @notice uint256 maxWithdrawableTokens = (AlchemistV3(alchemistAddress).LTV() - LTV) * yieldTokens / LTV;
    /// @notice AlchemistV3(alchemistAddress).withdraw(maxWithdrawableTokens, msg.sender);
    /// @notice ```
    ///
    /// @param amount     The number of tokens to withdraw.
    /// @param recipient  The address of the recipient.
    /// @param tokenId The tokenId of account.
    ///
    /// @return amountWithdrawn The number of yield tokens that were withdrawn to `recipient`.
    function withdraw(uint256 amount, address recipient, uint256 tokenId) external returns (uint256 amountWithdrawn);

    /// @notice Mint `amount` debt tokens.
    ///
    /// @notice `recipient` must be non-zero or this call will revert with an {IllegalArgument} error.
    /// @notice `amount` must be greater than zero or this call will revert with a {IllegalArgument} error.
    ///
    /// @notice Emits a {Mint} event.
    ///
    /// @notice **Example:**
    /// @notice ```
    /// @notice uint256 amtDebt = 5000;
    /// @notice AlchemistV3(alchemistAddress).mint(amtDebt, msg.sender);
    /// @notice ```
    ///
    /// @param tokenId The tokenId of account.
    /// @param amount    The amount of tokens to mint.
    /// @param recipient The address of the recipient.
    function mint(uint256 tokenId, uint256 amount, address recipient) external;

    /// @notice Mint `amount` debt tokens from the account owned by `owner` to `recipient`.
    ///
    /// @notice `recipient` must be non-zero or this call will revert with an {IllegalArgument} error.
    /// @notice `amount` must be greater than zero or this call will revert with a {IllegalArgument} error.
    ///
    /// @notice Emits a {Mint} event.
    ///
    /// @notice **_NOTE:_** The caller of `mintFrom()` must have **mintAllowance()** to mint debt from the `Account` controlled by **owner** for at least the amount of **yieldTokens** that **shares** will be converted to.  This can be done via the `approveMint()` or `permitMint()` methods.
    ///
    /// @notice **Example:**
    /// @notice ```
    /// @notice uint256 amtDebt = 5000;
    /// @notice AlchemistV3(alchemistAddress).mintFrom(msg.sender, amtDebt, msg.sender);
    /// @notice ```
    ///
    /// @param tokenId   The tokenId of account.
    /// @param amount    The amount of tokens to mint.
    /// @param recipient The address of the recipient.
    function mintFrom(uint256 tokenId, uint256 amount, address recipient) external;

    /// @notice Burn `amount` debt tokens to credit the account owned by `recipientId`.
    ///
    /// @notice `amount` will be limited up to the amount of unearmarked debt that `recipient` currently holds.
    ///
    /// @notice `recipientId` must be non-zero or this call will revert with an {IllegalArgument} error.
    /// @notice `amount` must be greater than zero or this call will revert with a {IllegalArgument} error.
    /// @notice account for `recipientId` must have non-zero debt or this call will revert with an {IllegalState} error.
    ///
    /// @notice Emits a {Burn} event.
    ///
    /// @notice **Example:**
    /// @notice ```
    /// @notice uint256 amtBurn = 5000;
    /// @notice AlchemistV3(alchemistAddress).burn(amtBurn, 420);
    /// @notice ```
    ///
    /// @param amount    The amount of tokens to burn.
    /// @param recipientId   The tokenId of account to being credited.
    ///
    /// @return amountBurned The amount of tokens that were burned.
    function burn(uint256 amount, uint256 recipientId) external returns (uint256 amountBurned);

    /// @notice Repay `amount` debt using yield tokens to credit the account owned by `recipientId`.
    ///
    /// @notice `amount` will be limited up to the amount of debt that `recipient` currently holds.
    ///
    /// @notice `amount` must be greater than zero or this call will revert with a {IllegalArgument} error.
    /// @notice `recipient` must be non-zero or this call will revert with an {IllegalArgument} error.
    ///
    /// @notice Emits a {Repay} event.
    ///
    /// @notice **Example:**
    /// @notice ```
    /// @notice uint256 amtRepay = 5000;
    /// @notice AlchemistV3(alchemistAddress).repay(amtRepay, msg.sender);
    /// @notice ```
    ///
    /// @param amount          The amount of the yield tokens to repay with.
    /// @param recipientTokenId   The tokenId of account to be repaid
    ///
    /// @return amountRepaid The amount of tokens that were repaid.
    function repay(uint256 amount, uint256 recipientTokenId) external returns (uint256 amountRepaid);

    /**
     * @notice Liquidates `owner` if the debt for account `owner` is greater than the underlying value of their collateral * LTV.
     *
     * @notice `owner` must be non-zero or this call will revert with an {IllegalArgument} error.
     *
     * @notice Emits a {Liquidate} event.
     *
     * @notice **Example:**
     * @notice ```
     * @notice AlchemistV2(alchemistAddress).liquidate(id4);
     * @notice ```
     *
     * @param accountId   The tokenId of account
     *
     * @return yieldAmount         Yield tokens sent to the transmuter.
     * @return feeInYield          Fee paid to liquidator in yield tokens.
     * @return feeInUnderlying     Fee paid to liquidator in underlying token.
     */
    function liquidate(uint256 accountId) external returns (uint256 yieldAmount, uint256 feeInYield, uint256 feeInUnderlying);


    /// @notice Self liquidates the account owned by `accountId`.
    ///
    /// @notice `accountId` must be non-zero or this call will revert with an {IllegalArgument} error.
    /// @notice `recipient` must be non-zero or this call will revert with an {IllegalArgument} error.
    /// @notice `accountId` must be owned by `msg.sender` or this call will revert with an {Unauthorized} error.
    /// @notice `accountId` must be healthy (overcollateralized) or this call will revert with an {AccountNotHealthy} error.
    /// @notice in the event that account is not healthy, the account can only be liquidated using the regular liquidation path. (i.e. liquidate(accountId))
    ///
    /// @notice Emits a {SelfLiquidated} event.
    ///
    /// @param accountId The tokenId of account to be self liquidated.
    /// @param recipient The address of the recipient.
    function selfLiquidate(uint256 accountId, address recipient) external returns (uint256 amountLiquidated);

    /// @notice Liquidates `owners` if the debt for account `owner` is greater than the underlying value of their collateral * LTV.
    ///
    /// @notice `owner` must be non-zero or this call will revert with an {IllegalArgument} error.
    ///
    ///
    /// @notice **Example:**
    /// @notice ```
    /// @notice AlchemistV3(alchemistAddress).batchLiquidate([id1, id35]);
    /// @notice ```
    ///
    /// @param accountIds   The tokenId of each account
    ///
    /// @return totalAmountLiquidated   Amount in yield tokens sent to the transmuter.
    /// @return totalFeesInYield        Amount sent to liquidator in yield tokens.
    /// @return totalFeesInUnderlying   Amount sent to liquidator in underlying token.
    function batchLiquidate(uint256[] memory accountIds)
        external
        returns (uint256 totalAmountLiquidated, uint256 totalFeesInYield, uint256 totalFeesInUnderlying);

    /// @notice Redeems `amount` debt from the alchemist in exchange for yield tokens sent to the transmuter.
    ///
    /// @notice This function is only callable by the transmuter.
    ///
    /// @notice Emits a {Redeem} event.
    ///
    /// @param amount The amount of tokens to redeem.
    function redeem(uint256 amount) external returns (uint256 sharesSent);

    /// @notice Reduces syntheticTokensIssued by `amount`.
    ///
    /// @notice This function is only callable by the transmuter.
    ///
    /// @param amount The amount of tokens burned during redemption.
    function reduceSyntheticsIssued(uint256 amount) external;

    /// @notice Sets lastTransmuterTokenBalance to `amount`.
    ///
    /// @notice This function is only callable by the transmuter.
    ///
    /// @param amount The balance of the transmuter.
    function setTransmuterTokenBalance(uint256 amount) external;

    /// @notice Resets all mint allowances by account managed by `tokenId`.
    ///
    /// @notice This function is only callable by the owner of the token id or the AlchemistV3Position contract.
    ///
    /// @notice Emits a {MintAllowancesReset} event.
    ///
    /// @param tokenId The token id of the account.
    function resetMintAllowances(uint256 tokenId) external;
}

interface IAlchemistV3AdminActions {
    /// @notice Sets the pending administrator.
    ///
    /// @notice `msg.sender` must be the admin or this call will will revert with an {Unauthorized} error.
    ///
    /// @notice Emits a {PendingAdminUpdated} event.
    ///
    /// @dev This is the first step in the two-step process of setting a new administrator. After this function is called, the pending administrator will then need to call {acceptAdmin} to complete the process.
    ///
    /// @param value The address to set the pending admin to.
    function setPendingAdmin(address value) external;

    /// @notice Sets the active state of a guardian.
    ///
    /// @notice `msg.sender` must be the admin or this call will will revert with an {Unauthorized} error.
    ///
    /// @notice Emits a {GuardianSet} event.
    ///
    /// @param guardian The address of the target guardian.
    /// @param isActive The active state to set for the guardian.
    function setGuardian(address guardian, bool isActive) external;

    /// @notice Allows for `msg.sender` to accepts the role of administrator.
    ///
    /// @notice `msg.sender` must be the admin or this call will revert with an {Unauthorized} error.
    /// @notice The current pending administrator must be non-zero or this call will revert with an {IllegalState} error.
    ///
    /// @dev This is the second step in the two-step process of setting a new administrator. After this function is successfully called, this pending administrator will be reset and the new administrator will be set.
    ///
    /// @notice Emits a {AdminUpdated} event.
    /// @notice Emits a {PendingAdminUpdated} event.
    function acceptAdmin() external;

    /// @notice Set a new alchemist deposit cap.
    ///
    /// @notice `msg.sender` must be the admin or this call will revert with an {Unauthorized} error.
    ///
    /// @notice Emits a {DepositCapUpdated} event.
    ///
    /// @param value The value of the new deposit cap.
    function setDepositCap(uint256 value) external;

    /// @notice Sets the token adapter for the yield token.
    ///
    /// @notice `msg.sender` must be the admin or this call will will revert with an {Unauthorized} error.
    ///
    /// @notice Emits a {TokenAdapterSet} event.
    ///
    /// @param value The address of token adapter.
    function setTokenAdapter(address value) external;

    /// @notice Set the minimum collateralization ratio.
    ///
    /// @notice `msg.sender` must be the admin or this call will revert with an {Unauthorized} error.
    ///
    /// @notice Emits a {MinimumCollateralizationUpdated} event.
    ///
    /// @param value The new minimum collateralization ratio.
    function setMinimumCollateralization(uint256 value) external;

    /// @notice Set a new protocol fee receiver.
    ///
    /// @notice `msg.sender` must be the admin or this call will revert with an {Unauthorized} error.
    ///
    /// @notice Emits a {ProtocolFeeRe
Download .txt
└── src/
    ├── AlTokenV3.sol
    ├── AlchemistAllocator.sol
    ├── AlchemistCurator.sol
    ├── AlchemistETHVault.sol
    ├── AlchemistGate.sol
    ├── AlchemistStrategyClassifier.sol
    ├── AlchemistTokenVault.sol
    ├── AlchemistV3.sol
    ├── AlchemistV3Position.sol
    ├── AlchemistV3PositionRenderer.sol
    ├── FrxEthEthDualOracleAggregatorAdapter.sol
    ├── MYTStrategy.sol
    ├── PerpetualGauge.sol
    ├── Transmuter.sol
    ├── adapters/
    │   ├── AbstractFeeVault.sol
    │   └── EulerUSDCAdapter.sol
    ├── base/
    │   ├── ErrorMessages.sol
    │   ├── Errors.sol
    │   └── TransmuterErrors.sol
    ├── external/
    │   ├── AlEth.sol
    │   └── interfaces/
    │       ├── IDetailedERC20.sol
    │       ├── ISettlerActions.sol
    │       └── IVelodromePair.sol
    ├── interfaces/
    │   ├── IAlchemicToken.sol
    │   ├── IAlchemistCurator.sol
    │   ├── IAlchemistETHVault.sol
    │   ├── IAlchemistTokenVault.sol
    │   ├── IAlchemistV3.sol
    │   ├── IAlchemistV3Position.sol
    │   ├── IAllocator.sol
    │   ├── IERC20Burnable.sol
    │   ├── IERC20Metadata.sol
    │   ├── IERC20Minimal.sol
    │   ├── IERC20Mintable.sol
    │   ├── IERC721Enumerable.sol
    │   ├── IFeeVault.sol
    │   ├── IMYTStrategy.sol
    │   ├── IMetadataRenderer.sol
    │   ├── IStrategyClassifier.sol
    │   ├── ITokenAdapter.sol
    │   ├── ITransmuter.sol
    │   ├── IWETH.sol
    │   ├── IWhitelist.sol
    │   ├── IWstETHLike.sol
    │   ├── IYearnVaultV2.sol
    │   ├── IYieldToken.sol
    │   └── test/
    │       └── ITestYieldToken.sol
    ├── libraries/
    │   ├── FixedPointMath.sol
    │   ├── NFTMetadataGenerator.sol
    │   ├── SafeCast.sol
    │   ├── SafeERC20.sol
    │   ├── Sets.sol
    │   ├── StakingGraph.sol
    │   └── TokenUtils.sol
    ├── mocks/
    │   ├── ERC20Mock.sol
    │   ├── FixedPointMathOld.sol
    │   ├── Pool.sol
    │   ├── Stake.sol
    │   └── StakingPoolMock.sol
    ├── router/
    │   └── AlchemistRouter.sol
    ├── strategies/
    │   ├── AaveStrategy.sol
    │   ├── ERC4626Strategy.sol
    │   ├── EtherfiEETHStrategy.sol
    │   ├── MoonwellStrategy.sol
    │   ├── OraclePricedSwapStrategy.sol
    │   ├── SFraxETHStrategy.sol
    │   ├── SiUSDStrategy.sol
    │   ├── TokeAutoStrategy.sol
    │   ├── WstETHEthereumStrategy.sol
    │   ├── WstETHL2Strategy.sol
    │   └── interfaces/
    │       └── ITokemac.sol
    ├── test/
    │   ├── AlchemistAllocator.t.sol
    │   ├── AlchemistCurator.t.sol
    │   ├── AlchemistETHVault.t.sol
    │   ├── AlchemistStrategyClassifier.t.sol
    │   ├── AlchemistTokenVault.t.sol
    │   ├── AlchemistV3.t.sol
    │   ├── AlchemistV3_6_decimals.t.sol
    │   ├── BaseStrategyTest.sol
    │   ├── DeploySFraxETHStrategyScript.t.sol
    │   ├── DeploySiUSDStrategiesScript.t.sol
    │   ├── DeployWstETHEthereumStrategyScript.t.sol
    │   ├── DeployWstETHL2StrategyScript.t.sol
    │   ├── DeployYvWETHStrategyScript.t.sol
    │   ├── FrxEthEthDualOracleAggregatorAdapter.t.sol
    │   ├── IntegrationTest.t.sol
    │   ├── Invariants/
    │   │   ├── CrucibleTest.sol
    │   │   ├── FullSystemInvariantsTest.sol
    │   │   ├── HardenedInvariantsTest.sol
    │   │   └── InvariantBaseTest.t.sol
    │   ├── InvariantsTest.t.sol
    │   ├── MYTStrategy.t.sol
    │   ├── MultiStrategyARBETH.invariant.t.sol
    │   ├── MultiStrategyARBUSDC.invariant.t.sol
    │   ├── MultiStrategyETH.invariant.t.sol
    │   ├── MultiStrategyOPETH.invariant.t.sol
    │   ├── MultiStrategyOPUSDC.invariant.t.sol
    │   ├── MultiStrategyUSDC.invariant.t.sol
    │   ├── PerpetualGaugeTest.t.sol
    │   ├── README.md
    │   ├── Transmuter.t.sol
    │   ├── ZeroXSwapVerifier.t.sol
    │   ├── base/
    │   │   ├── BaseStrategyMulti.sol
    │   │   ├── BaseStrategySimple.sol
    │   │   ├── StrategyHandler.sol
    │   │   ├── StrategyOps.sol
    │   │   ├── StrategyRevertUtils.sol
    │   │   ├── StrategySetup.sol
    │   │   └── StrategyTypes.sol
    │   ├── libraries/
    │   │   ├── AlchemistNFTHelper.sol
    │   │   ├── CustomBase64.sol
    │   │   └── MYTTestHelper.sol
    │   ├── mocks/
    │   │   ├── AlchemicTokenV3.sol
    │   │   ├── MockAlchemistAllocator.sol
    │   │   ├── MockAlchemistCurator.sol
    │   │   ├── MockMYTStrategy.sol
    │   │   ├── MockMYTVault.sol
    │   │   ├── MockWETH.sol
    │   │   ├── MockYieldToken.sol
    │   │   ├── TestERC20.sol
    │   │   ├── TestYieldToken.sol
    │   │   └── TokenAdapterMock.sol
    │   ├── router/
    │   │   └── AlchemistRouter.t.sol
    │   └── strategies/
    │       ├── AaveV3ARBUSDCStrategy.t.sol
    │       ├── AaveV3ARBWETHStrategy.t.sol
    │       ├── AaveV3ETHWETHStrategy.t.sol
    │       ├── AaveV3OPUSDCStrategy.t.sol
    │       ├── AaveV3OPWETHStrategy.t.sol
    │       ├── EtherfiEETHStrategy.t.sol
    │       ├── EulerARBUSDCStrategy.t.sol
    │       ├── EulerARBWETHStrategy.t.sol
    │       ├── EulerUSDCStrategy.t.sol
    │       ├── EulerWETHStrategy.t.sol
    │       ├── FluidARBUSDCStrategy.t.sol
    │       ├── SFraxETHStrategy.t.sol
    │       ├── SiUSDStrategy.t.sol
    │       ├── TokeAutoETHStrategy.t.sol
    │       ├── TokeAutoUSDStrategy.t.sol
    │       ├── WstethMainnetStrategy.t.sol
    │       ├── WstethOptimismStrategy.t.sol
    │       ├── YvUSDCStrategy.t.sol
    │       ├── YvWETHStrategy.t.sol
    │       └── utils/
    │           └── offchain/
    │               └── quotes/
    │                   ├── stethToWeth.json
    │                   ├── wethToWsteth.json
    │                   └── wstethToWeth.json
    └── utils/
        ├── PermissionedProxy.sol
        ├── Whitelist.sol
        └── ZeroXSwapVerifier.sol
Condensed preview — 148 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,921K chars).
[
  {
    "path": "src/AlTokenV3.sol",
    "chars": 1976,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity 0.8.28;\n\nimport {CrossChainCanonicalBase} from \"lib/v2-foun"
  },
  {
    "path": "src/AlchemistAllocator.sol",
    "chars": 7889,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {IVaultV2} from \"lib/vault-v2/src/interfaces/IVaultV2.s"
  },
  {
    "path": "src/AlchemistCurator.sol",
    "chars": 7273,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {IVaultV2} from \"../lib/vault-v2/src/interfaces/IVaultV"
  },
  {
    "path": "src/AlchemistETHVault.sol",
    "chars": 3035,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/AlchemistGate.sol",
    "chars": 408,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\ncontract Al"
  },
  {
    "path": "src/AlchemistStrategyClassifier.sol",
    "chars": 3532,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IStrategyClassifier} from \"./interfaces/IStrategyClassi"
  },
  {
    "path": "src/AlchemistTokenVault.sol",
    "chars": 1940,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/ut"
  },
  {
    "path": "src/AlchemistV3.sol",
    "chars": 80272,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"./interfaces/IAlchemistV3.sol\";\nimport {ITransmuter} fr"
  },
  {
    "path": "src/AlchemistV3Position.sol",
    "chars": 5209,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {ERC721Enumerable} from \"@openzeppelin/contracts/token/E"
  },
  {
    "path": "src/AlchemistV3PositionRenderer.sol",
    "chars": 592,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IMetadataRenderer} from \"./interfaces/IMetadataRenderer"
  },
  {
    "path": "src/FrxEthEthDualOracleAggregatorAdapter.sol",
    "chars": 1593,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ninterface IFrxEthEthDualOracle {\n    function getPrices() exter"
  },
  {
    "path": "src/MYTStrategy.sol",
    "chars": 15075,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {IVaultV2} from \"vault-v2/interfaces/IVaultV2.sol\";\nimp"
  },
  {
    "path": "src/PerpetualGauge.sol",
    "chars": 6750,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport { IERC20 } from \"@openzeppelin/contracts/token/ERC20/IE"
  },
  {
    "path": "src/Transmuter.sol",
    "chars": 13289,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.28;\n\nimport {IAlchemistV3} from \"./interfaces/IAlchemistV3.s"
  },
  {
    "path": "src/adapters/AbstractFeeVault.sol",
    "chars": 2988,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport {IF"
  },
  {
    "path": "src/adapters/EulerUSDCAdapter.sol",
    "chars": 749,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.28;\n\nimport \"../libraries/TokenUtils.sol\";\nimport \"@openzepp"
  },
  {
    "path": "src/base/ErrorMessages.sol",
    "chars": 774,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >=0.8.4;\n\n/// @notice An error used to indicate that an argument "
  },
  {
    "path": "src/base/Errors.sol",
    "chars": 1082,
    "preview": "pragma solidity ^0.8.23;\n\n/// @notice An error used to indicate that an action could not be completed because either the"
  },
  {
    "path": "src/base/TransmuterErrors.sol",
    "chars": 415,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity 0.8.28;\n\nerror NotRegisteredAlchemist();\n\nerror AlchemistDuplicat"
  },
  {
    "path": "src/external/AlEth.sol",
    "chars": 4507,
    "preview": "// SPDX-License-Identifier: GPL-3.0\npragma solidity ^0.8.28;\npragma experimental ABIEncoderV2;\n\nimport {ERC20} from \"@op"
  },
  {
    "path": "src/external/interfaces/IDetailedERC20.sol",
    "chars": 312,
    "preview": "// SPDX-License-Identifier: GPL-3.0\npragma solidity ^0.8.28;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n"
  },
  {
    "path": "src/external/interfaces/ISettlerActions.sol",
    "chars": 9821,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport {ISignatureTransfer} from \"../../../lib/permit2/src/int"
  },
  {
    "path": "src/external/interfaces/IVelodromePair.sol",
    "chars": 558,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\nimport {IERC20} from \"../../../lib/openzeppelin-contracts/contr"
  },
  {
    "path": "src/interfaces/IAlchemicToken.sol",
    "chars": 1490,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity >=0.5.0;\n\nimport {IERC20} from \"../../lib/openzeppelin-cont"
  },
  {
    "path": "src/interfaces/IAlchemistCurator.sol",
    "chars": 2303,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ninterface IAlchemistCurator {\n    function increaseAbsoluteCap("
  },
  {
    "path": "src/interfaces/IAlchemistETHVault.sol",
    "chars": 2393,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n/**\n * @title IAlchemistETHVault\n * @notice Interface for the A"
  },
  {
    "path": "src/interfaces/IAlchemistTokenVault.sol",
    "chars": 2288,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/**\n "
  },
  {
    "path": "src/interfaces/IAlchemistV3.sol",
    "chars": 42880,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\n/// @notice Contract initialization parameters.\nstruct Alchemi"
  },
  {
    "path": "src/interfaces/IAlchemistV3Position.sol",
    "chars": 2321,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC721Enumerable} from \"../interfaces/IERC721Enumerabl"
  },
  {
    "path": "src/interfaces/IAllocator.sol",
    "chars": 1178,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n\ninterface IAllocator {\n    /// @notice Allocate with direct al"
  },
  {
    "path": "src/interfaces/IERC20Burnable.sol",
    "chars": 768,
    "preview": "pragma solidity >=0.5.0;\n\nimport \"../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\";\n\n/// @title  IERC2"
  },
  {
    "path": "src/interfaces/IERC20Metadata.sol",
    "chars": 559,
    "preview": "pragma solidity >=0.5.0;\n\n/// @title  IERC20Metadata\n/// @author Alchemix Finance\ninterface IERC20Metadata {\n    /// @no"
  },
  {
    "path": "src/interfaces/IERC20Minimal.sol",
    "chars": 3144,
    "preview": "pragma solidity >=0.5.0;\n\n/// @title  IERC20Minimal\n/// @author Alchemix Finance\ninterface IERC20Minimal {\n    /// @noti"
  },
  {
    "path": "src/interfaces/IERC20Mintable.sol",
    "chars": 452,
    "preview": "pragma solidity >=0.5.0;\n\nimport \"../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\";\n\n/// @title  IERC2"
  },
  {
    "path": "src/interfaces/IERC721Enumerable.sol",
    "chars": 1074,
    "preview": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Enumerab"
  },
  {
    "path": "src/interfaces/IFeeVault.sol",
    "chars": 669,
    "preview": "pragma solidity >=0.5.0;\n\n/// @title  IFeeVault\n/// @author Alchemix Finance\ninterface IFeeVault {\n    /**\n     * @notic"
  },
  {
    "path": "src/interfaces/IMYTStrategy.sol",
    "chars": 4760,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ninterface IMYTStrategy {\n    // Enums\n    enum RiskClass {\n    "
  },
  {
    "path": "src/interfaces/IMetadataRenderer.sol",
    "chars": 313,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ninterface IMetadataRenderer {\n    /// @notice Generate the toke"
  },
  {
    "path": "src/interfaces/IStrategyClassifier.sol",
    "chars": 649,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.28;\n\n/// @notice Simple interface for DAO-defined percentage-based"
  },
  {
    "path": "src/interfaces/ITokenAdapter.sol",
    "chars": 862,
    "preview": "pragma solidity >=0.5.0;\n\n/// @title  ITokenAdapter\n/// @author Alchemix Finance\ninterface ITokenAdapter {\n    /// @noti"
  },
  {
    "path": "src/interfaces/ITransmuter.sol",
    "chars": 9598,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.28;\n\nimport \"./IAlchemistV3.sol\";\n\ninterface ITransmuter {\n "
  },
  {
    "path": "src/interfaces/IWETH.sol",
    "chars": 474,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/interfaces/IWhitelist.sol",
    "chars": 1880,
    "preview": "pragma solidity ^0.8.23;\n\n/// @title  Whitelist\n/// @author Alchemix Finance\ninterface IWhitelist {\n    /// @dev Emitted"
  },
  {
    "path": "src/interfaces/IWstETHLike.sol",
    "chars": 325,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ninterface IWstETHLike {\n    function balanceOf(address account)"
  },
  {
    "path": "src/interfaces/IYearnVaultV2.sol",
    "chars": 4699,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity >=0.5.0;\n\nimport \"../../lib/openzeppelin-contracts/contracts/token/ERC20"
  },
  {
    "path": "src/interfaces/IYieldToken.sol",
    "chars": 106,
    "preview": "pragma solidity ^0.8.23;\n\ninterface IYieldToken {\n    function price() external view returns (uint256);\n}\n"
  },
  {
    "path": "src/interfaces/test/ITestYieldToken.sol",
    "chars": 1766,
    "preview": "pragma solidity >=0.5.0;\n\nimport \"../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\";\n\n/// @title  IT"
  },
  {
    "path": "src/libraries/FixedPointMath.sol",
    "chars": 9768,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity ^0.8.23;\n\n/**\n * @notice A library which implements fixed p"
  },
  {
    "path": "src/libraries/NFTMetadataGenerator.sol",
    "chars": 8199,
    "preview": "pragma solidity 0.8.28;\n\nimport {Base64} from \"@openzeppelin/contracts/utils/Base64.sol\";\nimport {Strings} from \"@openze"
  },
  {
    "path": "src/libraries/SafeCast.sol",
    "chars": 1541,
    "preview": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.5.0;\n\nimport {IllegalArgument} from \"../base/Errors.sol"
  },
  {
    "path": "src/libraries/SafeERC20.sol",
    "chars": 3667,
    "preview": "// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.4;\n\nimport {IERC20} from \"../../lib/openzeppelin-contrac"
  },
  {
    "path": "src/libraries/Sets.sol",
    "chars": 2049,
    "preview": "pragma solidity ^0.8.23;\n\n/// @title  Sets\n/// @author Alchemix Finance\nlibrary Sets {\n    using Sets for AddressSet;\n\n "
  },
  {
    "path": "src/libraries/StakingGraph.sol",
    "chars": 8030,
    "preview": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity 0.8.28;\n\n/* Block-granular stake progression tracking for t"
  },
  {
    "path": "src/libraries/TokenUtils.sol",
    "chars": 6571,
    "preview": "pragma solidity ^0.8.23;\n\nimport \"../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\";\nimport \"../../lib/"
  },
  {
    "path": "src/mocks/ERC20Mock.sol",
    "chars": 581,
    "preview": "// SPDX-License-Identifier: GPL-3.0\npragma solidity ^0.8.23;\n\nimport {ERC20} from \"../../lib/openzeppelin-contracts/cont"
  },
  {
    "path": "src/mocks/FixedPointMathOld.sol",
    "chars": 1852,
    "preview": "//SPDX-License-Identifier: Unlicense\npragma solidity 0.8.28;\n\nlibrary FixedPointMath {\n  uint256 public constant DECIMAL"
  },
  {
    "path": "src/mocks/Pool.sol",
    "chars": 4346,
    "preview": "// SPDX-License-Identifier: GPL-3.0\npragma solidity 0.8.28;\npragma experimental ABIEncoderV2;\n\nimport {Math} from \"@open"
  },
  {
    "path": "src/mocks/Stake.sol",
    "chars": 1929,
    "preview": "// SPDX-License-Identifier: GPL-3.0\npragma solidity 0.8.28;\npragma experimental ABIEncoderV2;\n\nimport {Math} from \"@open"
  },
  {
    "path": "src/mocks/StakingPoolMock.sol",
    "chars": 14796,
    "preview": "// SPDX-License-Identifier: GPL-3.0\npragma solidity 0.8.28;\npragma experimental ABIEncoderV2;\n\nimport {IERC20} from \"@op"
  },
  {
    "path": "src/router/AlchemistRouter.sol",
    "chars": 23887,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/strategies/AaveStrategy.sol",
    "chars": 5403,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/strategies/ERC4626Strategy.sol",
    "chars": 2648,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/strategies/EtherfiEETHStrategy.sol",
    "chars": 7803,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {OraclePricedSwapStrategy} from \"./OraclePricedSwapStrat"
  },
  {
    "path": "src/strategies/MoonwellStrategy.sol",
    "chars": 5571,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/strategies/OraclePricedSwapStrategy.sol",
    "chars": 10157,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {MYTStrategy} from \"../MYTStrategy.sol\";\nimport {TokenUt"
  },
  {
    "path": "src/strategies/SFraxETHStrategy.sol",
    "chars": 5326,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/strategies/SiUSDStrategy.sol",
    "chars": 8917,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/strategies/TokeAutoStrategy.sol",
    "chars": 8167,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/strategies/WstETHEthereumStrategy.sol",
    "chars": 2810,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {OraclePricedSwapStrategy} from \"./OraclePricedSwapStra"
  },
  {
    "path": "src/strategies/WstETHL2Strategy.sol",
    "chars": 1382,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {OraclePricedSwapStrategy} from \"./OraclePricedSwapStra"
  },
  {
    "path": "src/strategies/interfaces/ITokemac.sol",
    "chars": 2882,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\nimport {IERC4626} from \"@openzeppelin/contracts/interfaces/IERC"
  },
  {
    "path": "src/test/AlchemistAllocator.t.sol",
    "chars": 31151,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {VaultV2} from "
  },
  {
    "path": "src/test/AlchemistCurator.t.sol",
    "chars": 14496,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {VaultV2} from"
  },
  {
    "path": "src/test/AlchemistETHVault.t.sol",
    "chars": 8547,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity 0.8.28;\n\nimport {IERC20} from \"../../lib/openzeppelin-contr"
  },
  {
    "path": "src/test/AlchemistStrategyClassifier.t.sol",
    "chars": 5355,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {AlchemistStrat"
  },
  {
    "path": "src/test/AlchemistTokenVault.t.sol",
    "chars": 5286,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport \"../AlchemistTokenVault.sol"
  },
  {
    "path": "src/test/AlchemistV3.t.sol",
    "chars": 291094,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity 0.8.28;\n\nimport {IERC20} from \"../../lib/openzeppelin-contr"
  },
  {
    "path": "src/test/AlchemistV3_6_decimals.t.sol",
    "chars": 21335,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity 0.8.28;\n\nimport {IERC20} from \"../../lib/openzeppelin-contr"
  },
  {
    "path": "src/test/BaseStrategyTest.sol",
    "chars": 1246,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"forge-std/interfaces/IERC20.sol\";\nimport "
  },
  {
    "path": "src/test/DeploySFraxETHStrategyScript.t.sol",
    "chars": 4079,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DeploySFraxETH"
  },
  {
    "path": "src/test/DeploySiUSDStrategiesScript.t.sol",
    "chars": 8388,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DeploySiUSDStr"
  },
  {
    "path": "src/test/DeployWstETHEthereumStrategyScript.t.sol",
    "chars": 3582,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DeployWstETHEt"
  },
  {
    "path": "src/test/DeployWstETHL2StrategyScript.t.sol",
    "chars": 3489,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {DeployWstETHL2"
  },
  {
    "path": "src/test/DeployYvWETHStrategyScript.t.sol",
    "chars": 7273,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC4626Mock} f"
  },
  {
    "path": "src/test/FrxEthEthDualOracleAggregatorAdapter.t.sol",
    "chars": 2397,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {FrxEthEthDualO"
  },
  {
    "path": "src/test/IntegrationTest.t.sol",
    "chars": 46193,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity 0.8.28;\n\nimport \"lib/openzeppelin-contracts/contracts/token"
  },
  {
    "path": "src/test/Invariants/CrucibleTest.sol",
    "chars": 37297,
    "preview": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\nimport \"../InvariantsTest.t.sol\";\nimport {ITestYie"
  },
  {
    "path": "src/test/Invariants/FullSystemInvariantsTest.sol",
    "chars": 2531,
    "preview": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\nimport \"./InvariantBaseTest.t.sol\";\n\ncontract Full"
  },
  {
    "path": "src/test/Invariants/HardenedInvariantsTest.sol",
    "chars": 42023,
    "preview": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity 0.8.28;\n\nimport \"../InvariantsTest.t.sol\";\nimport {ITestYie"
  },
  {
    "path": "src/test/Invariants/InvariantBaseTest.t.sol",
    "chars": 10021,
    "preview": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.0;\n\nimport \"../InvariantsTest.t.sol\";\n\ncontract Invari"
  },
  {
    "path": "src/test/InvariantsTest.t.sol",
    "chars": 19300,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity 0.8.28;\n\nimport {IERC20} from \"lib/openzeppelin-contracts/c"
  },
  {
    "path": "src/test/MYTStrategy.t.sol",
    "chars": 21719,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test, console} from \"forge-std/Test.sol\";\nimport {Trans"
  },
  {
    "path": "src/test/MultiStrategyARBETH.invariant.t.sol",
    "chars": 59628,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IERC20} from \"forge-std/in"
  },
  {
    "path": "src/test/MultiStrategyARBUSDC.invariant.t.sol",
    "chars": 59989,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IERC20} from \"forge-std/in"
  },
  {
    "path": "src/test/MultiStrategyETH.invariant.t.sol",
    "chars": 66391,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IERC20} from \"forge-std/in"
  },
  {
    "path": "src/test/MultiStrategyOPETH.invariant.t.sol",
    "chars": 57084,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IERC20} from \"forge-std/in"
  },
  {
    "path": "src/test/MultiStrategyOPUSDC.invariant.t.sol",
    "chars": 59362,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IERC20} from \"forge-std/in"
  },
  {
    "path": "src/test/MultiStrategyUSDC.invariant.t.sol",
    "chars": 65187,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IERC20} from \"forge-std/in"
  },
  {
    "path": "src/test/PerpetualGaugeTest.t.sol",
    "chars": 7539,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {PerpetualGauge} from \"../"
  },
  {
    "path": "src/test/README.md",
    "chars": 582,
    "preview": "## Runing Tests\n\n- Copy .example.env to your own .env\n- Use your preferred RPC urls or the default\n- run \"source .env\"\n-"
  },
  {
    "path": "src/test/Transmuter.t.sol",
    "chars": 24163,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {StdChea"
  },
  {
    "path": "src/test/ZeroXSwapVerifier.t.sol",
    "chars": 12888,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport {Test, console} from \"forge-std/Test.sol\";\nimport {Zero"
  },
  {
    "path": "src/test/base/BaseStrategyMulti.sol",
    "chars": 29014,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IVaultV2} from \"lib/vault-v2/src/interfaces/IVaultV2.so"
  },
  {
    "path": "src/test/base/BaseStrategySimple.sol",
    "chars": 10384,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IVaultV2} from \"lib/vault-v2/src/interfaces/IVaultV2.so"
  },
  {
    "path": "src/test/base/StrategyHandler.sol",
    "chars": 7163,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IVaultV2} from \"lib/vault-"
  },
  {
    "path": "src/test/base/StrategyOps.sol",
    "chars": 3495,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IAllocator} from \"../../interfaces/IAllocator.sol\";\nimp"
  },
  {
    "path": "src/test/base/StrategyRevertUtils.sol",
    "chars": 770,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n/// @notice Shared revert decoding and forwarding helpers for s"
  },
  {
    "path": "src/test/base/StrategySetup.sol",
    "chars": 13529,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IVaultV2} from \"lib/vault-"
  },
  {
    "path": "src/test/base/StrategyTypes.sol",
    "chars": 971,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n/// @notice Shared type definitions for the base strategy testi"
  },
  {
    "path": "src/test/libraries/AlchemistNFTHelper.sol",
    "chars": 3254,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IAlchemistV3Position} from \"../../interfaces/IAlchemist"
  },
  {
    "path": "src/test/libraries/CustomBase64.sol",
    "chars": 3429,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nlibrary CustomBase64 {\n    string internal constant TABLE_ENCOD"
  },
  {
    "path": "src/test/libraries/MYTTestHelper.sol",
    "chars": 1892,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Vm} from \"forg"
  },
  {
    "path": "src/test/mocks/AlchemicTokenV3.sol",
    "chars": 8595,
    "preview": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity 0.8.28;\n\nimport {AccessControl} from \"../../../lib/openzepp"
  },
  {
    "path": "src/test/mocks/MockAlchemistAllocator.sol",
    "chars": 327,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {AlchemistAllocator} from \"../../AlchemistAllocator.sol\""
  },
  {
    "path": "src/test/mocks/MockAlchemistCurator.sol",
    "chars": 263,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {AlchemistCurator} from \"../../AlchemistCurator.sol\";\n\nc"
  },
  {
    "path": "src/test/mocks/MockMYTStrategy.sol",
    "chars": 2735,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC2"
  },
  {
    "path": "src/test/mocks/MockMYTVault.sol",
    "chars": 230,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {VaultV2} from \"lib/vault-v2/src/VaultV2.sol\";\n\ncontract"
  },
  {
    "path": "src/test/mocks/MockWETH.sol",
    "chars": 577,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20."
  },
  {
    "path": "src/test/mocks/MockYieldToken.sol",
    "chars": 1692,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {TestYieldToken} from \"./TestYieldToken.sol\";\nimport {To"
  },
  {
    "path": "src/test/mocks/TestERC20.sol",
    "chars": 3381,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.23;\n\nimport \"../../../lib/openzeppelin-contracts/contracts/t"
  },
  {
    "path": "src/test/mocks/TestYieldToken.sol",
    "chars": 3095,
    "preview": "pragma solidity ^0.8.23;\n\nimport {ERC20} from \"../../../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol\";\n\nim"
  },
  {
    "path": "src/test/mocks/TokenAdapterMock.sol",
    "chars": 315,
    "preview": "pragma solidity ^0.8.23;\n\nimport {IYieldToken} from \"../../interfaces/IYieldToken.sol\";\n\ncontract TokenAdapterMock {\n   "
  },
  {
    "path": "src/test/router/AlchemistRouter.t.sol",
    "chars": 54945,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppeli"
  },
  {
    "path": "src/test/strategies/AaveV3ARBUSDCStrategy.t.sol",
    "chars": 10128,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {AaveStrategy} from \"."
  },
  {
    "path": "src/test/strategies/AaveV3ARBWETHStrategy.t.sol",
    "chars": 13036,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {AaveStrategy} from \"."
  },
  {
    "path": "src/test/strategies/AaveV3ETHWETHStrategy.t.sol",
    "chars": 10558,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {AaveStrategy} from \"."
  },
  {
    "path": "src/test/strategies/AaveV3OPUSDCStrategy.t.sol",
    "chars": 9655,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {AaveStrategy} from \"."
  },
  {
    "path": "src/test/strategies/AaveV3OPWETHStrategy.t.sol",
    "chars": 10565,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {AaveStrategy} from \"."
  },
  {
    "path": "src/test/strategies/EtherfiEETHStrategy.t.sol",
    "chars": 26913,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {BaseStrategyTest} from \"../BaseStrategyTest.sol\";\nimpor"
  },
  {
    "path": "src/test/strategies/EulerARBUSDCStrategy.t.sol",
    "chars": 5453,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {ERC4626Strategy} from"
  },
  {
    "path": "src/test/strategies/EulerARBWETHStrategy.t.sol",
    "chars": 12605,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {ERC4626Strategy} from"
  },
  {
    "path": "src/test/strategies/EulerUSDCStrategy.t.sol",
    "chars": 12903,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {ERC4626Strategy} from"
  },
  {
    "path": "src/test/strategies/EulerWETHStrategy.t.sol",
    "chars": 12777,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {ERC4626Strategy} from"
  },
  {
    "path": "src/test/strategies/FluidARBUSDCStrategy.t.sol",
    "chars": 10828,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {ERC4626Strategy} from"
  },
  {
    "path": "src/test/strategies/SFraxETHStrategy.t.sol",
    "chars": 12470,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \""
  },
  {
    "path": "src/test/strategies/SiUSDStrategy.t.sol",
    "chars": 11118,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {BaseStrategyTest} from \"../BaseStrategyTest.sol\";\nimpor"
  },
  {
    "path": "src/test/strategies/TokeAutoETHStrategy.t.sol",
    "chars": 23638,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n// Adjust these imports to your layout\n\nimport {TokeAutoStrategy"
  },
  {
    "path": "src/test/strategies/TokeAutoUSDStrategy.t.sol",
    "chars": 16011,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n// Adjust these imports to your layout\n\nimport {TokeAutoStrategy"
  },
  {
    "path": "src/test/strategies/WstethMainnetStrategy.t.sol",
    "chars": 32521,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IMYTStrategy} from \"../.."
  },
  {
    "path": "src/test/strategies/WstethOptimismStrategy.t.sol",
    "chars": 24454,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {IMYTStrategy} from \"../.."
  },
  {
    "path": "src/test/strategies/YvUSDCStrategy.t.sol",
    "chars": 2228,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {ERC4626Strategy} from"
  },
  {
    "path": "src/test/strategies/YvWETHStrategy.t.sol",
    "chars": 2098,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../BaseStrategyTest.sol\";\nimport {ERC4626Strategy} from"
  },
  {
    "path": "src/test/strategies/utils/offchain/quotes/stethToWeth.json",
    "chars": 5747,
    "preview": "{\n  \"allowanceTarget\": \"0x0000000000001ff3684f28c67538d4d072c22734\",\n  \"blockNumber\": \"24284935\",\n  \"buyAmount\": \"996896"
  },
  {
    "path": "src/test/strategies/utils/offchain/quotes/wethToWsteth.json",
    "chars": 6198,
    "preview": "{\n  \"allowanceTarget\": \"0x0000000000001ff3684f28c67538d4d072c22734\",\n  \"blockNumber\": \"24284934\",\n  \"buyAmount\": \"815149"
  },
  {
    "path": "src/test/strategies/utils/offchain/quotes/wstethToWeth.json",
    "chars": 6157,
    "preview": "{\n  \"allowanceTarget\": \"0x000000000022d473030f116ddee9f6b43ac78ba3\",\n  \"blockNumber\": \"24737769\",\n  \"buyAmount\": \"122273"
  },
  {
    "path": "src/utils/PermissionedProxy.sol",
    "chars": 2030,
    "preview": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\ncontract PermissionedProxy {\n    address admin;\n    mapping (a"
  },
  {
    "path": "src/utils/Whitelist.sol",
    "chars": 1680,
    "preview": "pragma solidity ^0.8.13;\n\nimport \"../base/Errors.sol\";\nimport \"../interfaces/IWhitelist.sol\";\nimport \"../../lib/openzepp"
  },
  {
    "path": "src/utils/ZeroXSwapVerifier.sol",
    "chars": 12347,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@"
  }
]

About this extraction

This page contains the full source code of the alchemix-finance/v3 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 148 files (1.8 MB), approximately 437.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!