// File: @openzeppelin/contracts/interfaces/IERC4626.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/IERC20.sol";
import "../token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 *
 * _Available since v4.7._
 */
interface IERC4626 is IERC20, IERC20Metadata {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   withdraw execution, and are accounted for during withdraw.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
     * through a redeem call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
     *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
     *   same transaction.
     * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
     *   redemption would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   redeem execution, and are accounted for during redeem.
     * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}

// File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

// File: @openzeppelin/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/utils/ERC721Holder.sol)

pragma solidity ^0.8.0;

import "../IERC721Receiver.sol";

/**
 * @dev Implementation of the {IERC721Receiver} interface.
 *
 * Accepts all token transfers.
 * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
 */
contract ERC721Holder is IERC721Receiver {
    /**
     * @dev See {IERC721Receiver-onERC721Received}.
     *
     * Always returns `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: contracts/interfaces/IAddressProvider.sol



//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.19;

interface IAddressProvider {
    function setLendingMarket(address market) external;

    function getLendingMarket() external view returns (address);

    function setTradingPoolFactory(address tradingPoolFactory) external;

    function getTradingPoolFactory() external view returns (address);

    function setSwapRouter(address swapRouter) external;

    function getSwapRouter() external view returns (address);

    function setGaugeController(address gaugeController) external;

    function getGaugeController() external view returns (address);

    function setLoanCenter(address loancenter) external;

    function getLoanCenter() external view returns (address);

    function setVotingEscrow(address nativeTokenVault) external;

    function getVotingEscrow() external view returns (address);

    function setNativeToken(address nativeToken) external;

    function getNativeToken() external view returns (address);

    function getNativeTokenVesting() external view returns (address);

    function setInterestRate(address interestRate) external;

    function getInterestRate() external view returns (address);

    function setNFTOracle(address nftOracle) external;

    function getNFTOracle() external view returns (address);

    function setTokenOracle(address tokenOracle) external;

    function getTokenOracle() external view returns (address);

    function setFeeDistributor(address feeDistributor) external;

    function getFeeDistributor() external view returns (address);

    function setGenesisNFT(address genesisNFT) external;

    function getGenesisNFT() external view returns (address);

    function setWETH(address weth) external;

    function getWETH() external view returns (address);

    function setBribes(address bribes) external;

    function getBribes() external view returns (address);

    function setLiquidityPairMetadata(address liquidityPairMetadata) external;

    function getLiquidityPairMetadata() external view returns (address);
}

// File: contracts/interfaces/IBribes.sol



//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.19;

interface IBribes {
    event DepositBribe(
        address indexed briber,
        address indexed token,
        address indexed gauge,
        uint256 amount
    );
    event WithdrawBribe(
        address indexed receiver,
        address indexed token,
        address indexed gauge,
        uint256 amount
    );

    event SalvageBribes(
        address indexed token,
        address indexed gauge,
        uint256 indexed epoch,
        uint256 amount
    );

    event ClaimBribes(
        address indexed receiver,
        address indexed token,
        address indexed gauge,
        uint256 tokenId,
        uint256 amount
    );

    function depositBribe(
        address briber,
        address token,
        address gauge,
        uint256 amount
    ) external;

    function withdrawBribe(
        address receiver,
        address token,
        address gauge,
        uint256 amount
    ) external;
}

// File: contracts/interfaces/ILendingMarket.sol



//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.19;

import {Trustus} from "../protocol/Trustus/Trustus.sol";

interface ILendingMarket {
    event Borrow(
        address indexed user,
        address indexed asset,
        address indexed nftAddress,
        uint256[] nftTokenIds,
        uint256 amount
    );

    event Repay(address indexed user, uint256 indexed loanId);

    event CreateLiquidationAuction(
        address indexed user,
        uint256 indexed loanId,
        uint256 bid
    );
    event BidLiquidationAuction(
        address indexed user,
        uint256 indexed loanId,
        uint256 bid
    );

    event ClaimLiquidation(address indexed user, uint256 indexed loanId);

    event CreateLendingPool(
        address indexed lendingPool,
        address indexed colletction,
        address indexed asset
    );

    event SetLendingPool(
        address indexed collection,
        address indexed asset,
        address indexed lendingPool
    );

    function borrow(
        address onBehalfOf,
        address asset,
        uint256 amount,
        address nftAddress,
        uint256[] memory nftTokenIds,
        uint256 genesisNFTId,
        bytes32 request,
        Trustus.TrustusPacket calldata packet
    ) external;

    function repay(uint256 loanId, uint256 amount) external;

    function createLiquidationAuction(
        address onBehalfOf,
        uint256 loanId,
        uint256 bid,
        bytes32 request,
        Trustus.TrustusPacket calldata packet
    ) external;

    function bidLiquidationAuction(
        address onBehalfOf,
        uint256 loanId,
        uint256 bid
    ) external;

    function claimLiquidation(uint256 loanId) external;

    function getLendingPool(
        address collection,
        address asset
    ) external view returns (address);

    function getTVLSafeguard() external view returns (uint256);
}

// File: contracts/interfaces/ILoanCenter.sol



//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.19;

import {DataTypes} from "../libraries/types/DataTypes.sol";
import {Trustus} from "../protocol/Trustus/Trustus.sol";

interface ILoanCenter {
    function createLoan(
        address owner,
        address lendingPool,
        uint256 amount,
        uint256 genesisNFTId,
        address nftAddress,
        uint256[] memory nftTokenIds,
        uint256 borrowRate
    ) external returns (uint256);

    function getLoan(
        uint256 loanId
    ) external view returns (DataTypes.LoanData memory);

    function getLoanState(
        uint256 loanId
    ) external view returns (DataTypes.LoanState);

    function getLoanLiquidationData(
        uint256 loanId
    ) external view returns (DataTypes.LoanLiquidationData memory);

    function getLoanAuctioneerFee(
        uint256 loanId
    ) external view returns (uint256);

    function repayLoan(uint256 loanId) external;

    function liquidateLoan(uint256 loanId) external;

    function auctionLoan(uint256 loanId, address user, uint256 bid) external;

    function updateLoanAuctionBid(
        uint256 loanId,
        address user,
        uint256 bid
    ) external;

    function getLoansCount() external view returns (uint256);

    function getNFTLoanId(
        address nftAddress,
        uint256 nftTokenID
    ) external view returns (uint256);

    function getLoanLendingPool(uint256 loanId) external view returns (address);

    function getLoanMaxDebt(
        uint256 loanId,
        uint256 tokensPrice
    ) external view returns (uint256);

    function getLoanDebt(uint256 loanId) external view returns (uint256);

    function getLoanInterest(uint256 loanId) external view returns (uint256);

    function getLoanTokenIds(
        uint256 loanId
    ) external view returns (uint256[] memory);

    function getLoanCollectionAddress(
        uint256 loanId
    ) external view returns (address);

    function updateLoanDebtTimestamp(
        uint256 loanId,
        uint256 newDebtTimestamp
    ) external;

    function updateLoanAmount(uint256 loanId, uint256 newAmount) external;

    function getCollectionLiquidationThreshold(
        address collection
    ) external view returns (uint256);

    function getCollectionMaxLTV(
        address collection
    ) external view returns (uint256);
}

// File: contracts/interfaces/ISwapRouter.sol



//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.19;

interface ISwapRouter {
    function approveTradingPool(address token, address tradingPool) external;

    function swap(
        address buyPool,
        address sellPool,
        uint256[] memory buyNftIds,
        uint256 maximumBuyPrice,
        uint256[] memory sellNftIds,
        uint256[] memory sellLps,
        uint256 minimumSellPrice
    ) external returns (uint256);
}

// File: contracts/interfaces/ITradingPool.sol



//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.19;

import {DataTypes} from "../libraries/types/DataTypes.sol";

interface ITradingPool {
    event AddLiquidity(
        address indexed user,
        uint256 indexed id,
        DataTypes.LPType indexed lpType,
        uint256[] nftIds,
        uint256 tokenAmount,
        uint256 spotPrice,
        address curve,
        uint256 delta,
        uint256 fee
    );
    event RemoveLiquidity(address indexed user, uint256 indexed lpId);

    event Buy(address indexed user, uint256[] nftIds, uint256 price);

    event Sell(address indexed user, uint256[] nftIds, uint256 price);

    event SetLpSpotPrice(
        address indexed user,
        uint256 indexed lpId,
        uint256 spotPrice
    );

    event SetLpPricingCurve(
        address indexed user,
        uint256 indexed lpId,
        address curve,
        uint256 delta
    );

    event SetLpFee(address indexed user, uint256 indexed lpId, uint256 fee);

    function addLiquidity(
        address receiver,
        DataTypes.LPType lpType,
        uint256[] memory nftIds,
        uint256 tokenAmount,
        uint256 initialPrice,
        address curve,
        uint256 delta,
        uint256 fee
    ) external;

    function removeLiquidity(uint256 lpId) external;

    function removeLiquidityBatch(uint256[] memory lpIds) external;

    function buy(
        address onBehalfOf,
        uint256[] memory nftIds,
        uint256 maximumPrice
    ) external returns (uint256);

    function sell(
        address onBehalfOf,
        uint256[] memory nftIds,
        uint256[] memory liquidityPairs,
        uint256 minimumPrice
    ) external returns (uint256);

    function getLP(
        uint256 lpId
    ) external view returns (DataTypes.LiquidityPair memory);

    function getLpCount() external view returns (uint256);

    function nftToLp(uint256 nftId) external view returns (uint256);

    function getToken() external view returns (address);

    function getNFT() external view returns (address);
}

// File: contracts/interfaces/IWETH.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.19;

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

/// @title Interface for WETH9
interface IWETH is IERC20 {
    /// @notice Deposit ether to get wrapped ether
    function deposit() external payable;

    /// @notice Withdraw wrapped ether to get ether
    function withdraw(uint256) external;
}

// File: contracts/libraries/types/DataTypes.sol



//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.19;

import {Trustus} from "../../protocol/Trustus/Trustus.sol";

/// @title DataTypes library
/// @author leNFT
/// @notice Defines the data types used in the protocol
/// @dev Library with the data types used in the protocol
library DataTypes {
    /// @notice Struct to store the price data of an array of assets from the same collection
    /// @param collection The address of the collection
    /// @param tokenIds The tokenIds of the assets
    /// @param amount The price of the assets
    struct AssetsPrice {
        address collection;
        uint256[] tokenIds;
        uint256 amount;
    }

    /// @notice Struct to store the risk parameters for a collection
    /// @param maxLTV The maximum loan to value ratio
    /// @param liquidationThreshold The threshold at which the loan can be liquidated
    struct CollectionRiskParameters {
        uint16 maxLTV;
        uint16 liquidationThreshold;
    }

    /// @notice Enum of the liquidity pair types
    /// @dev Trade: Can buy and sell and price can increase and decrease
    /// @dev TradeUp: Can buy and sell and price can only increase
    /// @dev TradeDown: Can buy and sell and price can only decrease
    /// @dev Buy: Can only buy (price will only decrease)
    /// @dev Sell: Can only sell (price will only increase)
    enum LPType {
        Trade,
        TradeUp,
        TradeDown,
        Buy,
        Sell
    }

    /// @notice Struct to store the liquidity pair data
    /// @param nftIds The tokenIds of the assets
    /// @param tokenAmount The amount of tokens in the liquidity pair
    /// @param spotPrice The spot price of the liquidity pair
    /// @param curve The address of the curve
    /// @param delta The delta of the curve
    /// @param fee The fee for the buy/sell trades
    /// @param lpType The type of liquidity pair
    struct LiquidityPair {
        uint256[] nftIds;
        uint128 tokenAmount;
        uint128 spotPrice;
        uint128 delta;
        address curve;
        uint16 fee;
        LPType lpType;
    }

    /// @notice Struct serving as a pointer from an NFT to a liquidity pair
    /// @param liquidityPair The index of the liquidity pair
    /// @param index The index of the NFT in the liquidity pair
    struct NftToLp {
        uint128 liquidityPair;
        uint128 index;
    }

    /// @notice Struct to store the working balance in gauges
    /// @param amount The amount of tokens
    /// @param weight The weight of the tokens
    /// @param timestamp The timestamp of the update
    struct WorkingBalance {
        uint128 amount;
        uint128 weight;
        uint40 timestamp;
    }

    /// @notice Struct to store the locked balance in the voting escrow
    /// @param amount The amount of tokens
    /// @param end The timestamp of the end of the lock
    struct LockedBalance {
        uint128 amount;
        uint40 end;
    }

    /// @notice Struct to store an abstract point in a weight curve
    /// @param bias The bias of the point
    /// @param slope The slope of the point
    /// @param timestamp The timestamp of the point
    struct Point {
        uint128 bias;
        uint128 slope;
        uint40 timestamp;
    }

    /// @notice Enum of all the states a loan can be in
    /// @dev State change flow: None -> Active -> Repaid -> Auction -> Liquidated
    /// @dev None (Default Value): We need a default that is not 'Active' - this is the zero value
    /// @dev Active: The loan has been initialized; funds have been delivered to the borrower and the collateral is held.
    /// @dev Repaid: The loan has been repaid; and the collateral has been returned to the borrower.
    /// @dev Auctioned: The loan's collateral has been auctioned off and its in the process of being liquidated.
    /// @dev Liquidated: The loan's collateral was claimed by the liquidator.
    enum LoanState {
        None,
        Active,
        Repaid,
        Auctioned,
        Liquidated
    }

    /// @notice Stores the data for a loan
    /// @param owner The owner of the loan
    /// @param amount The amount borrowed
    /// @param nftTokenIds The tokenIds of the NFT collaterals
    /// @param nftAsset The address of the NFT asset
    /// @param borrowRate The interest rate at which the loan was written
    /// @param initTimestamp The timestamp for the initial creation of the loan
    /// @param debtTimestamp The timestamp for debt computation
    /// @param pool The address of the lending pool associated with the loan
    /// @param genesisNFTId The genesis NFT id for the boost (0 if not used)
    /// @param state The current state of the loan
    struct LoanData {
        address owner;
        uint256 amount;
        uint256[] nftTokenIds;
        address nftAsset;
        uint16 borrowRate;
        uint40 initTimestamp;
        uint40 debtTimestamp;
        address pool;
        uint16 genesisNFTId;
        LoanState state;
    }

    /// @notice Stores the data for a loan auction
    /// @param auctioneer The address of the auctioneer (user who first auctioned the loan)
    /// @param liquidator The address of the liquidator (user with the highest bid)
    /// @param auctionStartTimestamp The timestamp for the start of the auction
    /// @param auctionMaxBid The maximum bid for the auction
    struct LoanLiquidationData {
        address auctioneer;
        address liquidator;
        uint40 auctionStartTimestamp;
        uint256 auctionMaxBid;
    }

    /// @notice Struct to store mint details for each Genesis NFT
    /// @param timestamp The timestamp of the mint
    /// @param locktime The locktime of the mint
    /// @param lpAmount The amount of LP tokens minted
    struct MintDetails {
        uint40 timestamp;
        uint40 locktime;
        uint128 lpAmount;
    }

    /// @notice Struct to store the parameters for a borrow call
    /// @param caller The caller of the borrow function
    /// @param onBehalfOf The address of the user on whose behalf the caller is borrowing
    /// @param asset The address of the asset being borrowed
    /// @param amount The amount of the asset being borrowed
    /// @param nftAddress The address of the NFT asset
    /// @param nftTokenIds The tokenIds of the NFT collaterals
    /// @param genesisNFTId The genesis NFT id for the boost (0 if not used)
    /// @param request The request ID for the borrow
    /// @param packet The Trustus packet for the borrow
    struct BorrowParams {
        address caller;
        address onBehalfOf;
        address asset;
        uint256 amount;
        address nftAddress;
        uint256[] nftTokenIds;
        uint256 genesisNFTId;
        bytes32 request;
        Trustus.TrustusPacket packet;
    }

    /// @notice Struct to store the parameters for a repay call
    /// @param caller The caller of the repay function
    /// @param loanId The ID of the loan being repaid
    /// @param amount The amount of debt being repaid
    struct RepayParams {
        address caller;
        uint256 loanId;
        uint256 amount;
    }

    /// @notice Struct to store the parameters for a create auction (liquidate) call
    /// @param caller The caller of the create auction function
    /// @param onBehalfOf The address of the user on whose behalf the caller is liquidating
    /// @param loanId The ID of the loan being liquidated
    /// @param bid The bid for the auction
    /// @param request The request ID for the liquidation
    /// @param packet The Trustus packet for the liquidation
    struct CreateAuctionParams {
        address caller;
        address onBehalfOf;
        uint256 loanId;
        uint256 bid;
        bytes32 request;
        Trustus.TrustusPacket packet;
    }

    /// @notice Struct to store the parameters for an auction bid call
    /// @param caller The caller of the auction bid function
    /// @param onBehalfOf The address of the user on whose behalf the caller is bidding
    /// @param loanId The ID of the loan being liquidated
    /// @param bid The bid for the auction
    struct BidAuctionParams {
        address caller;
        address onBehalfOf;
        uint256 loanId;
        uint256 bid;
    }

    /// @notice Struct to store the parameters for a claim liquidation call
    /// @param loanId The ID of the loan whose liquidation is being claimed
    struct ClaimLiquidationParams {
        uint256 loanId;
    }

    /// @notice Struct to store the parameters a user's VestingParams
    /// @param timestamp The timestamp of the vesting start
    /// @param period The vesting period
    /// @param cliff The vesting cliff
    /// @param amount The amount of tokens to vest
    struct VestingParams {
        uint256 timestamp;
        uint256 period;
        uint256 cliff;
        uint256 amount;
    }

    /// @notice Struct to store the parameters for the Genesis NFT balancer pool
    /// @param poolId The ID of the balancer pool
    /// @param pool The address of the balancer pool
    /// @param vault The address of the balancer vault
    /// @param queries The address of the balancer queries contract
    struct BalancerDetails {
        bytes32 poolId;
        address pool;
        address vault;
        address queries;
    }
}

// File: contracts/protocol/Trustus/Trustus.sol



// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;

/// @title Trustus
/// @author zefram.eth
/// @notice Trust-minimized method for accessing offchain data onchain
abstract contract Trustus {
    /// -----------------------------------------------------------------------
    /// Structs
    /// -----------------------------------------------------------------------

    /// @param v Part of the ECDSA signature
    /// @param r Part of the ECDSA signature
    /// @param s Part of the ECDSA signature
    /// @param request Identifier for verifying the packet is what is desired
    /// , rather than a packet for some other function/contract
    /// @param deadline The Unix timestamp (in seconds) after which the packet
    /// should be rejected by the contract
    /// @param payload The payload of the packet
    struct TrustusPacket {
        uint8 v;
        bytes32 r;
        bytes32 s;
        bytes32 request;
        uint256 deadline;
        bytes payload;
    }

    /// -----------------------------------------------------------------------
    /// Immutable parameters
    /// -----------------------------------------------------------------------

    /// @notice The chain ID used by EIP-712
    // solhint-disable-next-line var-name-mixedcase
    uint256 internal immutable INITIAL_CHAIN_ID;

    /// @notice The domain separator used by EIP-712
    // solhint-disable-next-line var-name-mixedcase
    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    /// -----------------------------------------------------------------------
    /// Storage variables
    /// -----------------------------------------------------------------------

    /// @notice Records whether an address is trusted as a packet provider
    /// @dev provider => value
    mapping(address => bool) internal isTrusted;

    /// -----------------------------------------------------------------------
    /// Modifiers
    /// -----------------------------------------------------------------------

    /// @notice Verifies whether a packet is valid and returns the result.
    /// Will revert if the packet is invalid.
    /// @dev The deadline, request, and signature are verified.
    /// @param request The identifier for the requested payload
    /// @param packet The packet provided by the offchain data provider
    modifier verifyPacket(bytes32 request, TrustusPacket calldata packet) {
        _verifyPacket(request, packet);
        _;
    }

    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor() {
        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator();
    }

    /// -----------------------------------------------------------------------
    /// Packet verification
    /// -----------------------------------------------------------------------

    /// @notice Verifies whether a packet is valid and returns the result.
    /// @dev The deadline, request, and signature are verified.
    /// @param request The identifier for the requested payload
    /// @param packet The packet provided by the offchain data provider
    function _verifyPacket(
        bytes32 request,
        TrustusPacket calldata packet
    ) internal view virtual {
        // verify deadline
        require(block.timestamp < packet.deadline, "T:V:DEADLINE_EXCEEDED");

        // verify request
        require(request == packet.request, "T:V:INVALID_REQUEST");

        // verify signature
        address recoveredAddress = ecrecover(
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(
                        abi.encode(
                            keccak256(
                                "VerifyPacket(bytes32 request,uint256 deadline,bytes payload)"
                            ),
                            packet.request,
                            packet.deadline,
                            keccak256(packet.payload)
                        )
                    )
                )
            ),
            packet.v,
            packet.r,
            packet.s
        );

        require(
            recoveredAddress != address(0) && isTrusted[recoveredAddress],
            "T:V:INVALID_SIGNATURE"
        );
    }

    /// @notice Sets the trusted status of an offchain data provider.
    /// @param signer The data provider's ECDSA public key as an Ethereum address
    /// @param isTrusted_ The desired trusted status to set
    function _setIsTrusted(address signer, bool isTrusted_) internal virtual {
        isTrusted[signer] = isTrusted_;
    }

    function _isTrusted(address signer) internal view virtual returns (bool) {
        return (isTrusted[signer]);
    }

    /// -----------------------------------------------------------------------
    /// EIP-712 compliance
    /// -----------------------------------------------------------------------

    /// @notice The domain separator used by EIP-712
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return
            block.chainid == INITIAL_CHAIN_ID
                ? INITIAL_DOMAIN_SEPARATOR
                : _computeDomainSeparator();
    }

    /// @notice Computes the domain separator used by EIP-712
    function _computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256(
                        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                    ),
                    keccak256("leNFT"),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }
}

// File: contracts/protocol/WETHGateway.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.19;

import {IWETH} from "../interfaces/IWETH.sol";
import {ILendingMarket} from "../interfaces/ILendingMarket.sol";
import {DataTypes} from "../libraries/types/DataTypes.sol";
import {ILoanCenter} from "../interfaces/ILoanCenter.sol";
import {IBribes} from "../interfaces/IBribes.sol";
import {ISwapRouter} from "../interfaces/ISwapRouter.sol";
import {ITradingPool} from "../interfaces/ITradingPool.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {Trustus} from "./Trustus/Trustus.sol";
import {IAddressProvider} from "../interfaces/IAddressProvider.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";

/// @title WETHGateway Contract
/// @author leNFT
/// @notice This contract is the proxy for ETH interactions with the leNFT protocol
/// @dev Interacts with the WETH in order to wrap and unwrap ETH
contract WETHGateway is ERC721Holder {
    IAddressProvider private immutable _addressProvider;
    IWETH private immutable _weth;

    modifier lendingPoolETH(address lendingPool) {
        _requireLendingPoolETH(lendingPool);
        _;
    }

    modifier loanPoolETH(uint256 loanId) {
        _requireLoanPoolETH(loanId);
        _;
    }

    modifier tradingPoolETH(address tradingPool) {
        _requireTradingPoolETH(tradingPool);
        _;
    }

    /// @notice Constructor for the WETHGateway contract
    /// @param addressProvider The address of the addressProvider contract
    /// @param weth The address of the WETH contract
    constructor(IAddressProvider addressProvider, IWETH weth) {
        _addressProvider = addressProvider;
        _weth = weth;
    }

    /// @notice Deposit ETH in a wETH lending pool
    /// @param lendingPool Lending pool to deposit into
    function depositLendingPool(
        address lendingPool
    ) external payable lendingPoolETH(lendingPool) {
        IWETH weth = _weth;
        // Deposit and approve WETH
        weth.deposit{value: msg.value}();
        weth.approve(lendingPool, msg.value);

        IERC4626(lendingPool).deposit(msg.value, msg.sender);
    }

    /// @notice Withdraw ETH from a WETH lending pool
    /// @param lendingPool Lending pool to withdraw from
    /// @param amount Amount of ETH to be withdrawn
    function withdrawLendingPool(
        address lendingPool,
        uint256 amount
    ) external lendingPoolETH(lendingPool) {
        IERC4626(lendingPool).withdraw(amount, address(this), msg.sender);
        _weth.withdraw(amount);

        (bool sent, ) = msg.sender.call{value: amount}("");
        require(sent, "ETHG:WLP:ETH_TRANSFER_FAILED");
    }

    /// @notice Borrow ETH from a WETH lending pool while an NFT as collateral
    /// @dev NFT approval needs to be given to the LoanCenter contract
    /// @param amount Amount of ETH to be borrowed
    /// @param nftAddress Address of the NFT collateral
    /// @param nftTokenIds Token ids of the NFT(s) collateral
    /// @param genesisNFTId Token id of the genesis NFT to be used for the loan (0 if none)
    /// @param request ID of the collateral price request sent by the trusted server
    /// @param packet Signed collateral price request sent by the trusted server
    function borrow(
        uint256 amount,
        address nftAddress,
        uint256[] calldata nftTokenIds,
        uint256 genesisNFTId,
        bytes32 request,
        Trustus.TrustusPacket calldata packet
    ) external {
        ILendingMarket market = ILendingMarket(
            _addressProvider.getLendingMarket()
        );

        // Transfer the collateral to the WETH Gateway
        for (uint256 i = 0; i < nftTokenIds.length; i++) {
            IERC721(nftAddress).safeTransferFrom(
                msg.sender,
                address(this),
                nftTokenIds[i]
            );

            // Approve the collateral to be moved by the market
            IERC721(nftAddress).approve(address(market), nftTokenIds[i]);
        }

        // SLOAD the WETH address to save gas
        IWETH weth = _weth;

        market.borrow(
            msg.sender,
            address(weth),
            amount,
            nftAddress,
            nftTokenIds,
            genesisNFTId,
            request,
            packet
        );

        weth.withdraw(amount);

        (bool sent, ) = msg.sender.call{value: amount}("");
        require(sent, "ETHG:B:ETH_TRANSFER_FAILED");
    }

    /// @notice Repay an an active loan with ETH
    /// @param loanId The ID of the loan to be paid
    function repay(uint256 loanId) external payable {
        ILoanCenter loanCenter = ILoanCenter(_addressProvider.getLoanCenter());
        address pool = loanCenter.getLoanLendingPool(loanId);

        ILendingMarket market = ILendingMarket(
            _addressProvider.getLendingMarket()
        );

        // SLOAD the WETH address to save gas
        IWETH weth = _weth;

        require(
            IERC4626(pool).asset() == address(weth),
            "ETHG:R:UNDERLYING_NOT_WETH"
        );

        // If we are repaying an auctioned loan we also need to pay the auctineer fee
        uint256 auctioneerFee;
        if (loanCenter.getLoanState(loanId) == DataTypes.LoanState.Auctioned) {
            auctioneerFee = loanCenter.getLoanAuctioneerFee(loanId);
            require(auctioneerFee < msg.value, "ETHG:R:NO_AUCTIONEER_FEE");
            weth.approve(address(market), auctioneerFee);
        }

        // Deposit and approve WETH
        weth.deposit{value: msg.value}();
        weth.approve(pool, msg.value - auctioneerFee);

        // Repay loan
        market.repay(loanId, msg.value - auctioneerFee);
    }

    /// @notice Deposit ETH and/or NFTs into a trading pool to provide liquidity
    /// @param pool The trading pool address
    /// @param lpType The type of LP
    /// @param nftIds Token ids of the NFTs to deposit
    /// @param initialPrice The initial price of the liquidity provider tokens
    /// @param curve The curve used to calculate the price of the LP tokens
    /// @param delta The minimum price change to update the curve
    /// @param fee The fee charged on trades in the pool
    function depositTradingPool(
        address pool,
        DataTypes.LPType lpType,
        uint256[] calldata nftIds,
        uint256 initialPrice,
        address curve,
        uint256 delta,
        uint256 fee
    ) external payable tradingPoolETH(pool) {
        // Transfer the NFTs to the WETH Gateway and approve them for use
        IERC721 tradingPoolNFT = IERC721(ITradingPool(pool).getNFT());
        if (nftIds.length > 0) {
            for (uint i = 0; i < nftIds.length; i++) {
                tradingPoolNFT.safeTransferFrom(
                    msg.sender,
                    address(this),
                    nftIds[i]
                );
            }
            tradingPoolNFT.setApprovalForAll(pool, true);
        }

        // Deposit and approve WETH
        if (msg.value > 0) {
            // SLOAD the WETH address to save gas
            IWETH weth = _weth;

            weth.deposit{value: msg.value}();
            weth.approve(pool, msg.value);
        }

        ITradingPool(pool).addLiquidity(
            msg.sender,
            lpType,
            nftIds,
            msg.value,
            initialPrice,
            curve,
            delta,
            fee
        );
    }

    /// @notice Withdraw liquidity from a trading pool
    /// @param pool The trading pool address
    /// @param lpId The ID of the liquidity provider tokens to withdraw
    function withdrawTradingPool(
        address pool,
        uint256 lpId
    ) external tradingPoolETH(pool) {
        // Send LP NFT to this contract
        IERC721(pool).safeTransferFrom(msg.sender, address(this), lpId);

        // Get LP info so we can send the correct amounts back
        DataTypes.LiquidityPair memory lp = ITradingPool(pool).getLP(lpId);

        // Remove liquidity
        ITradingPool(pool).removeLiquidity(lpId);

        // Send NFTs back to the user
        IERC721 tradingPoolNFT = IERC721(ITradingPool(pool).getNFT());
        for (uint i = 0; i < lp.nftIds.length; i++) {
            tradingPoolNFT.safeTransferFrom(
                address(this),
                msg.sender,
                lp.nftIds[i]
            );
        }

        // Send ETH back to the user
        _weth.withdraw(lp.tokenAmount);

        (bool sent, ) = msg.sender.call{value: lp.tokenAmount}("");
        require(sent, "ETHG:WTP:ETH_TRANSFER_FAILED");
    }

    /// @notice Withdraws liquidity from a trading pool for a batch of liquidity pairs.
    /// @param pool The address of the trading pool.
    /// @param lpIds The array of liquidity pair ids to withdraw.
    function withdrawBatchTradingPool(
        address pool,
        uint256[] calldata lpIds
    ) external tradingPoolETH(pool) {
        uint256 totalAmount;
        uint256[][] memory nftIds = new uint256[][](lpIds.length);

        // Send LP NFTs to this contract
        for (uint i = 0; i < lpIds.length; i++) {
            IERC721(pool).safeTransferFrom(msg.sender, address(this), lpIds[i]);

            // Get LP info so we can send the correct amounts back
            DataTypes.LiquidityPair memory lp = ITradingPool(pool).getLP(
                lpIds[i]
            );

            // Add up the total amount of ETH to withdraw
            totalAmount += lp.tokenAmount;

            // Add up the total amount of NFTs to withdraw
            nftIds[i] = lp.nftIds;
        }

        // Remove liquidity in batch
        ITradingPool(pool).removeLiquidityBatch(lpIds);

        // Send NFTs back to the user
        IERC721 tradingPoolNFT = IERC721(ITradingPool(pool).getNFT());
        for (uint a = 0; a < nftIds.length; a++) {
            for (uint b = 0; b < nftIds[a].length; b++) {
                tradingPoolNFT.safeTransferFrom(
                    address(this),
                    msg.sender,
                    nftIds[a][b]
                );
            }
        }

        // Send ETH back to the user
        _weth.withdraw(totalAmount);

        (bool sent, ) = msg.sender.call{value: totalAmount}("");
        require(sent, "ETHG:WBTP:ETH_TRANSFER_FAILED");
    }

    /// @notice Buys NFT from a trading pool by depositing WETH and specifying the NFT ids and maximum price to pay.
    /// @param pool The address of the trading pool.
    /// @param nftIds The array of NFT ids to buy.
    /// @param maximumPrice The maximum amount of ETH to pay for the purchase.
    function buy(
        address pool,
        uint256[] calldata nftIds,
        uint256 maximumPrice
    ) external payable tradingPoolETH(pool) {
        require(msg.value == maximumPrice, "ETHG:B:VALUE_NOT_MAXIMUM_PRICE");

        // SLOAD the WETH address to save gas
        IWETH weth = _weth;

        // Deposit and approve WETH
        weth.deposit{value: msg.value}();
        weth.approve(pool, msg.value);

        uint256 finalPrice = ITradingPool(pool).buy(
            msg.sender,
            nftIds,
            maximumPrice
        );

        // Send ETH back to the user
        if (msg.value > finalPrice) {
            weth.withdraw(msg.value - finalPrice);

            (bool sent, ) = msg.sender.call{value: msg.value - finalPrice}("");
            require(sent, "ETHG:B:ETH_TRANSFER_FAILED");
        }
    }

    /// @notice Sells NFTs against a pool's liquidity pairs, specifying the NFT ids, liquidity pairs, and minimum price expected.
    /// @param pool The address of the trading pool.
    /// @param nftIds The array of NFT ids to sell.
    /// @param liquidityPairs The array of liquidity pair to sell the NFTs against.
    /// @param minimumPrice The minimum amount of ETH to receive for the sale.
    function sell(
        address pool,
        uint256[] calldata nftIds,
        uint256[] calldata liquidityPairs,
        uint256 minimumPrice
    ) external tradingPoolETH(pool) {
        // Send NFTs to this contract and approve them for pool use
        IERC721 tradingPoolNFT = IERC721(ITradingPool(pool).getNFT());
        for (uint i = 0; i < nftIds.length; i++) {
            tradingPoolNFT.safeTransferFrom(
                msg.sender,
                address(this),
                nftIds[i]
            );
        }
        tradingPoolNFT.setApprovalForAll(pool, true);

        // Sell NFTs
        uint256 finalPrice = ITradingPool(pool).sell(
            address(this),
            nftIds,
            liquidityPairs,
            minimumPrice
        );

        // Send ETH back to the user
        _weth.withdraw(finalPrice);

        (bool sent, ) = msg.sender.call{value: finalPrice}("");
        require(sent, "ETHG:S:ETH_TRANSFER_FAILED");
    }

    /// @notice Swaps NFTs between two trading pools, with one pool acting as the buyer and the other as the seller.
    /// @param buyPool The address of the buying trading pool.
    /// @param sellPool The address of the selling trading pool.
    /// @param buyNftIds The array of NFT ids to buy.
    /// @param maximumBuyPrice The maximum amount of ETH to pay for the purchase.
    /// @param sellNftIds The array of NFT ids to sell.
    /// @param sellLps The array of liquidity pair to sell the NFTs against.
    /// @param minimumSellPrice The minimum amount of ETH to receive for the sale.
    function swap(
        address buyPool,
        address sellPool,
        uint256[] calldata buyNftIds,
        uint256 maximumBuyPrice,
        uint256[] calldata sellNftIds,
        uint256[] calldata sellLps,
        uint256 minimumSellPrice
    ) external payable {
        // SLOAD the WETH address to save gas
        IWETH weth = _weth;

        require(
            ITradingPool(buyPool).getToken() == address(weth),
            "ETHG:S:BUY_UNDERLYING_NOT_WETH"
        );
        require(
            ITradingPool(sellPool).getToken() == address(weth),
            "ETHG:S:SELL_UNDERLYING_NOT_WETH"
        );
        // Make sure the msg.value covers the swap
        if (maximumBuyPrice > minimumSellPrice) {
            require(
                msg.value == maximumBuyPrice - minimumSellPrice,
                "ETHG:S:INVALID_VALUE"
            );

            // Deposit and approve WETH
            weth.deposit{value: msg.value}();
            weth.approve(_addressProvider.getSwapRouter(), msg.value);
        }

        // avoid stack too deep
        {
            // Send NFTs to this contract and approve them for pool use
            IERC721 sellPoolNFT = IERC721(ITradingPool(sellPool).getNFT());
            for (uint i = 0; i < sellNftIds.length; i++) {
                sellPoolNFT.safeTransferFrom(
                    msg.sender,
                    address(this),
                    sellNftIds[i]
                );
            }
            sellPoolNFT.setApprovalForAll(sellPool, true);
        }

        // Swap
        uint256 returnedAmount = ISwapRouter(_addressProvider.getSwapRouter())
            .swap(
                buyPool,
                sellPool,
                buyNftIds,
                maximumBuyPrice,
                sellNftIds,
                sellLps,
                minimumSellPrice
            );

        // Send NFTs back to the user
        IERC721 buyPoolNFT = IERC721(ITradingPool(buyPool).getNFT());
        for (uint i = 0; i < buyNftIds.length; i++) {
            buyPoolNFT.safeTransferFrom(
                address(this),
                msg.sender,
                buyNftIds[i]
            );
        }

        // Send ETH back to the user
        if (returnedAmount > 0) {
            weth.withdraw(returnedAmount);
            (bool sent, ) = msg.sender.call{value: returnedAmount}("");
            require(sent, "ETHG:S:ETH_TRANSFER_FAILED");
        }
    }

    /// @notice Deposits ETH into the bribe contract to be used for bribing.
    /// @dev Bribe is applied to the next epoch
    /// @param gauge The address of the gauge to bribe.
    function depositBribe(address gauge) external payable {
        address bribes = _addressProvider.getBribes();
        // SLOAD the WETH address to save gas
        IWETH weth = _weth;
        weth.deposit{value: msg.value}();
        weth.approve(bribes, msg.value);
        IBribes(bribes).depositBribe(
            msg.sender,
            address(weth),
            gauge,
            msg.value
        );
    }

    /// @notice Liquidates a loan.
    /// @param loanId The id of the loan to liquidate.
    /// @param request The request id of the loan to liquidate.
    /// @param packet The Trustus packet of the loan to liquidate.
    function createLiquidationAuction(
        uint256 loanId,
        bytes32 request,
        Trustus.TrustusPacket calldata packet
    ) external payable loanPoolETH(loanId) {
        address lendingMarket = _addressProvider.getLendingMarket();
        // SLOAD the WETH address to save gas
        IWETH weth = _weth;
        // Deposit and approve WETH
        weth.deposit{value: msg.value}();
        weth.approve(lendingMarket, msg.value);

        // Create auction
        ILendingMarket(lendingMarket).createLiquidationAuction(
            msg.sender,
            loanId,
            msg.value,
            request,
            packet
        );
    }

    /// @notice Bids on a liquidation auction.
    /// @param loanId The id of the loan to bid on.
    function bidLiquidationAuction(
        uint256 loanId
    ) external payable loanPoolETH(loanId) {
        address lendingMarket = _addressProvider.getLendingMarket();
        // SLOAD the WETH address to save gas
        IWETH weth = _weth;
        // Deposit and approve WETH
        weth.deposit{value: msg.value}();
        weth.approve(lendingMarket, msg.value);

        // Bid on auction
        ILendingMarket(lendingMarket).bidLiquidationAuction(
            msg.sender,
            loanId,
            msg.value
        );
    }

    function _requireLendingPoolETH(address lendingPool) internal view {
        require(
            IERC4626(lendingPool).asset() == address(_weth),
            "ETHG:UNDERLYING_NOT_WETH"
        );
    }

    function _requireLoanPoolETH(uint256 loanId) internal view {
        // Make sure the loan pool asset is WETH
        require(
            IERC4626(
                ILoanCenter(_addressProvider.getLoanCenter())
                    .getLoanLendingPool(loanId)
            ).asset() == address(_weth),
            "ETHG:UNDERLYING_NOT_WETH"
        );
    }

    function _requireTradingPoolETH(address tradingPool) internal view {
        require(
            ITradingPool(tradingPool).getToken() == address(_weth),
            "ETHG:UNDERLYING_NOT_WETH"
        );
    }

    // Receive ETH function: Intended to receive ETH from WETH contract
    receive() external payable {
        require(msg.sender == address(_weth), "ETHG:RECEIVE:NOT_WETH");
    }

    /**
     * @dev Revert fallback calls
     */
    fallback() external payable {
        revert("ETHG:F:INVALID_CALL");
    }
}