ETH Price: $1,943.38 (-2.25%)
 

Overview

ETH Balance

0.00000001 ETH

Eth Value

Less Than $0.01 (@ $1,943.38/ETH)

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

> 10 Internal Transactions found.

Latest 11 internal transactions

Advanced mode:
Parent Transaction Hash Method Block
From
To
Transfer242915922026-01-22 16:43:1131 days ago1769100191
0xddE4E032...f1A621B9F
0 ETH
Transfer242915902026-01-22 16:42:4731 days ago1769100167
0xddE4E032...f1A621B9F
0 ETH
Transfer242915892026-01-22 16:42:3531 days ago1769100155
0xddE4E032...f1A621B9F
0 ETH
Transfer242915882026-01-22 16:42:2331 days ago1769100143
0xddE4E032...f1A621B9F
0 ETH
Transfer242915872026-01-22 16:42:1131 days ago1769100131
0xddE4E032...f1A621B9F
0 ETH
Transfer242915862026-01-22 16:41:5931 days ago1769100119
0xddE4E032...f1A621B9F
0 ETH
Transfer242915852026-01-22 16:41:4731 days ago1769100107
0xddE4E032...f1A621B9F
0 ETH
Transfer242915842026-01-22 16:41:3531 days ago1769100095
0xddE4E032...f1A621B9F
0 ETH
Transfer242915832026-01-22 16:41:2331 days ago1769100083
0xddE4E032...f1A621B9F
0 ETH
Transfer242915822026-01-22 16:41:1131 days ago1769100071
0xddE4E032...f1A621B9F
0 ETH
0x3d602d80242915812026-01-22 16:40:5931 days ago1769100059  Contract Creation0 ETH
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Minimal Proxy Contract for 0xf755c383b31f28dbdf4a6a03da4f007abdaf6261

Contract Name:
GlueERC721

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 22 : GlueERC721.sol
// SPDX-License-Identifier: BUSL-1.1
// https://github.com/glue-finance/glue/blob/main/LICENCE.txt

/**
 
 ██████╗ ██╗     ██╗   ██╗███████╗ 
██╔════╝ ██║     ██║   ██║██╔════╝ 
██║  ███╗██║     ██║   ██║█████╗   
██║   ██║██║     ██║   ██║██╔══╝   
╚██████╔╝███████╗╚██████╔╝███████╗ 
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝ 
██╗   ██╗ ██████╗ ██╗   ██╗██████╗ 
╚██╗ ██╔╝██╔═══██╗██║   ██║██╔══██╗
 ╚████╔╝ ██║   ██║██║   ██║██████╔╝
  ╚██╔╝  ██║   ██║██║   ██║██╔══██╗
   ██║   ╚██████╔╝╚██████╔╝██║  ██║
   ╚═╝    ╚═════╝  ╚═════╝ ╚═╝  ╚═╝
███╗   ██╗███████╗████████╗        
████╗  ██║██╔════╝╚══██╔══╝        
██╔██╗ ██║█████╗     ██║           
██║╚██╗██║██╔══╝     ██║           
██║ ╚████║██║        ██║           
╚═╝  ╚═══╝╚═╝        ╚═╝           
 
@title Glue V1 for Enumerable ERC721s
@author @BasedToschi
@notice A comprehensive protocol for making enumerable ERC721 NFT collections "sticky" through the Glue Protocol infrastructure
@dev This contract implements the core functionality of the Glue Protocol for NFT collections. The system consists of two primary components:
1. GlueStickERC721: Factory contract that creates and manages individual Glue instances for NFT collections
2. GlueERC721: Implementation contract that gets cloned for each sticky NFT collection

The protocol enables NFT collections to have backing assets by:
- Associating the collection with a unique glue address that can hold collateral (any ERC20 or ETH)
- Allowing users to "unglue" by burning NFTs from the collection to withdraw a proportional amount of collateral
- Supporting batch operations, flash loans, and advanced hook mechanisms for extended functionality

Lore:
-* "Glue Stick" is the factory contract that glues ERC721 tokens.
-* "Sticky Asset" is an asset fueled by glue.
-* "Glue Address" is the address of the glue that is linked to a Sticky Token.
-* "Glued Collaterals" are the collaterals glued to a Sticky Token.
-* "Apply the Glue" is the action of infusing a NFT Collection with glue, making it sticky by creating its Glue Address.
-* "Unglue" is the action of burning the supply of a Sticky Asset to withdraw the corresponding percentage of the collateral.
-* "Glued Loan" is the action of borrowing collateral from multiple glues.
-* "Glued Hook" is a tool to expand the functionality of the protocol, via integrating the Sticky Asset Standard in your contract.
-* "Sticky Asset Standard" A common tools to implenet in your contract to expand the Glue functions and simplifying the development process.
-* "Sticky Asset Native" SAN is an asset that is natively compatible with the Sticky Asset Standard.
*/

pragma solidity ^0.8.28;

/**
* @dev Imports standard OpenZeppelin implementation, interfaces, and extensions for secure functionalities
*/
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {IERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/**
* @dev Interfaces for GlueERC20
*/
import {IGlueERC721, IGlueStickERC721, IERC721Burnable} from "./interfaces/IGlueERC721.sol";
import {IGluedLoanReceiver} from "./interfaces/IGluedLoanReceiver.sol";
import {IGluedSettings} from "./interfaces/IGluedSettings.sol";
import {IGluedHooks} from "./interfaces/IGluedHooks.sol";

/**
* @dev Library providing high-precision mathematical operations, decimal conversion, and rounding utilities for token calculations
*/
import {GluedMath} from "./libraries/GluedMath.sol";

/**

█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗
╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝

 ██████╗ ██╗     ██╗   ██╗███████╗    ███████╗████████╗██╗ ██████╗██╗  ██╗
██╔════╝ ██║     ██║   ██║██╔════╝    ██╔════╝╚══██╔══╝██║██╔════╝██║ ██╔╝
██║  ███╗██║     ██║   ██║█████╗      ███████╗   ██║   ██║██║     █████╔╝ 
██║   ██║██║     ██║   ██║██╔══╝      ╚════██║   ██║   ██║██║     ██╔═██╗ 
╚██████╔╝███████╗╚██████╔╝███████╗    ███████║   ██║   ██║╚██████╗██║  ██╗
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝    ╚══════╝   ╚═╝   ╚═╝ ╚═════╝╚═╝  ╚═╝

* @title GlueStickERC721
* @notice Factory contract for deploying and managing glue instances for ERC721 NFT collections
* @dev This contract serves as the entry point for making NFT collections sticky in the Glue Protocol.
* It validates collections, deploys minimal proxies for individual glue instances, and provides
* batch operations across multiple collections. It also coordinates cross-glue flash loans.
*/
contract GlueStickERC721 is IGlueStickERC721 {

/**
--------------------------------------------------------------------------------------------------------
 ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▖ ▗▖▗▄▄▖ 
▐▌   ▐▌     █  ▐▌ ▐▌▐▌ ▐▌
 ▝▀▚▖▐▛▀▀▘  █  ▐▌ ▐▌▐▛▀▘ 
▗▄▄▞▘▐▙▄▄▖  █  ▝▚▄▞▘▐▌                                               
01010011 01100101 01110100 
01110101 01110000 
*/

    // Import SafeERC20 for ERC20 operations
    using SafeERC20 for IERC20;

    // Registry of NFT collections to their glue addresses
    mapping(address => address) private _getGlueAddress;

    // Array of all deployed glue addresses for enumeration
    address[] private _allGlues;

    // Implementation contract address that gets cloned for each collection
    address private immutable _THE_GLUE;

    /**
    * @notice Deploys the implementation contract and initializes the factory
    * @dev Sets up the factory by deploying the implementation contract that will be cloned
    * for each NFT collection that gets glued in the protocol
    *
    * Use case: One-time deployment of the GlueStickERC721 factory to establish
    * the NFT branch of the Glue protocol on a blockchain network
    */
    constructor () {

        // Deploy the implementation contract   
        _THE_GLUE = deployTheGlue();

    }

    /**
    * @notice Prevents reentrancy attacks using transient storage
    * @dev Custom implementation of reentrancy protection using transient storage
    * This approach optimizes gas costs by using tstore/tload instead of state variables
    * while maintaining robust security guarantees for critical functions
    *
    * Use case: Protecting critical functions against potential reentrancy exploits,
    * particularly during NFT and collateral transfers which could contain callbacks
    */
    modifier nnrtnt() {

        // Check if the slot is already set
        bytes32 slot = keccak256(abi.encodePacked(address(this), "ReentrancyGuard"));

        // If the slot is already set, revert with a specific error signature
        assembly {

            // If the slot is already set, revert with a specific error signature
            if tload(slot) { 
                mstore(0x00, 0x3ee5aeb5)
                revert(0x1c, 0x04)
            }

            // Set the slot to 1 to indicate the function is being executed
            tstore(slot, 1)
        }

        // Execute the function
        _;

        // Reset the slot to 0 after the function execution is complete
        assembly {
            tstore(slot, 0)
        }
    }

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖ ▗▖▗▖  ▗▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖  ▗▖ ▗▄▄▖
▐▌   ▐▌ ▐▌▐▛▚▖▐▌▐▌     █    █  ▐▌ ▐▌▐▛▚▖▐▌▐▌   
▐▛▀▀▘▐▌ ▐▌▐▌ ▝▜▌▐▌     █    █  ▐▌ ▐▌▐▌ ▝▜▌ ▝▀▚▖
▐▌   ▝▚▄▞▘▐▌  ▐▌▝▚▄▄▖  █  ▗▄█▄▖▝▚▄▞▘▐▌  ▐▌▗▄▄▞▘
01000110 01110101 01101110 01100011 01110100 
01101001 01101111 01101110 01110011                               
*/

    /**
    * @notice Creates a new GlueERC721 contract for a specified NFT collection
    * @dev Validates the NFT collection for compatibility, creates a deterministic clone
    * of the implementation contract, initializes it with the collection address, and
    * registers it in the protocol registry. The created glue instance becomes the
    * collateral vault for the NFT collection.
    * 
    * @param asset The address of the ERC721 collection to be glued
    * @return glueAddress The address of the newly created glue instance
    *
    * Use cases:
    * - Adding asset backing capabilities to existing NFT collections
    * - Creating collateralization mechanisms for NFTs
    * - Establishing new NFT economic models with withdrawal mechanisms
    * - Supporting floor price protection for collections through backing
    */
    function applyTheGlue(address asset) external override returns (address glueAddress) {

        // Validate inputs
        if(asset == address(0)) revert InvalidAsset(asset);

        // Check if the token is valid
        (bool isAllowed) = checkAsset(asset);

        // If the token is not valid, revert
        if(!isAllowed) revert InvalidAsset(asset);

        // Check if the token is already glued
        if(_getGlueAddress[asset] != address(0)) revert DuplicateGlue(asset);

        // Generate a salt for the deterministic clone
        bytes32 salt = keccak256(abi.encodePacked(asset));

        // Clone the implementation contract
        glueAddress = Clones.cloneDeterministic(_THE_GLUE, salt);

        // Initialize the glue contract
        IGlueERC721(glueAddress).initialize(asset);

        // Store the glue address for the token
        _getGlueAddress[asset] = glueAddress;

        // Add the glue address to the array of all glued addresses
        _allGlues.push(glueAddress);

        // Emit an event to signal the addition of a new glue
        emit GlueAdded(asset, glueAddress, _allGlues.length);

        // Return the glue address
        return glueAddress;
    }

    /**
    * @notice Processes ungluing operations for multiple NFT collections in a single transaction
    * @dev Efficiently batches unglue operations across multiple NFT collections, managing the
    * transfer of NFTs from caller to glue contracts, and execution of unglue operations.
    * Supports both single and multiple recipient configurations.
    * 
    * @param stickyAssets Array of NFT collection addresses to unglue from
    * @param tokenIds Two-dimensional array of token IDs to unglue for each collection
    * @param collaterals Array of collateral addresses to withdraw (common across all unglue operations)
    * @param recipients Array of recipient addresses to receive the unglued collateral
    *
    * Use cases:
    * - Unglue collaterals across multiple sticky NFT collections
    * - Efficient withdrawal of collaterals from multiple sticky NFT collections
    * - Consolidated position exits for complex NFT strategies
    * - Multi-collection redemption in a single transaction
    */
    function batchUnglue(address[] calldata stickyAssets,uint256[][] calldata tokenIds,address[] calldata collaterals,address[] calldata recipients) external override nnrtnt {

        // Validate inputs
        if(stickyAssets.length == 0 || stickyAssets.length != tokenIds.length || recipients.length == 0) 
            revert InvalidInputs();

        // Process each sticky token in the batch
        for(uint256 i; i < stickyAssets.length;) {

            // Get the sticky token
            address stickyAsset = stickyAssets[i];

            // Get the token IDs
            uint256[] calldata tokenIdBatch = tokenIds[i];
            
            // Transfer each token ID individually
            for (uint256 j = 0; j < tokenIdBatch.length; j++) {

                // Transfer the token ID from the caller to this contract
                IERC721(stickyAsset).transferFrom(msg.sender, address(this), tokenIdBatch[j]);
            }
            
            // If there are no token IDs, skip to the next sticky token
            if (tokenIdBatch.length == 0) continue;
            
            // Get the glue address for this sticky token
            address glueAddress = _getGlueAddress[stickyAsset];

            // If the glue address is not set, skip to the next sticky token
            if(glueAddress == address(0) ) continue;

            // Approve each token ID individually
            for (uint256 j = 0; j < tokenIdBatch.length; j++) {

                // Approve the token ID to the glue address
                IERC721(stickyAsset).approve(glueAddress, tokenIdBatch[j]);
            }

            // If there are multiple recipients, validate inputs
            if(recipients.length > 1) {

                // Validate inputs
                if (recipients.length != stickyAssets.length || recipients[i] == address(0)) revert InvalidInputs();

                // Execute unglue for this sticky token
                IGlueERC721(glueAddress).unglue(
                    collaterals,
                    tokenIdBatch,
                    recipients[i]
                );

            // If there is only one recipient, validate inputs
            } else {

                // Validate inputs
                if (recipients[0] == address(0)) revert InvalidInputs();

                // Execute unglue for this sticky token
                IGlueERC721(glueAddress).unglue(
                    collaterals,
                    tokenIdBatch,
                    recipients[0]
                );
            }

            // Increment the index
            unchecked { ++i; }
        }

        // Emit an event to signal the completion of the batch ungluing
        emit BatchUnglueExecuted(stickyAssets, tokenIds, collaterals, recipients);
    }

    /**
    * @notice Executes multiple flash loans across multiple glues.
    * @dev This function calculates the loans, executes them, and verifies the repayments.
    *
    * @param glues The addresses of the glues to borrow from.
    * @param collateral The address of the collateral to borrow.
    * @param loanAmount The total amount of collaterals to borrow.
    * @param receiver The address of the receiver.
    * @param params Additional parameters for the receiver.
    *
    * Use cases:
    * - Flash Loans across multiple glues
    * - Capital-efficient arbitrage across DEXes
    * - Liquidation operations in lending protocols
    * - Complex cross-protocol interactions requiring upfront capital
    * - Temporary liquidity for atomic multi-step operations
    * - Collateral swaps without requiring pre-owned capital
    */
    function gluedLoan(address[] calldata glues,address collateral,uint256 loanAmount,address receiver,bytes calldata params) external override nnrtnt {

        // Validate inputs
        if(receiver == address(0)) revert InvalidAddress();
        if(loanAmount == 0) revert InvalidInputs();
        if(glues.length == 0) revert InvalidInputs();

        // Calculate the loans
        LoanData memory loanData = _calculateLoans(glues, collateral, loanAmount);

        // Execute the loans
        _executeLoans(loanData, glues, collateral, receiver);

        // Execute the receiver's callback
        if (!IGluedLoanReceiver(receiver).executeOperation(
            glues[0:loanData.count],
            collateral,
            loanData.expectedAmounts,
            params
        )) revert FlashLoanFailed();

        // Verify the balances
        _verifyBalances(loanData, glues, collateral);
        
    }

    /**
    * @notice Calculates the flash loans for each glue.
    * @dev This function calculates the loans, executes them, and verifies the repayments.
    * @param glues The addresses of the glues to borrow from.
    * @param collateral The address of the collateral to borrow.
    * @param loanAmount The total amount of collateral to borrow.
    * @return loanData The data for the loans.
    *
    * Use cases:
    * - Calculate the ammount to borrow from each glue
    */
    function _calculateLoans(address[] calldata glues, address collateral, uint256 loanAmount) private view returns (LoanData memory loanData) {

        // Initialize the arrays for the loans
        loanData.toBorrow = new uint256[](glues.length);
        loanData.expectedAmounts = new uint256[](glues.length);
        loanData.expectedBalances = new uint256[](glues.length);
        
        // Initialize the total collected amount
        uint256 totalCollected;

        // Initialize the index for the loans
        uint256 j;

        // Process each glue
        for (uint256 i; i < glues.length;) {

            // If the total collected amount is greater than or equal to the total amount, break
            if (totalCollected >= loanAmount) break;
            
            // Get the glue address
            address glue = glues[i];

            // If the glue address is invalid, revert
            if(glue == address(0)) revert InvalidAddress();

            // Get the available balance of the glue
            uint256 available = getGlueBalance(glue, collateral);

            // If the available balance is 0, revert
            if(available == 0) revert InvalidGlueBalance(glue, available, collateral);
            
            // If the available balance is greater than 0, calculate the loans
            if (available > 0) {

                // Calculate the amount to borrow
                uint256 toBorrow = loanAmount - totalCollected;

                // If the amount to borrow is greater than the available balance, set the amount to borrow to the available balance
                if (toBorrow > available) toBorrow = available;

                // If the amount to borrow is 0, skip to the next glue
                if(toBorrow == 0) continue;

                // Get the flash loan fee
                uint256 fee = IGlueERC721(glue).getFlashLoanFeeCalculated(toBorrow);
                
                // Store the loan data
                loanData.toBorrow[j] = toBorrow;
                loanData.expectedAmounts[j] = toBorrow + fee;
                loanData.expectedBalances[j] = available + fee;
                totalCollected += toBorrow;
                j++;
            }

            // Increment the index
            unchecked { ++i; }
        }

        // Set the count of the loans
        loanData.count = j;

        // If the total collected amount is less than the total amount, revert
        if (totalCollected < loanAmount)
            revert InsufficientLiquidity(totalCollected, loanAmount);

        // Return the loan data
        return loanData;
    }

    /**
    * @notice Executes the flash loans for each glue.
    * @dev This function executes the loans and verifies the repayments.
    * @param loanData The data for the loans.
    * @param glues The addresses of the glues to borrow from.
    * @param collateral The address of the collateral to borrow.
    * @param receiver The address of the receiver.
    *
    * Use cases:
    * - Execute the flash loans
    */
    function _executeLoans(LoanData memory loanData,address[] calldata glues,address collateral,address receiver) private {

        // Process each glue
        for (uint256 i; i < loanData.count;) {
            
            // Execute the loan
            if(!IGlueERC721(glues[i]).loanHandler(
                receiver,
                collateral,
                loanData.toBorrow[i]
            )) revert FlashLoanFailed();

            // Increment the index
            unchecked { ++i; }
        }
    }

    /**
    * @notice Verifies the balances for each glue.
    * @dev This function verifies the balances for each glue.
    * @param loanData The data for the loans.
    * @param glues The addresses of the glues to borrow from.
    * @param collateral The address of the collateral to borrow.
    *
    * Use cases:
    * - Verify the balances for each glue after loans are executed
    */
    function _verifyBalances(LoanData memory loanData,address[] calldata glues,address collateral) private view {

        // Verify the balances
        for (uint256 i; i < loanData.count;) {

            // Get the glue address
            address glue = glues[i];

            // If the glue address is invalid, revert
            if(glue == address(0)) revert InvalidAddress();

            // If the balance is less than the expected balance, revert
            if (getGlueBalance(glue, collateral) < loanData.expectedBalances[i])
                revert RepaymentFailed(glue);

            // Increment the index
            unchecked { ++i; }
        }
    }

    /**
    * @notice Deploys the _THE_GLUE contract.
    * @dev This function is only called once when deploying the implementation contract
    * Actual glue instances are created as clones and initialized via initialize()
    * @return address The address of the deployed GlueERC721 contract
    *
    * Use cases:
    * - One-time deployment of the implementation contract for NFT collections
    */
    function deployTheGlue() internal returns (address) {

        // Deploy the implementation contract
        GlueERC721 glueContract = new GlueERC721(address(this));

        // Get the address of the deployed implementation contract
        address glueAddress = address(glueContract);

        // If the address is 0, revert
        if(glueAddress == address(0)) revert FailedToDeployGlue();

        // Return the address of the deployed implementation contract
        return glueAddress;
    }

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▄▄▄ 
▐▌ ▐▌▐▌   ▐▌ ▐▌▐▌  █
▐▛▀▚▖▐▛▀▀▘▐▛▀▜▌▐▌  █
▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌▐▙▄▄▀
01010010 01100101 
01100001 01100100                         
*/

    /**
    * @notice Retrieves expected collateral amounts from batch ungluing operations for NFTs
    * @dev View function to calculate expected collateral returns for multiple NFT collections.
    * This is essential for front-end applications and integrations to estimate expected
    * returns before executing batch unglue operations.
    * 
    * @param stickyAssets Array of NFT collection addresses
    * @param stickyAmounts Array of NFT counts to simulate ungluing (number of NFTs, not IDs)
    * @param collaterals Array of collateral addresses to check
    * @return collateralAmounts 2D array of corresponding collateral amounts [glueIndex][collateralIndex]
    *
    * Use cases:
    * - Pre-transaction estimation for front-end applications
    * - Strategy optimization based on expected returns
    * - User interface displays showing potential redemption values
    */
    function getBatchCollaterals(address[] calldata stickyAssets,uint256[] calldata stickyAmounts,address[] calldata collaterals) external view override returns (uint256[][] memory collateralAmounts) {
        // Validate inputs
        if(stickyAssets.length != stickyAmounts.length) revert InvalidInputs();

        // Initialize the memory array for the collateral amounts
        collateralAmounts = new uint256[][](stickyAssets.length);
        
        // Process each sticky token
        for(uint256 i; i < stickyAssets.length;) {

            // Get the glue address for this sticky token
            address glueAddress = _getGlueAddress[stickyAssets[i]];

            // If the glue address is not set, create an empty array for the collateral amounts
            if(glueAddress == address(0)) {
                // Create empty array for invalid glue addresses
                collateralAmounts[i] = new uint256[](collaterals.length);
            } else {
                // Get collateral amounts for this sticky token
                (uint256[] memory tokenCollateralAmounts) = IGlueERC721(glueAddress).collateralByAmount(stickyAmounts[i], collaterals);

                // Store the collateral amounts
                collateralAmounts[i] = tokenCollateralAmounts;
            }

            // Increment the index
            unchecked { ++i; }
        }

        // Return the sticky tokens and the collateral amounts
        return collateralAmounts;
    }

    /**
    * @notice Checks if the given ERC721 address has valid totalSupply and no decimals
    * @dev This function performs static calls to check if token is a valid NFT
    * Token validation is critical for ensuring only compatible collections can be glued,
    * preventing issues with non-enumerable NFT collections.
    * 
    * @param asset The address of the ERC721 asset to check
    * @return isValid Indicates whether the token is valid
    *
    * Use cases:
    * - Pre-glue verification to prevent incompatible token issues
    * - Protocol security to maintain compatibility standards
    * - Front-end validation before attempting glue operations
    */
    function checkAsset(address asset) public view override returns (bool isValid) {

        // First check if it supports ERC721 interface
        bytes4 ERC721InterfaceId = 0x80ac58cd; 

        // Try to check if it supports the ERC721 interface
        try IERC165(asset).supportsInterface(ERC721InterfaceId) returns (bool supports721) {

            // If it doesn't support the ERC721 interface, return false
            if (!supports721) {
                return false;
            }
        } catch {

            // If it doesn't support the ERC721 interface, return false
            return false;
        }

        // Then check for totalSupply
        (bool hasTotalSupply, ) = asset.staticcall(abi.encodeWithSignature("totalSupply()"));

        // If it doesn't have a totalSupply, return false
        if (!hasTotalSupply) {
            return false;
        }

        // Return true if it supports the ERC721 interface and has a totalSupply
        return true;
    }

    /**
    * @notice Computes the address of the GlueERC721 contract for the given ERC721 address.
    * @dev Uses the Clones library to predict the address of the minimal proxy.
    *
    * @param asset The address of the ERC721 contract.
    * @return predictedGlueAddress The computed address of the GlueERC721 contract.
    *
    * Use cases:
    * - Complex integrations requiring pre-knowledge of glue addresses
    * - Front-end preparation before actual glue deployment
    * - Cross-contract interactions that reference glue addresses
    * - Security verification of expected deployment addresses
    */
    function computeGlueAddress(address asset) public view override returns (address predictedGlueAddress) {

        // Validate inputs
        if(asset == address(0)) revert InvalidAsset(asset);

        // Compute the glue address
        bytes32 salt = keccak256(abi.encodePacked(asset));

        // Return the predicted address
        return Clones.predictDeterministicAddress(_THE_GLUE, salt, address(this));
    }

    /**
    * @notice Checks if a given token is sticky and returns its glue address
    * @dev Utility function for external contracts and front-ends to verify token status
    * in the Glue protocol and retrieve the associated glue address if it exists.
    * 
    * @param asset The address of the NFT Collection to check
    * @return isSticky bool Indicates whether the token is sticky.
    * @return glueAddress The glue address for the token if it's sticky, otherwise address(0).
    *
    * Use cases:
    * - UI elements showing token glue status
    * - Protocol integrations needing to verify glue existence
    * - Smart contracts checking if a token can be unglued
    * - External protocols building on top of the Glue protocol
    */
    function isStickyAsset(address asset) public view override returns (bool isSticky, address glueAddress) {

        // Return a boolean, true if the token is sticky and the glue address
        return (_getGlueAddress[asset] != address(0), _getGlueAddress[asset]);
    }

    /** 
    * @notice Retrieves the balance of a given collateral in a glue.
    * @dev Handles both ERC20 collaterals and native ETH (when collateral address is address(0)),
    * providing a unified interface for balance queries that's used throughout the protocol.
    * 
    * @param glue The address of the glue.
    * @param collateral The address of the collateral.
    * @return uint256 The balance of the collateral in the glue.
    *
    * Use cases:
    * - Collateral availability verification for flash loans
    * - Used in getGluesBalances to track the balance of each glue for each collateral
    */
    function getGlueBalance(address glue,address collateral) internal view returns (uint256) {

        // If the collateral is 0, return the balance of the glue
        if(collateral == address(0)) {

            // Return the balance of the collateral
            return glue.balance;

        } else {

            // Return the balance of the collateral
            return IERC20(collateral).balanceOf(glue);
        }
    }

    /**
    * @notice Retrieves the balances of multiple collaterals across multiple glues
    * @dev Returns a 2D array where each row represents a glue and each column represents a collateral
    * @dev This function is used to get the balances of multiple collaterals across multiple glues
    *
    * @param glues The addresses of the glues to check
    * @param collaterals The addresses of the collaterals to check for each glue
    * @return balances a 2D array of balances [glueIndex][collateralIndex]
    *
    * Use cases:
    * - Batch querying collateral positions across multiple glues
    * - Dashboard displays showing complete portfolio positions
    * - Cross-glue analytics and reporting
    */
    function getGluesBalances(address[] calldata glues, address[] calldata collaterals) external view override returns (uint256[][] memory balances) {
        // Initialize the 2D balances array
        balances = new uint256[][](glues.length);
        
        // Process each glue
        for (uint256 i; i < glues.length;) {
            // Initialize the balances array for this glue
            balances[i] = new uint256[](collaterals.length);
            
            // Process each collateral for this glue
            for (uint256 j; j < collaterals.length;) {
                // Get the balance of this collateral in this glue
                balances[i][j] = getGlueBalance(glues[i], collaterals[j]);
                
                // Increment the collateral index
                unchecked { ++j; }
            }
            
            // Increment the glue index
            unchecked { ++i; }
        }
        
        // Return the 2D balances array
        return balances;
    }

    /**
    * @notice Returns the total number of deployed glues.
    * @return existingGlues The length of the _allGlues array.
    *
    * Use cases:
    * - Informational queries about the total number of deployed glues
    */
    function allGluesLength() external view override returns (uint256 existingGlues) {

        // Return the length of the allGlues array
        return _allGlues.length;
    }

    /**
    * @notice Retrieves the glue address for a given token
    * @dev Returns the glue address for the given token
    *
    * @param asset The address of the NFT collection to get the glue address for
    * @return glueAddress The glue address for the given token, if it exists, otherwise address(0)
    *
    * Use cases:
    * - Retrieving the glue address for a given token
    */
    function getGlueAddress(address asset) external view override returns (address glueAddress) {

        // Return the glue address for the given token
        return _getGlueAddress[asset];
    }

    /**
    * @notice Retrieves a glue address by its index in the registry
    * @dev Returns the address of a deployed glue at the specified index
    * This provides indexed access to the array of all deployed glues
    * 
    * @param index The index in the allGlues array to query
    * @return glueAddress The address of the glue at the specified index
    *
    * Use cases:
    * - Enumeration of all deployed glues in the protocol
    * - Accessing specific glues by index for reporting or integration
    * - Batch operations on sequential glue addresses
    */
    function getGlueAtIndex(uint256 index) external view override returns (address glueAddress) {

        // Revert if the index is out of bounds
        if (index >= _allGlues.length) {
            return address(0);
        }
        
        // Return the glue address at the specified index
        return _allGlues[index];
    }

}

/**
                                                                                                                                               
█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗█████╗
╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝╚════╝

████████╗██╗  ██╗███████╗     ██████╗ ██╗     ██╗   ██╗███████╗
╚══██╔══╝██║  ██║██╔════╝    ██╔════╝ ██║     ██║   ██║██╔════╝
   ██║   ███████║█████╗      ██║  ███╗██║     ██║   ██║█████╗  
   ██║   ██╔══██║██╔══╝      ██║   ██║██║     ██║   ██║██╔══╝  
   ██║   ██║  ██║███████╗    ╚██████╔╝███████╗╚██████╔╝███████╗
   ╚═╝   ╚═╝  ╚═╝╚══════╝     ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝
 
* @title GlueERC721
* @notice Implementation contract for individual NFT collection glue instances
* @dev This contract is deployed once and then cloned using minimal proxies for each glued NFT collection.
* It manages collateral holdings, processes NFT ungluing operations, calculates proportional
* withdrawals based on NFT count vs total supply, and facilitates flash loans. The contract 
* implements ERC721Holder for safe NFT receipt during ungluing operations.
*/
contract GlueERC721 is Initializable, ERC721Holder, IGlueERC721 {

/**
--------------------------------------------------------------------------------------------------------
 ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▖ ▗▖▗▄▄▖ 
▐▌   ▐▌     █  ▐▌ ▐▌▐▌ ▐▌
 ▝▀▚▖▐▛▀▀▘  █  ▐▌ ▐▌▐▛▀▘ 
▗▄▄▞▘▐▙▄▄▖  █  ▝▚▄▞▘▐▌                                               
01010011 01100101 01110100 
01110101 01110000 
*/

    // Address for address payable (ETH)
    using Address for address payable;

    // SafeERC20 for IERC20
    using SafeERC20 for IERC20;

    // GluedMath for uint256
    using GluedMath for uint256;

    // Protocol constants
    /// @notice Precision factor used for fractional calculations (10^18)
    uint256 private constant PRECISION = 1e18;

    /// @notice Protocol fee percentage in PRECISION units (0.1%)
    uint256 private constant PROTOCOL_FEE = 1e15; 

    /// @notice Flash loan fee percentage in PRECISION units (0.01%)
    uint256 private constant LOAN_FEE = 1e14; 

    /// @notice Special address used to represent native ETH in the protocol
    address private constant ETH_ADDRESS = address(0);

    /// @notice Dead address used for NFTs that don't support burning
    address private constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    /// @notice Address of the protocol-wide settings contract
    address private constant SETTINGS = 0x9976457c0C646710827bE1E36139C2b73DA6d2f3;
    
    // Immutable reference to factory

    /// @notice Address of the GlueStick factory that created this glue
    address private immutable GLUE_STICK;
    
    // Glue instance state

    /// @notice Address of the ERC721 collection this glue is associated with
    address private STICKY_ASSET;

    /// @notice Flag indicating if the NFT collection doesn't support burning
    bool private notBurnable;

    /// @notice Flag indicating if NFTs are stored in this contract rather than burned/transferred
    bool private stickySupplyStored;

    /// @notice Enum tracking hook capability status (UNCHECKED, NO_HOOK, or HOOK)
    BIO private bio;

    /**
    * @notice Constructor sets the factory address and initializes core variables
    * @dev This constructor is only called once when deploying the implementation contract
    * Actual glue instances are created as clones and initialized via initialize()
    * 
    * @param _glueStickAddress Address of the factory contract that deploys glue instances
    *
    * Use case: One-time deployment of the implementation contract for NFT collections
    */
    constructor(address _glueStickAddress) {

        // If the glue stick address is 0, revert
        if(_glueStickAddress == address(0)) revert InvalidGlueStickAddress();

        // Set the glue stick address
        GLUE_STICK = _glueStickAddress;
    }

    /**
    * @notice Guards against reentrancy attacks using transient storage
    * @dev Custom implementation of reentrancy protection using transient storage (tstore/tload)
    * instead of a standard state variable, optimizing gas costs while maintaining security
    *
    * Use case: Securing all external functions against reentrancy attacks,
    * particularly important for functions handling NFT and collateral transfers
    */
    modifier nnrtnt() {

        // Create a slot for the reentrancy guard
        bytes32 slot = keccak256(abi.encodePacked(address(this), "ReentrancyGuard"));

        // If the slot is already set, revert
        assembly {

            // If the slot is already set, revert with a specific error signature
            if tload(slot) { 
                mstore(0x00, 0x3ee5aeb5)
                revert(0x1c, 0x04)
            }

            // Set the slot to 1 to indicate the function is being executed
            tstore(slot, 1)
        }

        // Execute the function
        _;

        // Reset the slot to 0 after the function execution is complete
        assembly {
            tstore(slot, 0)
        }
    }

    /**
    * @notice Initializes a newly deployed glue clone for an NFT collection
    * @dev Called by the factory when creating a new glue instance through cloning
    * Sets up the core state variables and establishes the relationship between
    * this glue instance and its associated NFT collection
    * 
    * @param asset Address of the ERC721 collection to be linked with this glue
    *
    * Use cases:
    * - Creating a new glue address for a NFT collection (now Sticky Token) in which attach collateral
    * - Establishing the collection-glue relationship in the protocol
    */
    function initialize(address asset) external nnrtnt initializer {

        // If the sender is not the glue stick, revert
        if(msg.sender != GLUE_STICK) revert Unauthorized();

        // If the token address to glue is 0, revert
        if(asset == address(0)) revert InvalidAsset(asset);

        // Set the sticky token
        STICKY_ASSET = asset;

        // Set inital boolean values
        stickySupplyStored = false;
        notBurnable = false;
        bio = BIO.UNCHECKED;
    }

    /**
    * @notice Allows the contract to receive ETH.
    */
    receive() external payable {}

    /**
    * @notice Override ERC721Holder's onERC721Received to only accept STICKY_ASSET
    * @dev This implementation ensures only the STICKY_ASSET can be received during unglue
    *
    * @param operator The address which called `safeTransferFrom` function
    * @param from The address which previously owned the token
    * @param tokenId The NFT identifier which is being transferred
    * @param data Additional data with no specified format
    * @return bytes4 The function selector
    *
    * Use cases:
    * - Ensuring the ONLY ERC721 that can be received is the STICKY_ASSET.
    */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes memory data
    ) public virtual override returns (bytes4) {

        // Only allow receiving the sticky token
        if (msg.sender != STICKY_ASSET) {
            revert NoAssetsTransferred();
        }
        
        // Call parent implementation
        return super.onERC721Received(operator, from, tokenId, data);
    }

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖ ▗▖▗▖  ▗▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖  ▗▖ ▗▄▄▖
▐▌   ▐▌ ▐▌▐▛▚▖▐▌▐▌     █    █  ▐▌ ▐▌▐▛▚▖▐▌▐▌   
▐▛▀▀▘▐▌ ▐▌▐▌ ▝▜▌▐▌     █    █  ▐▌ ▐▌▐▌ ▝▜▌ ▝▀▚▖
▐▌   ▝▚▄▞▘▐▌  ▐▌▝▚▄▄▖  █  ▗▄█▄▖▝▚▄▞▘▐▌  ▐▌▗▄▄▞▘
01000110 01110101 01101110 01100011 01110100 
01101001 01101111 01101110 01110011                               
*/

    /**
    * @notice Core function that processes NFT ungluing operations to release collateral
    * @dev Handles the complete ungluing workflow for NFTs: verifying ownership,
    * managing transfers, calculating proportional collateral amounts, applying fees,
    * executing hook logic if enabled, and distributing collateral to the recipient.
    * 
    * @param collaterals Array of collateral token addresses to withdraw
    * @param tokenIds Array of NFT token IDs to burn for collateral withdrawal
    * @param recipient Address to receive the withdrawn collateral
    * @return supplyDelta Calculated proportion of total NFT supply (in PRECISION units)
    * @return realAmount Number of NFTs processed (after removing duplicates)
    * @return beforeTotalSupply NFT collection supply before the unglue operation
    * @return afterTotalSupply NFT collection supply after the unglue operation
    *
    * Use cases:
    * - Redeeming collateral from the protocol by burning NFTs
    * - Converting sticky NFTs back to their collaterals
    */
    function unglue(address[] calldata collaterals, uint256[] calldata tokenIds, address recipient) external override nnrtnt returns (uint256 supplyDelta, uint256 realAmount, uint256 beforeTotalSupply, uint256 afterTotalSupply) {

        // If no collateral is selected, revert
        if(collaterals.length == 0) revert NoAssetsSelected();

        // If the recipient is 0, set it to the sender
        if (recipient == address(0)) {recipient = msg.sender;}

        // Process the unique token IDs
        realAmount = processUniqueTokenIds(tokenIds, recipient);

        // Get the real total supply
        (beforeTotalSupply, afterTotalSupply) = getRealTotalSupply(realAmount);

        // Calculate the supply delta
        supplyDelta = calculateSupplyDelta(realAmount, beforeTotalSupply);

        // Compute the collateral
        computeCollateral(collaterals, supplyDelta, recipient);

        // Emit the unglued event
        emit unglued(recipient, realAmount, beforeTotalSupply, afterTotalSupply, supplyDelta);

        // Return the values
        return (supplyDelta, realAmount, beforeTotalSupply, afterTotalSupply);
    }

    /**
    * @notice Processes token IDs array to remove duplicates and verify ownership
    * @dev Creates a new array with unique token IDs and verifies ownership
    *
    * @param tokenIds Array of token IDs to process
    * @param recipient The address of the recipient of the unglue operation
    * @return uniqueCount Number of unique token IDs
    *
    * Use cases:
    * - Removing duplicates from the token IDs array
    * - Verifying ownership of the token IDs
    */
    function processUniqueTokenIds(uint256[] calldata tokenIds, address recipient) private returns (uint256) {

        // If no token IDs are selected, revert
        if(tokenIds.length == 0) revert NoAssetsSelected();

        // Create a slot for the duplicate token ID check
        bytes32 duplicateSlot = keccak256(abi.encodePacked(address(this), "DuplicateTokenIdCheck"));

        // Initialize the count
        uint256 count = 0;
        
        // Process each token ID
        for (uint256 i = 0; i < tokenIds.length; i++) {

            // Get the token ID
            uint256 tokenId = tokenIds[i];

            // Create a slot for the duplicate token ID check
            bytes32 slot = keccak256(abi.encodePacked(duplicateSlot, tokenId));
            
            // Check if the token ID is a duplicate
            bool isDuplicate;
            assembly {

                // Get the duplicate status
                isDuplicate := tload(slot)

                // Set the duplicate status to true
                tstore(slot, 1)
            }
            
            // If the token ID is not a duplicate
            if (!isDuplicate) {

                // Check if the sender owns the token ID
                if(IERC721(STICKY_ASSET).ownerOf(tokenId) != msg.sender) revert NoAssetsTransferred();

                // Increment the count
                count++;
            }
        }

        // If no token IDs are processed, revert
        if (count == 0) revert NoAssetsTransferred();

        // Create a new array with unique token IDs
        uint256[] memory uniqueTokenIds = new uint256[](count);

        // Initialize the unique count
        uint256 uniqueCount = 0;

        // Process each token ID
        for (uint256 i = 0; i < tokenIds.length; i++) {

            // Get the token ID
            uint256 tokenId = tokenIds[i];

            // Create a slot for the duplicate token ID check
            bytes32 slot = keccak256(abi.encodePacked(duplicateSlot, tokenId));
            
            // Check if the token ID is processed
            bool isProcessed;
            assembly {

                // Get the processed status
                isProcessed := tload(slot)

                // Set the processed status to false
                tstore(slot, 0)
            }

            // If the token ID is processed
            if (isProcessed) {

                // Add the token ID to the unique token IDs array
                uniqueTokenIds[uniqueCount] = tokenId;

                // Increment the unique count
                uniqueCount++;
            }
        }

        // Burn the main tokens
        burnMain(uniqueTokenIds);

        // Execute the hook
        tryHook(address(this), uniqueCount, uniqueTokenIds, recipient);

        // If no tokens are transferred, revert
        if (uniqueCount == 0) {
            revert NoAssetsTransferred();
        }

        // Return the unique count
        return uniqueCount;
    }

    /**
    * @notice Calculates the real total supply of the sticky token by excluding balances in dead and burn addresses.
    * This function is used to calculate the total supply before and after the unglue operation.
    *
    * @param realAmount The amount of sticky tokens being unglued
    * @return beforeTotalSupply The total supply before ungluing
    * @return afterTotalSupply The total supply after ungluing
    *
    * Use cases:
    * - Calculating the total supply before and after ungluing
    * - Ensuring accurate supply metrics for fair collateral distribution
    */
    function getRealTotalSupply(uint256 realAmount) private view returns (uint256, uint256) {

        // Get the before total supply
        uint256 beforeTotalSupply = (getNFTTotalSupply() + realAmount) - getNFTBalance(DEAD_ADDRESS);

        // Subtract the balance of the glue
        beforeTotalSupply -= getNFTBalance(address(this));

        // Get the after total supply
        uint256 afterTotalSupply = beforeTotalSupply - realAmount;
        
        // Return the values
        return (beforeTotalSupply, afterTotalSupply);
    }

    /**
    * @notice Calculates the supply delta based on the real amount and real total supply.
    * This function is used to calculate the supply delta based on the real amount and real total supply.
    *
    * @param realAmount The real amount of supply.
    * @param beforeTotalSupply The real total supply.
    * @return The calculated supply delta.
    *
    * Use cases:
    * - Calculating the supply delta based on the real amount and real total supply.
    */
    function calculateSupplyDelta(uint256 realAmount, uint256 beforeTotalSupply) private pure returns (uint256) {

        // Calculate the supply delta
        return GluedMath.md512(realAmount, PRECISION, beforeTotalSupply);
    }

    /**
    * @notice Burns the main tokens held by the glue or transfers them to the dead address if burning fails.
    * This function is used to burn the main tokens held by the glue or transfer them to the dead address if burning fails.
    *
    * @param _tokenIds The token IDs to burn or transfer.
    *
    * Use cases:
    * - Burning the main tokens held by the glue.
    * - Transferring the main tokens to the dead address if burning fails.
    */
    function burnMain(uint256[] memory _tokenIds) private {

        // Process each token ID
        for (uint256 i = 0; i < _tokenIds.length; i++) {

            // Get the token ID
            uint256 tokenId = _tokenIds[i];

            // If the token is not burnable, try to burn it
            if (!notBurnable) {

                // Try to burn the token
                try IERC721Burnable(STICKY_ASSET).burn(tokenId) {

                    // Burn successful, continue to next iteration
                    continue;

                } catch {

                    // Set the not burnable flag to true
                    notBurnable = true;
                }
            } 

            // If the token is not burnable and the token is not stored, try to transfer it to the dead address
            if (notBurnable && !stickySupplyStored) {

                // Try to transfer the token to the dead address
                try IERC721(STICKY_ASSET).transferFrom(msg.sender, DEAD_ADDRESS, tokenId) {

                    // Transfer successful, continue to next iteration
                    continue;

                } catch {

                    // Set the sticky token stored flag to true
                    stickySupplyStored = true;
                }
            }

            // If the token is not burnable and the token is stored, try to transfer it to the glue
            if (notBurnable && stickySupplyStored) {

                // Try to transfer the token to the glue
                try IERC721(STICKY_ASSET).transferFrom(msg.sender, address(this), tokenId) {

                    // Transfer successful, continue to next iteration
                    continue;

                } catch {

                    // Revert
                    revert FailedToProcessCollection();
                }
            }
        }
    }

    /**
    * @dev Processes the withdrawals for the given token addresses and amounts.
    * It also checks for duplicates and calculates the asset availability.
    * It also calculates the protocol fee and the recipient amount.
    * It also executes the hook if enabled.
    * It also sends the glue fee and the protocol fee to the glue fee address and the team address respectively.
    * It also sends the recipient amount to the recipient.
    *
    * @param collaterals The addresses of the collateral tokens to withdraw.
    * @param supplyDelta The change in the token supply.
    * @param recipient The address of the recipient.
    *
    * Use cases:
    * - Ungluing assets from the glue.
    * - Sending the unglued assets to the recipient.
    * - Calculating the protocol fee and the recipient amount.
    * - Executing the hook if enabled.
    * - Sending the glue fee and the protocol fee to the glue fee address and the team address respectively.
    */
    function computeCollateral(address[] calldata collaterals, uint256 supplyDelta, address recipient) private {

        // Create a slot for the duplicate address check
        bytes32 duplicateSlot = keccak256(abi.encodePacked(address(this), "DuplicateAddressCheck"));

        // Fetch fee information directly from SETTINGS
        (uint256 glueFee, address glueFeeAddress, address teamAddress) = IGluedSettings(SETTINGS).getProtocolFeeInfo();

        // Process each collateral
        for (uint256 i = 0; i < collaterals.length; i++) {

            // Get the collateral
            address gluedCollateral = collaterals[i];

            // If the collateral is the sticky token, continue
            if(gluedCollateral == STICKY_ASSET) continue;
            
            // Check if the collateral is a duplicate
            bytes32 slot = keccak256(abi.encodePacked(duplicateSlot, gluedCollateral));

            // Check if the collateral is a duplicate
            bool isDuplicate;
            assembly {

                // Get the duplicate flag
                isDuplicate := tload(slot)

                // Set the duplicate flag to true
                tstore(slot, 1)
            }

            // If the collateral is a duplicate, continue
            if (isDuplicate) continue;

            // Calculate the asset availability
            uint256 assetAvailability = GluedMath.md512(getAssetBalance(gluedCollateral, address(this)), supplyDelta, PRECISION);

            // If the asset availability is 0, continue
            if (assetAvailability == 0) continue;

            // Calculate fees
            uint256 protocolFeeAmount = GluedMath.md512Up(assetAvailability, PROTOCOL_FEE, PRECISION);

            // Calculate the recipient amount
            uint256 recipientAmount = assetAvailability - protocolFeeAmount;

            // If the recipient amount is 0, continue
            if(recipientAmount == 0) continue;

            // Check if out hook is enabled (bit 1, 0x2) in BIO
            if (bio == BIO.UNCHECKED || bio == BIO.HOOK) {

                // Execute the hook
                recipientAmount = tryHook(gluedCollateral, recipientAmount, new uint256[](0), recipient);
            }
            
            // Calculate the glue fee amount
            uint256 glueFeeAmount = GluedMath.md512Up(protocolFeeAmount, glueFee, PRECISION);

            // If the glue fee amount is greater than the protocol fee amount, set the glue fee amount to the protocol fee amount
            if (glueFeeAmount > protocolFeeAmount) glueFeeAmount = protocolFeeAmount;
            
            // For ETH transfers
            if (gluedCollateral == ETH_ADDRESS) {

                // Send the glue fee to the glue fee address
                payable(glueFeeAddress).sendValue(glueFeeAmount);

                // If the glue fee amount is less than the protocol fee amount, send the protocol fee to the team address
                if (glueFeeAmount < protocolFeeAmount) {

                    // Send the protocol fee to the team address
                    payable(teamAddress).sendValue(protocolFeeAmount - glueFeeAmount);
                }

                // Send the recipient amount to the recipient
                payable(recipient).sendValue(recipientAmount);

            } else {
                
                // Send the glue fee to the glue fee address
                IERC20(gluedCollateral).safeTransfer(glueFeeAddress, glueFeeAmount);

                // If the glue fee amount is less than the protocol fee amount, send the protocol fee to the team address
                if (glueFeeAmount < protocolFeeAmount) {

                    // Send the protocol fee to the team address
                    IERC20(gluedCollateral).safeTransfer(teamAddress, protocolFeeAmount - glueFeeAmount);
                }

                // Send the recipient amount to the recipient
                IERC20(gluedCollateral).safeTransfer(recipient, recipientAmount);
            }
        }

        // Reset the duplicate flags
        for (uint256 i = 0; i < collaterals.length; i++) {

            // Get the collateral
            address gluedCollateral = collaterals[i];

            // Reset the duplicate flag
            bytes32 slot = keccak256(abi.encodePacked(duplicateSlot, gluedCollateral));
            assembly {

                // Reset the duplicate flag
                tstore(slot, 0)
            }
        }

    }

    /**
    * @notice Executes a hook based on the token address and returns the hook amount
    * @dev This function assumes all checks are done outside and just executes the hook
    *
    * @param asset The address of the asset
    * @param amount The amount of the asset
    * @param tokenIds The token IDs to execute the hook on
    * @param recipient The address of the recipient of the unglue operation
    * @return The amount of tokens consumed by the hook operation
    *
    * Use cases:
    * - Executing the hook if enabled.
    * - Sending the hook amount to the sticky token.
    * - Returning the amount minus the hook amount.
    */
    function tryHook(address asset, uint256 amount, uint256[] memory tokenIds, address recipient) private returns (uint256) {

        // If the token is the sticky token, execute the hook
        // This hook dont send ammount to the sticky token, but is designed to track for expanded integration the burned IDs.
        if (asset == STICKY_ASSET) {

            if (bio == BIO.HOOK || bio == BIO.UNCHECKED) {

            try IGluedHooks(STICKY_ASSET).executeHook(asset, amount, tokenIds, recipient) {
                // Hook executed successfully
            } catch {
                // Hook execution failed, but we continue processing
                // The assets have already been transferred
            }

                // Return 0
                return 0;

            }

            // Return 0
            return 0;
        
        } else {

            // Initialize the hook amount
            uint256 hookAmount;

            // If the hook is unchecked, try to get the hook size
            if (bio == BIO.UNCHECKED) {

                // Try to get the hook size
                try IGluedHooks(STICKY_ASSET).hasHook() returns (bool hasHook) {

                    // If the hook is enabled, set the bio to hook
                    if (hasHook) {

                        // Set the bio to hook
                        bio = BIO.HOOK;
                    } else {

                        // Set the bio to no hook
                        bio = BIO.NO_HOOK;
                    }
                } catch {

                    // Set the bio to no hook
                    bio = BIO.NO_HOOK;
                }
            }

            // If the hook is enabled, try to get the hook size
            if (bio == BIO.HOOK) {

                // Try to get the hook size
                try IGluedHooks(STICKY_ASSET).hookSize(asset, amount) returns (uint256 hookSize) {

                    // If the hook size is greater than the precision, set the hook size to the precision
                    if (hookSize > PRECISION) {
                        hookSize = PRECISION;
                    }

                    // Calculate the hook amount
                    hookAmount = GluedMath.md512(amount, hookSize, PRECISION);

                } catch {
                    // If hook size retrieval fails, default to 0
                    return amount;
                }
            } else {

                // No hook enabled
                return amount;
            }

            // Ensure hook amount doesn't exceed available amount
            hookAmount = hookAmount > amount ? amount : hookAmount;
            
            // Only when there's actually an amount to transfer
            if (hookAmount > 0) {
                
                // If the token is not ETH, transfer the hook amount to the sticky token
                if (asset != ETH_ADDRESS) {

                    // Get the balance before
                    uint256 balanceBefore = IERC20(asset).balanceOf(STICKY_ASSET);

                    // Transfer the hook amount to the sticky token
                    IERC20(asset).safeTransfer(STICKY_ASSET, hookAmount);

                    // Get the balance after
                    uint256 balanceAfter = IERC20(asset).balanceOf(STICKY_ASSET);

                    // If the balance after is less than or equal to the balance before, set the hook amount to 0
                    if (balanceAfter <= balanceBefore) {

                        // Set the hook amount to 0
                        hookAmount = 0;

                    } else {

                        // Set the hook amount to the balance after minus the balance before
                        hookAmount = balanceAfter - balanceBefore;
                        
                    }
                } else {

                    // Send the hook amount to the sticky token
                    payable(STICKY_ASSET).sendValue(hookAmount);

                }
            }
            
            // Call appropriate hook function with try-catch to handle potential failures
            try IGluedHooks(STICKY_ASSET).executeHook(asset, hookAmount, tokenIds, recipient) {
                // Hook executed successfully
            } catch {
                // Hook execution failed, but we continue processing
                // The assets have already been transferred
            }

            // Return the amount minus the hook amount
            return amount - hookAmount;
        }
    }

    /**
    * @notice Retrieves the balance of the specified token held by the glue.
    * @dev This function is used to get the balance of the specified token for the given account.
    *
    * @param asset The address of the token contract.
    * @param account The address of the account.
    * @return The balance of the token held by the account.
    *
    * Use cases:
    * - Retrieving the balance of the specified token for the given account.
    */
    function getAssetBalance(address asset, address account) private view returns (uint256) {

        // If the token is ETH, return the balance of the account
        if (asset == ETH_ADDRESS) {

            // Return the balance of the account
            return account.balance;
        } else {

            // Return the balance of the token
            return IERC20(asset).balanceOf(account);
        }
    }

    /**
    * @dev Retrieves the balance of a given ERC721 token for a specific account.
    * @param account The address of the account to check the balance for.
    * @return The balance of the ERC721 token held by the account.
    *
    * Use cases:
    * - Retrieving the balance of the ERC721 token for the given account.
    */
    function getNFTBalance(address account) private view returns (uint256) {

        // If the account is the zero address, return 0
        if (account == address(0)) {

            // Return 0
            return 0;
        }

        // Try to get the balance of the ERC721 token
        try IERC721(STICKY_ASSET).balanceOf(account) returns (uint256 balance) {

            // Return the balance
            return balance;
        } catch {

            // Return 0
            return 0;   
        }
    }

    /**
    * @notice Retrieves the total supply of the specified token.
    * @dev This function is used to get the total supply of the specified token.
    *
    * @return The total supply of the token.
    *
    * Use cases:
    * - Retrieving the total supply of the specified token.
    */
    function getNFTTotalSupply() private view returns (uint256) {

        // Try to get the total supply of the ERC721 token
        uint256 totalSupply = IERC721Enumerable(STICKY_ASSET).totalSupply();

        // Return the total supply
        return totalSupply;
    }

    /**
    * @notice Calculates the asset availability based on the asset balance and supply delta.
    * @dev This function is used to calculate the asset availability based on the asset balance and supply delta.
    *
    * @param assetBalance The balance of the asset.
    * @param supplyDelta The supply delta.
    * @return The calculated asset availability.
    *
    * Use cases:
    * - Calculating the asset availability based on the asset balance and supply delta.
    */
    function calculateAssetAvailability(uint256 assetBalance, uint256 supplyDelta) private pure returns (uint256) {

        // Return the calculated asset availability
        return GluedMath.md512(assetBalance, supplyDelta, PRECISION);
    }

    /**
    * @notice Initiates a flash loan.
    * @dev This function is used to initiate a flash loan.
    *
    * @param collateral The address of the collateral token.
    * @param amount The amount of tokens to flash loan.
    * @param receiver The address of the receiver.
    * @param params The parameters for the flash loan.
    * @return success boolean indicating success
    *
    * Use cases:
    * - Initiating a simplified Glued loan from this Glue.
    * - Initiating a flash loan with simpler integration.
    */
    function flashLoan(address collateral,uint256 amount,address receiver,bytes calldata params) external override returns (bool success) {
        
        // Create an array with just this glue address
        address[] memory glues = new address[](1);

        // Set the glue address
        glues[0] = address(this);
        
        // Call the GlueStick's gluedLoan function
        try IGlueStickERC721(GLUE_STICK).gluedLoan(glues,collateral,amount,receiver,params) {

            // Set the success to true
            success = true;

        // If the loan operation failed
        } catch {

            // Set the success to false
            success = false;
        }
    }

    /**
    * @notice Initiates a minimal flash loan.
    * @dev This function is used for the Glue Stick to handle collateral in a Glued Loan.
    * @dev Only the Glue Stick can call this function.
    *
    * @param receiver The address of the receiver.
    * @param collateral The address of the token to flash loan.
    * @param amount The amount of tokens to flash loan.
    * @return loanSent boolean indicating success
    *
    * Use cases:
    * - Handle collateral in a Glued Loan.
    */
    function loanHandler(address receiver, address collateral, uint256 amount) external override nnrtnt returns (bool loanSent) {
        
        // If the sender is not the glue stick, revert
        if(msg.sender != GLUE_STICK) revert Unauthorized();

        // If the token is the sticky token, revert
        if(collateral == STICKY_ASSET) revert InvalidAsset(collateral);

        // If the token is ETH, send the amount to the receiver
        if(collateral == ETH_ADDRESS) {

            // Send the amount to the receiver
            payable(receiver).sendValue(amount);

        } else {

            // If the token is not ETH, transfer the amount to the receiver
            IERC20(collateral).safeTransfer(receiver, amount);
        }

        // Emit the GlueLoan event
        emit GlueLoan(collateral, amount, receiver);
        
        // Return Status
        return true;
    }

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▄▄▄ 
▐▌ ▐▌▐▌   ▐▌ ▐▌▐▌  █
▐▛▀▚▖▐▛▀▀▘▐▛▀▜▌▐▌  █
▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌▐▙▄▄▀
01010010 01100101 
01100001 01100100                         
*/

    /**
    * @notice Calculates the supply delta based on the sticky NFT amount and total supply.
    * @dev This function is used to calculate the supply delta based on the sticky NFT amount and total supply.
    *
    * @param stickyAmount The amount of sticky NFTs.
    * @return supplyDelta The calculated supply delta.
    *
    * Use cases:
    * - Calculating the supply delta based on the sticky NFT amount.
    *
    */
    function getSupplyDelta(uint256 stickyAmount) public view override returns (uint256 supplyDelta) {

        // Get the real total supply
        (uint256 beforeTotalSupply, ) = getRealTotalSupply(stickyAmount);

        // Return the calculated supply delta
        return calculateSupplyDelta(stickyAmount, beforeTotalSupply);
    }

    /** 
    * @notice Retrieves the adjusted total supply of the Sticky NFT Collection.
    * @dev This function is used to get the adjusted total supply of the Sticky NFT Collection.
    *
    * @return adjustedTotalSupply The adjusted total supply of the Sticky NFT Collection.
    *
    * Use cases:
    * - Retrieving the adjusted and actual total supply of the Sticky NFT Collection.
    */
    function getAdjustedTotalSupply() external view override returns (uint256 adjustedTotalSupply) {

        // Get the real total supply
        (uint256 beforeTotalSupply, ) = getRealTotalSupply(0); 

        // Return the adjusted total supply
        return beforeTotalSupply;
    }

    /**
    * @notice Retrieves the protocol fee percentage.
    * @dev This function is used to get the protocol fee percentage.
    *
    * @return protocolFee The protocol fee as a fixed-point number with 18 decimal places.
    *
    * Use cases:
    * - Retrieving the protocol fee percentage fixed to 1e15 = 0.1% | 1e18 = 100%.
    */
    function getProtocolFee() external pure override returns (uint256 protocolFee) {

        // Return the protocol fee
        return (PROTOCOL_FEE);
    }

    /**
    * @notice Retrieves the flash loan fee percentage.
    * @dev This function is used to get the flash loan fee percentage.
    * @dev The flash loan fee is fully paid to the Glue
    *
    * @return flashLoanFee The flash loan fee as a fixed-point number with 18 decimal places.
    *
    * Use cases:
    * - Retrieving the flash loan fee percentage fixed to 1e14 = 0.01% | 1e18 = 100%.
    */
    function getFlashLoanFee() external pure override returns (uint256 flashLoanFee) {

        // Return the flash loan fee
        return (LOAN_FEE);
    }

    /**
    * @notice Retrieves the flash loan fee for a given amount.
    * @dev This function is used to get the flash loan fee for a given amount.
    *
    * @param amount The amount to calculate the flash loan fee for.
    * @return fee The flash loan fee applied to a given amount.
    *
    * Use cases:
    * - Retrieving the flash loan fee applied to a given amount.
    */
    function getFlashLoanFeeCalculated(uint256 amount) external pure override returns (uint256 fee) {

        // Return the flash loan fee applied to a given amount
        return (GluedMath.md512Up(amount, LOAN_FEE, PRECISION));
    }

    /**
    * @notice Retrieves the total hook size for a sepecific collateral.
    * @dev This function is used to get the total hook size for a sepecific collateral or sticky token.
    *
    * @param collateral The address of the collateral token.
    * @param collateralAmount The amount of tokens to calculate the hook size for.
    * @return hookSize The total hook size.
    *
    * Use cases:
    * - Retrieving the total hook size for a specific collateral.
    */
    function getTotalHookSize(address collateral, uint256 collateralAmount) public view override returns (uint256 hookSize) {
        
        // If the collateral is the sticky token, return 0
        if (collateral == STICKY_ASSET) {

            // Return 0
            return 0;
        }
        
        // Try to get inHookSize if the hook is enabled
        if (bio == BIO.HOOK) {

            // Try to get the hook size
            try IGluedHooks(STICKY_ASSET).hooksImpact(collateral, collateralAmount, 0) returns (uint256 size) {

                // Return the hook size
                return size;
            } catch {

                // Return 0
                return 0;
            }
        }

        // Return 0
        return 0;
    }

    /**
    * @notice Calculates the amount of collateral tokens that can be unglued for a given amount of sticky tokens.
    * @dev This function is used to calculate the amount of collateral tokens that can be unglued for a given amount of sticky tokens.
    *
    * @param stickyAmount The amount of sticky tokens to be burned.
    * @param collaterals An array of addresses representing the collateral tokens to unglue.
    * @return amounts An array containing the corresponding amounts that can be unglued.
    * @dev This function accounts for the protocol fee in its calculations.
    *
    * Use cases:
    * - Calculating the amount of collateral tokens that can be unglued for a given amount of sticky tokens.
    */
    function collateralByAmount (uint256 stickyAmount, address[] calldata collaterals) external view override returns (uint256[] memory amounts) {

        // If the collaterals array is empty, revert
        if(collaterals.length == 0) revert NoCollateralSelected();

        // If the amount is 0, revert
        if(stickyAmount == 0) revert ZeroAmount();

        // Calculate the supply delta based on the sticky token amount
        uint256 supplyDelta = getSupplyDelta(stickyAmount);
        
        // Create array for final unglue amounts
        uint256[] memory finalUnglueAmounts = new uint256[](collaterals.length);
        
        // Process each collateral and calculate available amounts with hooks
        for (uint256 i = 0; i < collaterals.length; i++) {

            // Get the collateral address
            address gluedCollateral = collaterals[i];
            
            // If the collateral is the sticky token, set the unglue amount to 0
            if(gluedCollateral == STICKY_ASSET) {

                // Set the unglue amount to 0   
                finalUnglueAmounts[i] = 0;

                // Continue to the next collateral
                continue;
            }
            
            // Get asset balance and calculate initial availability
            uint256 assetBalance = getAssetBalance(gluedCollateral, address(this));
            
            // If the asset balance is greater than 0
            if (assetBalance > 0) {

                // Calculate asset availability based on supply delta
                uint256 assetAvailability = calculateAssetAvailability(assetBalance, supplyDelta);
                
                // Apply protocol fee
                uint256 afterFeeAmount = assetAvailability - GluedMath.md512(assetAvailability, PROTOCOL_FEE, PRECISION);
                
                // Apply hooks if enabled
                uint256 hookSize = getTotalHookSize(gluedCollateral, afterFeeAmount);

                // If the hook size is greater than 0
                if (hookSize > 0) {

                    // Calculate the hook amount
                    uint256 hookAmount = GluedMath.md512(afterFeeAmount, hookSize, PRECISION);
                    
                    // If the hook amount is greater than the after fee amount, set the hook amount to the after fee amount
                    if (hookAmount > afterFeeAmount) {
                        hookAmount = afterFeeAmount;
                    }
                    
                    // Set the unglue amount to the after fee amount minus the hook amount
                    finalUnglueAmounts[i] = afterFeeAmount - hookAmount;
                } else {

                    // Set the unglue amount to the after fee amount
                    finalUnglueAmounts[i] = afterFeeAmount;
                }
            } else {

                // Set the unglue amount to 0
                finalUnglueAmounts[i] = 0;
            }
        }

        // Return the collaterals and the final unglue amounts
        return (finalUnglueAmounts);
    }

    /**
    * @notice Retrieves the balance of an array of specified collateral tokens for the glue contract.
    * @dev This function is used to get the balance of an array of specified collateral tokens for the glue contract.
    *
    * @param collaterals An array of addresses representing the collateral tokens.
    * @return balances An array containing the corresponding balances.
    *
    * Use cases:
    * - Retrieving the balance of an array of specified collateral tokens for the glue contract.
    */
    function getBalances(address[] calldata collaterals) external view override returns (uint256[] memory balances) {

        // Create an array for the balances
        balances = new uint256[](collaterals.length);

        // Process each collateral and get the balance
        for (uint256 i = 0; i < collaterals.length; i++) {

            // Get the balance of the collateral
            balances[i] = getAssetBalance(collaterals[i], address(this));
        }

        // Return the collateral addresses and the balances
        return (balances);
    }

    /**
    * @notice Retrieves the balance of the sticky NFTs for the glue contract.
    * @dev This function is used to get the balance of the sticky NFTs for the glue contract.
    *
    * @return stickyAmount The balance of the sticky NFTs.
    *
    * Use cases:
    * - Retrieving the balance of the sticky NFTs for the glue contract.
    */
    function getStickySupplyStored() external view override returns (uint256 stickyAmount) {

        // Return the balance of the sticky token
        return getNFTBalance(address(this));
    }

    /**
    * @notice Retrieves the settings contract address.
    * @dev This function is used to get the settings contract address.
    *
    * @return settings The address of the settings contract.
    *
    * Use cases:
    * - Retrieving the settings contract address.
    */
    function getSettings() external pure override returns (address settings) {

        // Return the settings contract address
        return SETTINGS;
    }

    /**
    * @notice Retrieves the address of the GlueStick factory contract.
    * @dev This function is used to get the address of the GlueStick factory contract.
    *
    * @return glueStick The address of the GlueStick factory contract.
    *
    * Use cases:
    * - Retrieving the address of the GlueStick factory contract.
    */
    function getGlueStick() external view override returns (address glueStick) {

        // Return the glue stick address
        return GLUE_STICK;
    }

    /**
    * @notice Retrieves the address of the sticky token.
    * @dev This function is used to get the address of the sticky token.
    *
    * @return stickyAsset The address of the sticky NFT collection.
    *
    * Use cases:
    * - Retrieving the address of the sticky token.
    */
    function getStickyAsset() external view override returns (address stickyAsset) {

        // Return the sticky token address
        return STICKY_ASSET;
    }

    /**
    * @notice Retrieves if the glue is expanded with active Hooks.
    * @dev This function is used to get if the glue is expanded with active Hooks:
    * - BIO.HOOK: The glue is expanded with active Hooks.
    * - BIO.NO_HOOK: The glue is not expanded with active Hooks.
    * - BIO.UNCHECKED: The glue didn't have learned yet (before the first unglue interaction).
    *
    * @return hooksStatus The bio of the hooks status.
    *
    * Use cases:
    * - Knowing if the glue is expanded with active Hooks for external interactions.
    */
    function isExpanded() external view override returns (BIO hooksStatus) {

        // Return the hooks status
        return bio;
    }

    /**
    * @notice Retrieves if the Sticky Asset is natively not burnable and 
    * if the sticky token is permanently stored in the contract.
    * @dev This function is used to get if the Sticky Asset is natively not burnable, 
    * and if the sticky token is permanently stored in the contract.
    *
    * @return noNativeBurn A boolean representing if the sticky asset is natively not burnable.
    * @return stickySupplyGlued A boolean representing if the sticky token is permanently stored in the contract.
    *
    * Use cases:
    * - Knowing if the Sticky Asset is natively not burnable and if the sticky token is permanently stored in the contract.
    */
    function getSelfLearning() external view override returns (bool noNativeBurn, bool stickySupplyGlued) {

        // Return if not burnable and sticky supply stored flags
        return (notBurnable, stickySupplyStored);
    }

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

File 3 of 22 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

File 4 of 22 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)

pragma solidity ^0.8.20;

import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    error CloneArgumentsTooLong();

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        return clone(implementation, 0);
    }

    /**
     * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
     * to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function clone(address implementation, uint256 value) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(value, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple times will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        return cloneDeterministic(implementation, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
     * a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministic(
        address implementation,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(value, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create opcode, which should never revert.
     */
    function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
        return cloneWithImmutableArgs(implementation, args, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
     * parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneWithImmutableArgs(
        address implementation,
        bytes memory args,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        assembly ("memory-safe") {
            instance := create(value, add(bytecode, 0x20), mload(bytecode))
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
     * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
     * at the same address.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal returns (address instance) {
        return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
     * but with a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.deploy(value, salt, bytecode);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.computeAddress(salt, keccak256(bytecode), deployer);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
    }

    /**
     * @dev Get the immutable args attached to a clone.
     *
     * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
     *   function will return an empty array.
     * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
     *   `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
     *   creation.
     * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
     *   function should only be used to check addresses that are known to be clones.
     */
    function fetchCloneArgs(address instance) internal view returns (bytes memory) {
        bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
        assembly ("memory-safe") {
            extcodecopy(instance, add(result, 32), 45, mload(result))
        }
        return result;
    }

    /**
     * @dev Helper that prepares the initcode of the proxy with immutable args.
     *
     * An assembly variant of this function requires copying the `args` array, which can be efficiently done using
     * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
     * abi.encodePacked is more expensive but also more portable and easier to review.
     *
     * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
     * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
     */
    function _cloneCodeWithImmutableArgs(
        address implementation,
        bytes memory args
    ) private pure returns (bytes memory) {
        if (args.length > 24531) revert CloneArgumentsTooLong();
        return
            abi.encodePacked(
                hex"61",
                uint16(args.length + 45),
                hex"3d81600a3d39f3363d3d373d3d3d363d73",
                implementation,
                hex"5af43d82803e903d91602b57fd5bf3",
                args
            );
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
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);
}

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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Enumerable.sol)

pragma solidity ^0.8.20;

import {IERC721} from "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}

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

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 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 ERC-721 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 ERC-721
     * 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 address zero.
     *
     * 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 12 of 22 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.20;

/**
 * @title ERC-721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC-721 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 13 of 22 : ERC721Holder.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/utils/ERC721Holder.sol)

pragma solidity ^0.8.20;

import {IERC721Receiver} from "../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}.
 */
abstract contract ERC721Holder is IERC721Receiver {
    /**
     * @dev See {IERC721Receiver-onERC721Received}.
     *
     * Always returns `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }

        (bool success, bytes memory returndata) = recipient.call{value: amount}("");
        if (!success) {
            _revert(returndata);
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {Errors.FailedCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
     * of an unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {Errors.FailedCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            assembly ("memory-safe") {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert Errors.FailedCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}

File 16 of 22 : Errors.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * 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[ERC 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);
}

// SPDX-License-Identifier: BUSL-1.1
// https://github.com/glue-finance/glue/blob/main/LICENCE.txt

/**

 ██████╗ ██╗     ██╗   ██╗███████╗██████╗                             
██╔════╝ ██║     ██║   ██║██╔════╝██╔══██╗                            
██║  ███╗██║     ██║   ██║█████╗  ██║  ██║                            
██║   ██║██║     ██║   ██║██╔══╝  ██║  ██║                            
╚██████╔╝███████╗╚██████╔╝███████╗██████╔╝                            
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═════╝                             
██╗  ██╗ ██████╗  ██████╗ ██╗  ██╗███████╗                            
██║  ██║██╔═══██╗██╔═══██╗██║ ██╔╝██╔════╝                            
███████║██║   ██║██║   ██║█████╔╝ ███████╗                            
██╔══██║██║   ██║██║   ██║██╔═██╗ ╚════██║                            
██║  ██║╚██████╔╝╚██████╔╝██║  ██╗███████║                            
╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═╝╚══════╝                            
██╗███╗   ██╗████████╗███████╗██████╗ ███████╗ █████╗  ██████╗███████╗
██║████╗  ██║╚══██╔══╝██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝
██║██╔██╗ ██║   ██║   █████╗  ██████╔╝█████╗  ███████║██║     █████╗  
██║██║╚██╗██║   ██║   ██╔══╝  ██╔══██╗██╔══╝  ██╔══██║██║     ██╔══╝  
██║██║ ╚████║   ██║   ███████╗██║  ██║██║     ██║  ██║╚██████╗███████╗
╚═╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝  ╚═╝ ╚═════╝╚══════╝

*/

pragma solidity ^0.8.28;

/**
 * @title IGluedHooks
 * @author BasedToschi
 * @notice Extension interface that defines callback mechanisms for Sticky Assets to interact with the Glue Protocol
 * @dev Provides a standardized set of methods that Sticky Assets must implement to receive and process callbacks
 * during critical operations like collateral ungluing and token burning. This interface enables advanced features
 * like fee collection, rebasing mechanisms, and custom logic execution during protocol interactions.
 */

interface IGluedHooks {
    
    /**
     * @notice Calculates the appropriate hook size for token operations
     * @dev Called by the Glue Protocol to determine what percentage of tokens should be redirected to the token contract
     * during ungluing operations. The return value represents a percentage in PRECISION units (1e18 = 100%)
     *
     * @param asset The address of the token being processed (collateral or sticky token)
     * @param amount The amount of tokens being processed by the hook
     * @return size The hook size as a percentage in PRECISION units (e.g. 1e18 = 100%, 5e17 = 50%)
     *
     * Use case: Implementing dynamic fee models, rebasing mechanisms, or treasury allocations during ungluing
     */
    function hookSize(address asset, uint256 amount) external view returns (uint256 size);
    
    /**
     * @notice Main hook execution function called during ungluing operations
     * @dev This function is invoked after tokens have been transferred to the sticky asset contract
     * and gives the asset an opportunity to execute custom logic with the received tokens
     *
     * @param asset The address of the token transferred to the contract (ETH = address(0))
     * @param amount The precise amount of tokens transferred to the contract
     * @param tokenIds Array of token IDs for ERC721 operations (empty for ERC20 tokens)
     * @param recipient The address of the recipient of the hook
     *
     * Use case: Implementing automatic token buybacks, liquidity provision, or reward distribution
     */
    function executeHook(address asset, uint256 amount, uint256[] memory tokenIds, address recipient) external;

    /**
     * @notice Calculates the total impact of all hooks during ungluing operations
     * @dev Provides an aggregated view of how hooks will affect the token distribution
     * for a specific collateral and amount combination
     *
     * @param collateral The address of the collateral token being unglued
     * @param collateralAmount The amount of collateral token being withdrawn from the glue
     * @param stickyAmount The amount of sticky tokens being burned (for ERC20 only)
     * @return size The combined impact of all hooks as a percentage in PRECISION units
     *
     * Use case: Client-side prediction of expected output amounts accounting for hooks
     */
    function hooksImpact(address collateral, uint256 collateralAmount, uint256 stickyAmount) external view returns (uint256 size);

   /**
     * @notice Global hook enablement flag
     * @dev Constant that indicates whether hook functionality is active for this token
     *
     * @return hook Boolean flag: true if hooks are enabled, false if disabled
     *
     * Use case: Protocol optimizations to skip hook processing entirely for tokens without hooks
     */
    function hasHook() external view returns (bool hook);

}

File 19 of 22 : IGluedLoanReceiver.sol
// SPDX-License-Identifier: BUSL-1.1
// https://github.com/glue-finance/glue/blob/main/LICENCE.txt

/**

 ██████╗ ██╗     ██╗   ██╗███████╗██████╗ ██╗      ██████╗  █████╗ ███╗   ██╗
██╔════╝ ██║     ██║   ██║██╔════╝██╔══██╗██║     ██╔═══██╗██╔══██╗████╗  ██║
██║  ███╗██║     ██║   ██║█████╗  ██║  ██║██║     ██║   ██║███████║██╔██╗ ██║
██║   ██║██║     ██║   ██║██╔══╝  ██║  ██║██║     ██║   ██║██╔══██║██║╚██╗██║
╚██████╔╝███████╗╚██████╔╝███████╗██████╔╝███████╗╚██████╔╝██║  ██║██║ ╚████║
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═══╝
██████╗ ███████╗ ██████╗███████╗██╗██╗   ██╗███████╗██████╗                  
██╔══██╗██╔════╝██╔════╝██╔════╝██║██║   ██║██╔════╝██╔══██╗                 
██████╔╝█████╗  ██║     █████╗  ██║██║   ██║█████╗  ██████╔╝                 
██╔══██╗██╔══╝  ██║     ██╔══╝  ██║╚██╗ ██╔╝██╔══╝  ██╔══██╗                 
██║  ██║███████╗╚██████╗███████╗██║ ╚████╔╝ ███████╗██║  ██║                 
╚═╝  ╚═╝╚══════╝ ╚═════╝╚══════╝╚═╝  ╚═══╝  ╚══════╝╚═╝  ╚═╝                 
██╗███╗   ██╗████████╗███████╗██████╗ ███████╗ █████╗  ██████╗███████╗       
██║████╗  ██║╚══██╔══╝██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝       
██║██╔██╗ ██║   ██║   █████╗  ██████╔╝█████╗  ███████║██║     █████╗         
██║██║╚██╗██║   ██║   ██╔══╝  ██╔══██╗██╔══╝  ██╔══██║██║     ██╔══╝         
██║██║ ╚████║   ██║   ███████╗██║  ██║██║     ██║  ██║╚██████╗███████╗       
╚═╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝  ╚═╝ ╚═════╝╚══════╝       

 */
pragma solidity ^0.8.28;

/**
 * @title IGluedLoanReceiver
 * @author BasedToschi
 * @notice Interface that must be implemented by contracts receiving glued loans from the Glue Protocol
 * @dev This interface standardizes the callback mechanism used by the Glue Protocol's flash loan feature,
 * allowing seamless integration with DeFi protocols and custom applications
 */
interface IGluedLoanReceiver {
    /**
     * @notice Executes custom logic after receiving a flash loan from the Glue Protocol
     * @dev This function is called by the protocol after transferring borrowed assets to the receiver contract
     * The receiver must ensure that the borrowed assets plus fees are returned to the glue contracts before this
     * function completes execution, otherwise the transaction will revert
     * 
     * @param glues Array of glue contract addresses that provided the loaned assets
     * @param collateral Address of the borrowed token (address(0) for ETH)
     * @param expectedAmounts Array of amounts expected to be repaid to each glue contract
     * @param params Arbitrary data passed by the loan initiator for custom execution
     * @return loanSuccess Boolean indicating whether the operation was successful
     * 
     * Use cases:
     * - Flash Loans across multiple glues
     * - Capital-efficient arbitrage across DEXes
     * - Liquidation operations in lending protocols
     * - Complex cross-protocol interactions requiring upfront capital
     * - Temporary liquidity for atomic multi-step operations
     * - Collateral swaps without requiring pre-owned capital
     */
    function executeOperation(
        address[] memory glues,
        address collateral,
        uint256[] memory expectedAmounts,
        bytes memory params
    ) external returns (bool loanSuccess);
}

// SPDX-License-Identifier: BUSL-1.1
// https://github.com/glue-finance/glue/blob/main/LICENCE.txt
/**

 ██████╗ ██╗     ██╗   ██╗███████╗██████╗                             
██╔════╝ ██║     ██║   ██║██╔════╝██╔══██╗                            
██║  ███╗██║     ██║   ██║█████╗  ██║  ██║                            
██║   ██║██║     ██║   ██║██╔══╝  ██║  ██║                            
╚██████╔╝███████╗╚██████╔╝███████╗██████╔╝                            
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═════╝                             
███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗      
██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝      
███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗      
╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║      
███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║      
╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝      
██╗███╗   ██╗████████╗███████╗██████╗ ███████╗ █████╗  ██████╗███████╗
██║████╗  ██║╚══██╔══╝██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝
██║██╔██╗ ██║   ██║   █████╗  ██████╔╝█████╗  ███████║██║     █████╗  
██║██║╚██╗██║   ██║   ██╔══╝  ██╔══██╗██╔══╝  ██╔══██║██║     ██╔══╝  
██║██║ ╚████║   ██║   ███████╗██║  ██║██║     ██║  ██║╚██████╗███████╗
╚═╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝  ╚═╝ ╚═════╝╚══════╝

 */

pragma solidity ^0.8.28;

/**
 * @title IGluedSettings
 * @dev Interface for managing protocol configuration parameters, fee structures, and administrative controls.
 * This contract serves as the central configuration hub for the Glued protocol, allowing for
 * dynamic adjustment of fees, designation of fee recipients, and governance of protocol parameters.
 * The interface includes functions for ownership management, fee updates, address assignments,
 * and provides various status checks for protocol governance.
 */
interface IGluedSettings {

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖ ▗▖▗▖  ▗▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖  ▗▖ ▗▄▄▖
▐▌   ▐▌ ▐▌▐▛▚▖▐▌▐▌     █    █  ▐▌ ▐▌▐▛▚▖▐▌▐▌   
▐▛▀▀▘▐▌ ▐▌▐▌ ▝▜▌▐▌     █    █  ▐▌ ▐▌▐▌ ▝▜▌ ▝▀▚▖
▐▌   ▝▚▄▞▘▐▌  ▐▌▝▚▄▄▖  █  ▗▄█▄▖▝▚▄▞▘▐▌  ▐▌▗▄▄▞▘
01000110 01110101 01101110 01100011 01110100 
01101001 01101111 01101110 01110011                               
*/

    // █████╗ Management Addresses
    // ╚════╝ Owner: Can update most of the parameters
    //        Team: Can receive portion of protocol fees
    //        Glue: Can receive glue protocol fees
    
    /**
    * @notice Transfers governance control to a new owner address
    * @dev Critical operation that shifts all admin capabilities to the provided address
    *
    * @param newOwner The address of the new contract owner that will receive full administrative control
    *
    * Use case: Transferring protocol control during governance transitions or to multisig/DAO control
    */
    function transferOwnership(address newOwner) external;

    /**
    * @notice Sets the team address that receives remaining protocol fee portion
    * @dev Only the current team address can update this to ensure secure transitions
    *
    * @param newTeamAddress The new address to receive the team's share of protocol fees
    *
    * Use case: Transitioning team treasury management or updating protocol revenue destinations
    */
    function setTeamAddress(address newTeamAddress) external;

    /**
    * @notice Sets the address that receives the main glue fee portion
    * @dev Updates the destination for glue protocol fees, impacting protocol revenue distribution
    *
    * @param newGlueFeeAddress The new address to receive the glue fee revenue share
    *
    * Use case: Redirecting protocol revenue to new treasury contracts or revenue management systems
    */
    function setGlueFeeAddress(address newGlueFeeAddress) external;

    /**
    * @notice Permanently renounces ownership of the contract
    * @dev Irreversibly sets owner to address(0), removing all governance capabilities
    *
    * Use case: Protocol decentralization milestone or transitioning to fully immutable operations
    */
    function renounceOwnership() external;

    // █████╗ Protocol Fees
    // ╚════╝ The total fee that affect expanded glue products
    //        The Glue V1 protocol fee is not affected by this options, that one is fixed at 0.1%
    
    /**
    * @notice Updates the expanded protocol fee percentage
    * @dev Controls the fee applied to expanded glue protocol operations, bounded by min/max safety limits
    *
    * @param newExpProtocolFee The new expanded protocol fee percentage (in PRECISION units)
    *
    * Use case: Fee adjustment in response to market conditions or protocol revenue requirements
    */
    function updateExpProtocolFee(uint256 newExpProtocolFee) external;

    /**
    * @notice Updates the swap protocol fee percentage
    * @dev Controls the fee applied to swap operations, bounded by min/max safety limits
    *
    * @param newSwapProtocolFee The new swap protocol fee percentage (in PRECISION units)
    *
    * Use case: Fee adjustment in response to market conditions or protocol revenue requirements
    */
    function updateSwapProtocolFee(uint256 newSwapProtocolFee) external;

    // █████╗ Glue Cut
    // ╚════╝ The portion of the protocol fees that are distributed to the glue fee address

    /**
    * @notice Updates the glue fee percentage for the Glue V1 protocol
    * @dev Sets the percentage of protocol fees distributed to the glue fee receiver
    *
    * @param newGlueFee The new main glue fee percentage (in PRECISION units)
    *
    * Use case: Adjusting fee distribution between protocol stakeholders based on governance decisions
    */
    function updateGlueFee(uint256 newGlueFee) external;

    /** 
    * @notice Updates the glue expansions fee percentage
    * @dev Controls the fee percentage applied specifically to protocol expansion operations
    *
    * @param newGlueExpFee The new glue expansion fee percentage (in PRECISION units)
    *
    * Use case: Fine-tuning economics for protocol expansion
    */
    function updateGlueExpFee(uint256 newGlueExpFee) external;
    
    /**
    * @notice Updates the glue swap fee percentage
    * @dev Controls the fee percentage applied specifically to protocol swap operations
    *
    * @param newGlueSwapFee The new glue swap fee percentage (in PRECISION units)
    *
    * Use case: Fine-tuning economics for protocol swap operations
    */
    function updateGlueSwapFee(uint256 newGlueSwapFee) external;

    // █████╗ Granular Permissions
    // ╚════╝ The permissions that can be permanently removed to lock the protocol parameters
    
    /**
    * @notice Permanently removes the ability to modify expanded protocol fees
    * @dev Irreversibly locks the expanded protocol fee parameter
    *
    * Use case: Granular control over protocol parameters
    */
    function removeExpProtocolFeeOwnership() external;
    
    /**
    * @notice Permanently removes the ability to modify the swap protocol fee
    * @dev Irreversibly locks the swap protocol fee parameter
    *
    * Use case: Granular control over protocol parameters
    */
    function removeSwapProtocolFeeOwnership() external;
    
    /**
    * @notice Permanently removes the ability to change the glue fee address
    * @dev Irreversibly locks the glue fee recipient address
    *
    * Use case: Granular control over protocol parameters
    */
    function removeGlueOwnership() external;
    
    /**
    * @notice Permanently removes the ability to modify the glue fee percentage
    * @dev Irreversibly locks the glue fee parameter
    *
    * Use case: Granular control over protocol parameters
    */
    function removeGlueFeeOwnership() external;
    
    /** 
    * @notice Permanently removes the ability to modify the glue expansion fee
    * @dev Irreversibly locks the glue expansion fee parameter
    *
    * Use case: Granular control over protocol parameters
    */
    function removeGlueExpFeeOwnership() external;
    
    /**
    * @notice Permanently removes the ability to modify the glue swap fee
    * @dev Irreversibly locks the glue swap fee parameter
    *
    * Use case: Granular control over protocol parameters
    */
    function removeGlueSwapFeeOwnership() external;

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▄▄▄ 
▐▌ ▐▌▐▌   ▐▌ ▐▌▐▌  █
▐▛▀▚▖▐▛▀▀▘▐▛▀▜▌▐▌  █
▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌▐▙▄▄▀
01010010 01100101 
01100001 01100100                         
*/
    
    // █████╗ Management Addresses
    // ╚════╝ Owner: Can update most of the parameters
    //        Team: Can receive portion of protocol fees
    //        Glue: Can receive glue protocol fees
    
    /**
    * @notice Retrieves the current owner address
    * @dev Provides public access to the address of the contract owner
    *
    * @return owner The current owner address
    *
    * Use case: Integration with protocol dashboards or verification of governance authority
    */
    function getOwner() external view returns (address owner);

    /**
    * @notice Retrieves the current team address
    * @dev Provides public access to the address receiving the team's protocol fee share
    *
    * @return team The current team address configured to receive protocol fees
    *
    * Use case: Integration with protocol dashboards or verification of fee destinations
    */
    function getTeamAddress() external view returns (address team);
    
    /**
    * @notice Retrieves the current glue fee address
    * @dev Provides public access to the address receiving the glue portion of protocol fees
    *
    * @return glue The current glue fee address configured to receive protocol fees
    *
    * Use case: Protocol analytics, fee flow verification, or integration with monitoring systems
    */
    function getGlue() external view returns (address glue);

    // █████╗ Protocol Fees
    // ╚════╝ The total fee that affect expanded glue products
    //        The Glue V1 protocol fee is not affected by this options, that one is fixed at 0.1%
    
    /**
    * @notice Retrieves the current expanded protocol fee percentage
    * @dev Provides public access to the fee rate applied to expanded protocol operations
    *
    * @return expansionsFee The current expansions protocol fee percentage (in PRECISION units)
    *
    * Use case: Fee calculation in expansion modules or client-side fee estimations
    */
    function getExpansionsFee() external view returns (uint256 expansionsFee);
    
    /**
    * @notice Retrieves the current swap protocol fee percentage
    * @dev Provides public access to the fee rate applied to swap operations
    *
    * @return tradingFee The current swap protocol fee percentage (in PRECISION units)
    *
    * Use case: Fee calculation in swap modules or client-side fee estimations
    */
    function getTradingFee() external view returns (uint256 tradingFee);

    // █████╗ Glue Cut
    // ╚════╝ The portion of the protocol fees that are distributed to the glue fee address
    
    /**
    * @notice Retrieves the current glue fee percentage
    * @dev Provides the percentage of protocol fees allocated to the glue fee address
    *
    * @return glueProtocolCut The current glue fee percentage (in PRECISION units)
    *
    * Use case: Fee distribution calculations or protocol revenue projections
    */
    function getGlueProtocolCut() external view returns (uint256 glueProtocolCut);
    
    /**
    * @notice Retrieves the current glue expansion fee percentage
    * @dev Provides the percentage applied specifically to expansion operations
    *
    * @return glueExpansionsCut The current glue expansion fee percentage (in PRECISION units)
    *
    * Use case: Protocol expansion planning or economic impact analysis
    */
    function getGlueExpansionsCut() external view returns (uint256 glueExpansionsCut);
    
    /**
    * @notice Retrieves the current glue swap fee percentage
    * @dev Provides the percentage applied specifically to swap operations
    *
    * @return glueTradingCut The current glue swap fee percentage (in PRECISION units)
    *
    * Use case: Protocol swap planning or economic impact analysis
    */
    function getGlueTradingCut() external view returns (uint256 glueTradingCut);

    // █████╗ Batch Info
    // ╚════╝ Functions designed to batch information retrieval for protocols interactions and UX
    
    /**
    * @notice Retrieves complete protocol fee configuration information
    * @dev Consolidated getter for core fee distribution parameters in a single call
    *
    * @return glueProtocolCut The portion of the protocol fees that are distributed to the glue fee address
    * @return glue The address receiving the glue portion of fees
    * @return team The address receiving the team portion of fees
    *
    * Use case: Efficient fee data retrieval for protocol operations and integrations
    */
    function getProtocolFeeInfo() external view returns (uint256 glueProtocolCut, address glue, address team);
    
    /**
    * @notice Retrieves extended protocol fee configuration including expansion parameters
    * @dev Consolidated getter for all fee parameters in a single call
    *
    * @return expansionsFee The current expanded protocol fee percentage
    * @return glueExpansionsCut The current glue expansion fee percentage
    * @return glue The address receiving the glue portion of fees
    * @return team The address receiving the team portion of fees
    *
    * Use case: Complete fee data retrieval for expansion modules and advanced protocol integrations
    */
    function getExpansionsFeeInfo() external view returns (uint256 expansionsFee, uint256 glueExpansionsCut, address glue, address team);
    
    /**
    * @notice Retrieves extended protocol fee configuration including swap parameters
    * @dev Consolidated getter for all fee parameters in a single call
    *
    * @return tradingFee The current swap protocol fee percentage
    * @return glueTradingCut The current glue swap fee percentage
    * @return glue The address receiving the glue portion of fees
    * @return team The address receiving the team portion of fees
    *
    * Use case: Complete fee data retrieval for swap modules and advanced protocol integrations
    */
    function getTradingFeeInfo() external view returns (uint256 tradingFee, uint256 glueTradingCut, address glue, address team);
    
    /**
    * @notice Retrieves the current state of all protocol governance permissions
    * @dev Provides a consolidated view of which protocol parameters can still be modified
    *
    * @return expProtocolFeeRenounced The changeable status of expanded protocol fee
    * @return swapProtocolFeeRenounced The changeable status of swap protocol fee
    * @return glueProtocolCutRenounced The changeable status of glue protocol cut
    * @return glueExpansionsCutRenounced The changeable status of glue expansion cut
    * @return glueTradingCutRenounced The changeable status of glue trading cut
    * @return glueAddressRenounced The changeable status of glue fee receiver
    *
    * Use case: Protocol governance dashboards or immutability verification for integrators
    */
    function getGlueOwnershipStatus() external view returns (bool expProtocolFeeRenounced, bool swapProtocolFeeRenounced, bool glueProtocolCutRenounced, bool glueExpansionsCutRenounced, bool glueTradingCutRenounced, bool glueAddressRenounced);

    // █████╗ Fee Ranges
    // ╚════╝ Information about the minimum and maximum values for each fees and glue cuts
    
    /**
    * @notice Retrieves the current expanded protocol fee range
    * @dev Provides public access to the minimum and maximum expanded protocol fee values
    *
    * @return minFee The minimum expansions fee percentage
    * @return maxFee The maximum expansions fee percentage
    *
    * Use case: Fee calculation in expansion modules or client-side fee estimations
    */
    function getExpansionsFeeRange() external pure returns (uint256 minFee, uint256 maxFee);

    /**
    * @notice Retrieves the current swap protocol fee range
    * @dev Provides public access to the minimum and maximum swap protocol fee values
    *
    * @return minFee The minimum trading fee percentage
    * @return maxFee The maximum trading fee percentage
    *
    * Use case: Fee calculation in swap modules or client-side fee estimations
    */
    function getTradingFeeRange() external pure returns (uint256 minFee, uint256 maxFee);

    /**
    * @notice Retrieves the current glue protocol cut range
    * @dev Provides public access to the minimum and maximum glue protocol cut values
    *
    * @return minCut The minimum glue cut from protocol fee percentage
    * @return maxCut The maximum glue cut from protocol fee percentage
    *
    * Use case: Fee distribution calculations or protocol revenue projections
    */
    function getGlueProtocolCutRange() external pure returns (uint256 minCut, uint256 maxCut);

    /**
    * @notice Retrieves the current glue expansion fee range
    * @dev Provides public access to the minimum and maximum glue expansion fee values
    *
    * @return minCut The minimum glue cut from expansion fee percentage
    * @return maxCut The maximum glue cut from expansion fee percentage
    *
    * Use case: Fee distribution calculations or protocol revenue projections
    */
    function getGlueExpansionsCutRange() external pure returns (uint256 minCut, uint256 maxCut);

    /**
    * @notice Retrieves the current glue trading fee range
    * @dev Provides public access to the minimum and maximum glue trading fee values
    *
    * @return minCut The minimum glue cut from trading fee percentage
    * @return maxCut The maximum glue cut from trading fee percentage
    *
    * Use case: Fee distribution calculations or protocol revenue projections
    */
    function getGlueTradingCutRange() external pure returns (uint256 minCut, uint256 maxCut);

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▄▄▖ ▗▄▄▖  ▗▄▖ ▗▄▄▖  ▗▄▄▖
▐▌   ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌   
▐▛▀▀▘▐▛▀▚▖▐▛▀▚▖▐▌ ▐▌▐▛▀▚▖ ▝▀▚▖
▐▙▄▄▖▐▌ ▐▌▐▌ ▐▌▝▚▄▞▘▐▌ ▐▌▗▄▄▞▘
01100101 01110010 01110010 
01101111 01110010 01110011
*/

    /**
    * @dev Error thrown when the caller does not have the permission to process the request
    */
    error OwnershipNotGranted();

    /**
    * @dev Error thrown when the inputs are invalid
    */
    error InvalidInputs();
    
/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖  ▗▖▗▄▄▄▖▗▖  ▗▖▗▄▄▄▖▗▄▄▖
▐▌   ▐▌  ▐▌▐▌   ▐▛▚▖▐▌  █ ▐▌   
▐▛▀▀▘▐▌  ▐▌▐▛▀▀▘▐▌ ▝▜▌  █  ▝▀▚▖
▐▙▄▄▖ ▝▚▞▘ ▐▙▄▄▖▐▌  ▐▌  █ ▗▄▄▞▘
01000101 01010110 01000101 
01001110 01010100 01010011
*/

    /**
    * @dev Emitted when ownership of the contract is transferred.
    * @param previousOwner Address of the former owner.
    * @param newOwner Address of the new owner.
    */
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
    * @dev Emitted when the team treasury address is changed.
    * @param previousTeamAddress The former team treasury address.
    * @param newTeamAddress The new team treasury address.
    */
    event TeamAddressUpdated(address indexed previousTeamAddress, address indexed newTeamAddress);

    /**
    * @dev Emitted when the Glue fee recipient address is changed.
    * @param previousGlueFeeAddress The former fee recipient address.
    * @param newGlueFeeAddress The new fee recipient address.
    */
    event GlueFeeAddressUpdated(address indexed previousGlueFeeAddress, address indexed newGlueFeeAddress);
    
    /**
    * @dev Emitted when the exponential protocol fee rate is updated.
    * @param newExpProtocolFee The new exponential protocol fee value.
    */
    event ExpProtocolFeeUpdated(uint256 newExpProtocolFee);
    
    /**
    * @dev Emitted when the swap protocol fee rate is updated.
    * @param newSwapProtocolFee The new swap protocol fee value.
    */
    event SwapProtocolFeeUpdated(uint256 newSwapProtocolFee);
    
    /**
    * @dev Emitted when the base Glue fee rate is updated.
    * @param newGlueFee The new base Glue fee value.
    */
    event GlueFeeUpdated(uint256 newGlueFee);
    
    /**
    * @dev Emitted when the exponential Glue fee rate is updated.
    * @param newGlueExpFee The new exponential Glue fee value.
    */
    event GlueExpFeeUpdated(uint256 newGlueExpFee);
    
    /**
    * @dev Emitted when the swap Glue fee rate is updated.
    * @param newGlueSwapFee The new swap Glue fee value.
    */
    event GlueSwapFeeUpdated(uint256 newGlueSwapFee);
    
    /**
    * @dev Emitted when the ability to modify exponential protocol fees is permanently removed.
    */
    event ExpProtocolFeeOwnershipRemoved();
    
    /**
    * @dev Emitted when the ability to modify swap protocol fees is permanently removed.
    */
    event SwapProtocolFeeOwnershipRemoved();
    
    /**
    * @dev Emitted when the ability to modify Glue ownership parameters is permanently removed.
    */
    event GlueOwnershipRemoved();
    
    /**
    * @dev Emitted when the ability to modify Glue fee parameters is permanently removed.
    */
    event GlueFeeOwnershipRemoved();
    
    /**
    * @dev Emitted when the ability to modify exponential Glue fee parameters is permanently removed.
    */
    event GlueExpFeeOwnershipRemoved();
    
    /**
    * @dev Emitted when the ability to modify swap Glue fee parameters is permanently removed.
    */
    event GlueSwapFeeOwnershipRemoved();
}

// SPDX-License-Identifier: BUSL-1.1
// https://github.com/glue-finance/glue/blob/main/LICENCE.txt
/**

 ██████╗ ██╗     ██╗   ██╗███████╗                                    
██╔════╝ ██║     ██║   ██║██╔════╝                                    
██║  ███╗██║     ██║   ██║█████╗                                      
██║   ██║██║     ██║   ██║██╔══╝                                      
╚██████╔╝███████╗╚██████╔╝███████╗                                    
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝   
██╗███╗   ██╗████████╗███████╗██████╗ ███████╗ █████╗  ██████╗███████╗
██║████╗  ██║╚══██╔══╝██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝
██║██╔██╗ ██║   ██║   █████╗  ██████╔╝█████╗  ███████║██║     █████╗  
██║██║╚██╗██║   ██║   ██╔══╝  ██╔══██╗██╔══╝  ██╔══██║██║     ██╔══╝  
██║██║ ╚████║   ██║   ███████╗██║  ██║██║     ██║  ██║╚██████╗███████╗
╚═╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝  ╚═╝ ╚═════╝╚══════╝                                 
███████╗ ██████╗ ██████╗     ███╗   ██╗███████╗████████╗███████╗      
██╔════╝██╔═══██╗██╔══██╗    ████╗  ██║██╔════╝╚══██╔══╝██╔════╝      
█████╗  ██║   ██║██████╔╝    ██╔██╗ ██║█████╗     ██║   ███████╗      
██╔══╝  ██║   ██║██╔══██╗    ██║╚██╗██║██╔══╝     ██║   ╚════██║      
██║     ╚██████╔╝██║  ██║    ██║ ╚████║██║        ██║   ███████║      
╚═╝      ╚═════╝ ╚═╝  ╚═╝    ╚═╝  ╚═══╝╚═╝        ╚═╝   ╚══════╝      

*/


pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/**

██████╗ ██╗   ██╗██████╗ ███╗   ██╗ █████╗ ██████╗ ██╗     ███████╗
██╔══██╗██║   ██║██╔══██╗████╗  ██║██╔══██╗██╔══██╗██║     ██╔════╝
██████╔╝██║   ██║██████╔╝██╔██╗ ██║███████║██████╔╝██║     █████╗  
██╔══██╗██║   ██║██╔══██╗██║╚██╗██║██╔══██║██╔══██╗██║     ██╔══╝  
██████╔╝╚██████╔╝██║  ██║██║ ╚████║██║  ██║██████╔╝███████╗███████╗
╚═════╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝  ╚═╝╚═════╝ ╚══════╝╚══════╝
███╗   ██╗███████╗████████╗███████╗                                
████╗  ██║██╔════╝╚══██╔══╝██╔════╝                                
██╔██╗ ██║█████╗     ██║   ███████╗                                
██║╚██╗██║██╔══╝     ██║   ╚════██║                                
██║ ╚████║██║        ██║   ███████║                                
╚═╝  ╚═══╝╚═╝        ╚═╝   ╚══════╝                                

* @title IERC721Burnable
* @author @BasedToschi
* @notice Interface for ERC721 tokens that support burning functionality
* @dev Extends the standard ERC721 interface with a burn function to destroy tokens
*/
interface IERC721Burnable is IERC721 {
    /**
    * @notice Burns (destroys) the token with the given ID
    * @dev The caller must own the token or be an approved operator
    * @param tokenId The ID of the token to burn
    */
    function burn(uint256 tokenId) external;
}

/**

 ██████╗ ██╗     ██╗   ██╗███████╗  
██╔════╝ ██║     ██║   ██║██╔════╝  
██║  ███╗██║     ██║   ██║█████╗    
██║   ██║██║     ██║   ██║██╔══╝    
╚██████╔╝███████╗╚██████╔╝███████╗  
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝  
███████╗████████╗██╗ ██████╗██╗  ██╗
██╔════╝╚══██╔══╝██║██╔════╝██║ ██╔╝
███████╗   ██║   ██║██║     █████╔╝ 
╚════██║   ██║   ██║██║     ██╔═██╗ 
███████║   ██║   ██║╚██████╗██║  ██╗
╚══════╝   ╚═╝   ╚═╝ ╚═════╝╚═╝  ╚═╝

* @title IGlueStickERC721
* @author @BasedToschi
* @notice Interface defining the factory contract API for creating and managing ERC721 NFT collection glue instances
* @dev This interface establishes the contract API for the GlueStickERC721 factory,
* which handles creation of glue contracts for NFT collections, batch operations,
* and cross-glue flash loans in the NFT branch of the Glue Protocol
*/
interface IGlueStickERC721 {
    
/**
--------------------------------------------------------------------------------------------------------
 ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▖ ▗▖▗▄▄▖ 
▐▌   ▐▌     █  ▐▌ ▐▌▐▌ ▐▌
 ▝▀▚▖▐▛▀▀▘  █  ▐▌ ▐▌▐▛▀▘ 
▗▄▄▞▘▐▙▄▄▖  █  ▝▚▄▞▘▐▌                                               
01010011 01100101 01110100 
01110101 01110000 
*/
    
    /**
    * @dev Struct for managing flash loan data across multiple glue contracts
    * @notice Encapsulates all data required for multi-glue flash loan operations
    */
    struct LoanData {
        uint256 count;              /// @notice Number of glue contracts participating in the loan
        uint256[] toBorrow;         /// @notice Amount to borrow from each glue contract
        uint256[] expectedAmounts;  /// @notice Expected repayment amount for each loan (including fees)
        uint256[] expectedBalances; /// @notice Expected final balance in each glue after repayment
    }

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖ ▗▖▗▖  ▗▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖  ▗▖ ▗▄▄▖
▐▌   ▐▌ ▐▌▐▛▚▖▐▌▐▌     █    █  ▐▌ ▐▌▐▛▚▖▐▌▐▌   
▐▛▀▀▘▐▌ ▐▌▐▌ ▝▜▌▐▌     █    █  ▐▌ ▐▌▐▌ ▝▜▌ ▝▀▚▖
▐▌   ▝▚▄▞▘▐▌  ▐▌▝▚▄▄▖  █  ▗▄█▄▖▝▚▄▞▘▐▌  ▐▌▗▄▄▞▘
01000110 01110101 01101110 01100011 01110100 
01101001 01101111 01101110 01110011                               
*/
    
    /**
    * @notice Creates a new GlueERC721 contract for a specified NFT collection
    * @dev Validates the NFT collection for compatibility, creates a deterministic clone
    * of the implementation contract, initializes it with the collection address, and
    * registers it in the protocol registry. The created glue instance becomes the
    * collateral vault for the NFT collection.
    * 
    * @param asset The address of the ERC721 collection to be glued
    * @return glueAddress The address of the newly created glue instance
    *
    * Use cases:
    * - Adding asset backing capabilities to existing NFT collections
    * - Creating collateralization mechanisms for NFTs
    * - Establishing new NFT economic models with withdrawal mechanisms
    * - Supporting floor price protection for collections through backing
    */
    function applyTheGlue(address asset) external returns (address glueAddress);

    /**
    * @notice Processes ungluing operations for multiple NFT collections in a single transaction
    * @dev Efficiently batches unglue operations across multiple NFT collections, managing the
    * transfer of NFTs from caller to glue contracts, and execution of unglue operations.
    * Supports both single and multiple recipient configurations.
    * 
    * @param stickyAssets Array of NFT collection addresses to unglue from
    * @param tokenIds Two-dimensional array of token IDs to unglue for each collection
    * @param collaterals Array of collateral addresses to withdraw (common across all unglue operations)
    * @param recipients Array of recipient addresses to receive the unglued collateral
    *
    * Use cases:
    * - Unglue collaterals across multiple sticky NFT collections
    * - Efficient withdrawal of collaterals from multiple sticky NFT collections
    * - Consolidated position exits for complex NFT strategies
    * - Multi-collection redemption in a single transaction
    */
    function batchUnglue(address[] calldata stickyAssets,uint256[][] calldata tokenIds,address[] calldata collaterals,address[] calldata recipients) external;
    
    /**
    * @notice Executes multiple flash loans across multiple glues.
    * @dev This function calculates the loans, executes them, and verifies the repayments.
    *
    * @param glues The addresses of the glues to borrow from.
    * @param collateral The address of the collateral to borrow.
    * @param loanAmount The total amount of collaterals to borrow.
    * @param receiver The address of the receiver.
    * @param params Additional parameters for the receiver.
    *
    * Use cases:
    * - Flash Loans across multiple glues
    * - Capital-efficient arbitrage across DEXes
    * - Liquidation operations in lending protocols
    * - Complex cross-protocol interactions requiring upfront capital
    * - Temporary liquidity for atomic multi-step operations
    * - Collateral swaps without requiring pre-owned capital
    */
    function gluedLoan(address[] calldata glues, address collateral, uint256 loanAmount, address receiver, bytes calldata params) external;

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▄▄▄ 
▐▌ ▐▌▐▌   ▐▌ ▐▌▐▌  █ 
▐▛▀▚▖▐▛▀▀▘▐▛▀▜▌▐▌  █
▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌▐▙▄▄▀
01010010 01100101 
01100001 01100100                         
*/
    
    /**
    * @notice Retrieves expected collateral amounts from batch ungluing operations for NFTs
    * @dev View function to calculate expected collateral returns for multiple NFT collections.
    * This is essential for front-end applications and integrations to estimate expected
    * returns before executing batch unglue operations.
    * 
    * @param stickyAssets Array of NFT collection addresses
    * @param stickyAmounts Array of NFT counts to simulate ungluing (number of NFTs, not IDs)
    * @param collaterals Array of collateral addresses to check
    * @return collateralAmounts 2D array of corresponding collateral amounts [glueIndex][collateralIndex]
    *
    * Use cases:
    * - Pre-transaction estimation for front-end applications
    * - Strategy optimization based on expected returns
    * - User interface displays showing potential redemption values
    */
    function getBatchCollaterals(address[] calldata stickyAssets,uint256[] calldata stickyAmounts,address[] calldata collaterals) external view returns (uint256[][] memory collateralAmounts);

    /**
    * @notice Checks if the given ERC721 address has valid totalSupply and no decimals
    * @dev This function performs static calls to check if token is a valid NFT
    * Token validation is critical for ensuring only compatible collections can be glued,
    * preventing issues with non-enumerable NFT collections.
    * 
    * @param asset The address of the ERC721 asset to check
    * @return isValid Indicates whether the token is valid
    *
    * Use cases:
    * - Pre-glue verification to prevent incompatible token issues
    * - Protocol security to maintain compatibility standards
    * - Front-end validation before attempting glue operations
    */
    function checkAsset(address asset) external view returns (bool isValid);
    
    /**
    * @notice Computes the address of the GlueERC721 contract for the given ERC721 address.
    * @dev Uses the Clones library to predict the address of the minimal proxy.
    *
    * @param asset The address of the ERC721 contract.
    * @return predictedGlueAddress The computed address of the GlueERC721 contract.
    *
    * Use cases:
    * - Complex integrations requiring pre-knowledge of glue addresses
    * - Front-end preparation before actual glue deployment
    * - Cross-contract interactions that reference glue addresses
    * - Security verification of expected deployment addresses
    */
    function computeGlueAddress(address asset) external view returns (address predictedGlueAddress);
    
    /**
    * @notice Checks if a given token is sticky and returns its glue address
    * @dev Utility function for external contracts and front-ends to verify token status
    * in the Glue protocol and retrieve the associated glue address if it exists.
    * 
    * @param asset The address of the NFT Collection to check
    * @return isSticky bool Indicates whether the token is sticky.
    * @return glueAddress The glue address for the token if it's sticky, otherwise address(0).
    *
    * Use cases:
    * - UI elements showing token glue status
    * - Protocol integrations needing to verify glue existence
    * - Smart contracts checking if a token can be unglued
    * - External protocols building on top of the Glue protocol
    */
    function isStickyAsset(address asset) external view returns (bool isSticky, address glueAddress);
    
    /**
    * @notice Retrieves the balances of multiple collaterals across multiple glues
    * @dev Returns a 2D array where each row represents a glue and each column represents a collateral
    * @dev This function is used to get the balances of multiple collaterals across multiple glues
    *
    * @param glues The addresses of the glues to check
    * @param collaterals The addresses of the collaterals to check for each glue
    * @return balances a 2D array of balances [glueIndex][collateralIndex]
    *
    * Use cases:
    * - Batch querying collateral positions across multiple glues
    * - Dashboard displays showing complete portfolio positions
    * - Cross-glue analytics and reporting
    */
    function getGluesBalances(address[] calldata glues, address[] calldata collaterals) external view returns (uint256[][] memory balances);

    /**
    * @notice Returns the total number of deployed glues.
    * @return existingGlues The length of the allGlues array.
    *
    * Use cases:
    * - Informational queries about the total number of deployed glues
    */
    function allGluesLength() external view returns (uint256 existingGlues);

    /**
    * @notice Retrieves the glue address for a given token
    * @dev Returns the glue address for the given token
    *
    * @param asset The address of the NFT collection to get the glue address for
    * @return glueAddress The glue address for the given token, if it exists, otherwise address(0)
    *
    * Use cases:
    * - Retrieving the glue address for a given token
    */
    function getGlueAddress(address asset) external view returns (address glueAddress);
    
    /**
    * @notice Retrieves a glue address by its index in the registry
    * @dev Returns the address of a deployed glue at the specified index
    * This provides indexed access to the array of all deployed glues
    * 
    * @param index The index in the allGlues array to query
    * @return glueAddress The address of the glue at the specified index
    *
    * Use cases:
    * - Enumeration of all deployed glues in the protocol
    * - Accessing specific glues by index for reporting or integration
    * - Batch operations on sequential glue addresses
    */
    function getGlueAtIndex(uint256 index) external view returns (address glueAddress);

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▄▄▖ ▗▄▄▖  ▗▄▖ ▗▄▄▖  ▗▄▄▖
▐▌   ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌   
▐▛▀▀▘▐▛▀▚▖▐▛▀▚▖▐▌ ▐▌▐▛▀▚▖ ▝▀▚▖
▐▙▄▄▖▐▌ ▐▌▐▌ ▐▌▝▚▄▞▘▐▌ ▐▌▗▄▄▞▘
01100101 01110010 01110010 
01101111 01110010 01110011
*/
    
    /**
    * @dev Error thrown when an invalid asset address is provided
    * @param asset The address of the invalid asset
    */
    error InvalidAsset(address asset);
    
    /**
    * @dev Error thrown when attempting to glue a collection that's already glued
    * @param asset The address of the duplicate collection
    */
    error DuplicateGlue(address asset);
    
    /**
    * @dev Error thrown when an invalid address (typically zero address) is provided
    */
    error InvalidAddress();
    
    /**
    * @dev Error thrown when function inputs are invalid or inconsistent
    */
    error InvalidInputs();
    
    /**
    * @dev Error thrown when a glue contract has insufficient balance
    * @param glue The address of the glue contract
    * @param balance The actual balance found
    * @param collateral The collateral being checked
    */
    error InvalidGlueBalance(address glue, uint256 balance, address collateral);
    
    /**
    * @dev Error thrown when there is insufficient liquidity for a flash loan
    * @param totalCollected The amount of liquidity that was collected
    * @param loanAmount The amount of liquidity that was required
    */
    error InsufficientLiquidity(uint256 totalCollected, uint256 loanAmount);
    
    /**
    * @dev Error thrown when a flash loan operation fails
    */
    error FlashLoanFailed();
    
    /**
    * @dev Error thrown when a flash loan repayment fails
    * @param glue The glue contract that failed to receive repayment
    */
    error RepaymentFailed(address glue);
    
    /**
    * @dev Error thrown when the deployment of a glue contract fails
    */
    error FailedToDeployGlue();
    
    /**
    * @dev Error thrown when no assets are selected for an operation
    */
    error NoAssetsSelected();
    
/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖  ▗▖▗▄▄▄▖▗▖  ▗▖▗▄▄▄▖▗▄▄▖
▐▌   ▐▌  ▐▌▐▌   ▐▛▚▖▐▌  █ ▐▌   
▐▛▀▀▘▐▌  ▐▌▐▛▀▀▘▐▌ ▝▜▌  █  ▝▀▚▖
▐▙▄▄▖ ▝▚▞▘ ▐▙▄▄▖▐▌  ▐▌  █ ▗▄▄▞▘
01000101 01010110 01000101 
01001110 01010100 01010011
*/
    
    /**
    * @notice Emitted when a new NFT collection is glued
    * @param asset The address of the collection that was glued
    * @param glueAddress The address of the created glue contract
    * @param glueIndex The index of the glue in the allGlues array
    */
    event GlueAdded(address indexed asset, address glueAddress, uint256 glueIndex);
    
    /**
    * @notice Emitted when a batch unglue operation is executed for NFTs
    * @param stickyAssets Array of NFT collections that were unglued
    * @param tokenIds Two-dimensional array of token IDs that were processed
    * @param collaterals Array of collateral addresses that were withdrawn
    * @param recipients Array of addresses that received the assets
    */
    event BatchUnglueExecuted(address[] stickyAssets,uint256[][] tokenIds,address[] collaterals,address[] recipients);
}

/**

████████╗██╗  ██╗███████╗         
╚══██╔══╝██║  ██║██╔════╝         
   ██║   ███████║█████╗           
   ██║   ██╔══██║██╔══╝           
   ██║   ██║  ██║███████╗         
   ╚═╝   ╚═╝  ╚═╝╚══════╝         
 ██████╗ ██╗     ██╗   ██╗███████╗
██╔════╝ ██║     ██║   ██║██╔════╝
██║  ███╗██║     ██║   ██║█████╗  
██║   ██║██║     ██║   ██║██╔══╝  
╚██████╔╝███████╗╚██████╔╝███████╗
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝

* @title IGlueERC721
* @author @BasedToschi
* @notice Interface for individual glue contract instances managing ERC721 NFT collection collateralization
* @dev This interface defines the API for GlueERC721 contracts that are created by the factory.
* These contracts manage collateral for NFT collections, process ungluing operations based on
* proportional NFT count vs. total supply, and provide flash loan functionality.
*/
interface IGlueERC721 {
    

/**
--------------------------------------------------------------------------------------------------------
 ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▖ ▗▖▗▄▄▖ 
▐▌   ▐▌     █  ▐▌ ▐▌▐▌ ▐▌
 ▝▀▚▖▐▛▀▀▘  █  ▐▌ ▐▌▐▛▀▘ 
▗▄▄▞▘▐▙▄▄▖  █  ▝▚▄▞▘▐▌                                               
01010011 01100101 01110100 
01110101 01110000 
*/
    
    /**
    * @notice Enum representing hook detection status
    * @dev Used to track if the collection implements and supports hooks
    * @dev UNCHECKED: The collection has not been checked for hooks. The check happens at the first unglue operation.
    * @dev NO_HOOK: The collection does not implement hooks
    * @dev HOOK: The collection implements hooks
    */
    enum BIO {UNCHECKED,NO_HOOK,HOOK}
    
    /**
    * @notice Initializes a newly deployed glue clone for an NFT collection
    * @dev Called by the factory when creating a new glue instance through cloning
    * Sets up the core state variables and establishes the relationship between
    * this glue instance and its associated NFT collection
    * 
    * @param asset Address of the ERC721 collection to be linked with this glue
    *
    * Use cases:
    * - Creating a new glue address for a NFT collection (now Sticky Token) in which attach collateral
    * - Establishing the collection-glue relationship in the protocol
    */
    function initialize(address asset) external;

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖ ▗▖▗▖  ▗▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▖ ▗▖  ▗▖ ▗▄▄▖
▐▌   ▐▌ ▐▌▐▛▚▖▐▌▐▌     █    █  ▐▌ ▐▌▐▛▚▖▐▌▐▌   
▐▛▀▀▘▐▌ ▐▌▐▌ ▝▜▌▐▌     █    █  ▐▌ ▐▌▐▌ ▝▜▌ ▝▀▚▖
▐▌   ▝▚▄▞▘▐▌  ▐▌▝▚▄▄▖  █  ▗▄█▄▖▝▚▄▞▘▐▌  ▐▌▗▄▄▞▘
01000110 01110101 01101110 01100011 01110100 
01101001 01101111 01101110 01110011                               
*/

    /**
    * @notice Core function that processes NFT ungluing operations to release collateral
    * @dev Handles the complete ungluing workflow for NFTs: verifying ownership,
    * managing transfers, calculating proportional collateral amounts, applying fees,
    * executing hook logic if enabled, and distributing collateral to the recipient.
    * 
    * @param collaterals Array of collateral token addresses to withdraw
    * @param tokenIds Array of NFT token IDs to burn for collateral withdrawal
    * @param recipient Address to receive the withdrawn collateral
    * @return supplyDelta Calculated proportion of total NFT supply (in PRECISION units)
    * @return realAmount Number of NFTs processed (after removing duplicates)
    * @return beforeTotalSupply NFT collection supply before the unglue operation
    * @return afterTotalSupply NFT collection supply after the unglue operation
    *
    * Use cases:
    * - Redeeming collateral from the protocol by burning NFTs
    * - Converting sticky NFTs back to their collaterals
    */
    function unglue(address[] calldata collaterals, uint256[] calldata tokenIds, address recipient) external returns (uint256 supplyDelta, uint256 realAmount, uint256 beforeTotalSupply, uint256 afterTotalSupply);
    
    /**
    * @notice Initiates a flash loan.
    * @dev This function is used to initiate a flash loan.
    *
    * @param collateral The address of the collateral token.
    * @param amount The amount of tokens to flash loan.
    * @param receiver The address of the receiver.
    * @param params The parameters for the flash loan.
    * @return success boolean indicating success
    *
    * Use cases:
    * - Initiating a simplified Glued loan from this Glue.
    * - Initiating a flash loan with simpler integration.
    */
    function flashLoan(address collateral, uint256 amount, address receiver, bytes calldata params) external returns (bool success);
    
    /**
    * @notice Initiates a minimal flash loan.
    * @dev This function is used for the Glue Stick to handle collateral in a Glued Loan.
    * @dev Only the Glue Stick can call this function.
    *
    * @param receiver The address of the receiver.
    * @param collateral The address of the token to flash loan.
    * @param amount The amount of tokens to flash loan.
    * @return loanSent boolean indicating success
    *
    * Use cases:
    * - Handle collateral in a Glued Loan.
    */
    function loanHandler(address receiver, address collateral, uint256 amount) external returns (bool loanSent);

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▄▄▄ 
▐▌ ▐▌▐▌   ▐▌ ▐▌▐▌  █
▐▛▀▚▖▐▛▀▀▘▐▛▀▜▌▐▌  █
▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌▐▙▄▄▀
01010010 01100101 
01100001 01100100                         
*/
    
    /**
    * @notice Calculates the supply delta based on the sticky NFT amount and total supply.
    * @dev This function is used to calculate the supply delta based on the sticky NFT amount and total supply.
    *
    * @param stickyAmount The amount of sticky NFTs.
    * @return supplyDelta The calculated supply delta.
    *
    * Use cases:
    * - Calculating the supply delta based on the sticky NFT amount.
    *
    */
    function getSupplyDelta(uint256 stickyAmount) external view returns (uint256 supplyDelta);
    
    /** 
    * @notice Retrieves the adjusted total supply of the Sticky NFT Collection.
    * @dev This function is used to get the adjusted total supply of the Sticky NFT Collection.
    *
    * @return adjustedTotalSupply The adjusted and actual total supply of the Sticky NFT Collection.
    *
    * Use cases:
    * - Retrieving the adjusted total supply of the Sticky NFT Collection.
    */
    function getAdjustedTotalSupply() external view returns (uint256 adjustedTotalSupply);
    
    /**
    * @notice Retrieves the protocol fee percentage.
    * @dev This function is used to get the protocol fee percentage.
    *
    * @return protocolFee The protocol fee as a fixed-point number with 18 decimal places.
    *
    * Use cases:
    * - Retrieving the protocol fee percentage fixed to 1e15 = 0.1% | 1e18 = 100%.
    */
    function getProtocolFee() external pure returns (uint256 protocolFee);
    
    /**
    * @notice Retrieves the flash loan fee percentage.
    * @dev This function is used to get the flash loan fee percentage.
    * @dev The flash loan fee is fully paid to the Glue
    *
    * @return flashLoanFee The flash loan fee as a fixed-point number with 18 decimal places.
    *
    * Use cases:
    * - Retrieving the flash loan fee percentage fixed to 1e14 = 0.01% | 1e18 = 100%.
    */
    function getFlashLoanFee() external pure returns (uint256 flashLoanFee);
    
    /**
    * @notice Retrieves the flash loan fee for a given amount.
    * @dev This function is used to get the flash loan fee for a given amount.
    *
    * @param amount The amount to calculate the flash loan fee for.
    * @return fee The flash loan fee applied to a given amount.
    *
    * Use cases:
    * - Retrieving the flash loan fee applied to a given amount.
    */
    function getFlashLoanFeeCalculated(uint256 amount) external pure returns (uint256 fee);

    /**
    * @notice Retrieves the total hook size for a sepecific collateral.
    * @dev This function is used to get the total hook size for a sepecific collateral or sticky token.
    *
    * @param collateral The address of the collateral token.
    * @param collateralAmount The amount of tokens to calculate the hook size for.
    * @return hookSize The total hook size.
    *
    * Use cases:
    * - Retrieving the total hook size for a specific collateral.
    */
    function getTotalHookSize(address collateral, uint256 collateralAmount) external view returns (uint256 hookSize);
    
    /**
    * @notice Calculates the amount of collateral tokens that can be unglued for a given amount of sticky tokens.
    * @dev This function is used to calculate the amount of collateral tokens that can be unglued for a given amount of sticky tokens.
    *
    * @param stickyAmount The amount of sticky tokens to be burned.
    * @param collaterals An array of addresses representing the collateral tokens to unglue.
    * @return amounts An array containing the corresponding amounts that can be unglued.
    * @dev This function accounts for the protocol fee in its calculations.
    *
    * Use cases:
    * - Calculating the amount of collateral tokens that can be unglued for a given amount of sticky tokens.
    */
    function collateralByAmount(uint256 stickyAmount, address[] calldata collaterals) external view returns (uint256[] memory amounts);
    
    /**
    * @notice Retrieves the balance of an array of specified collateral tokens for the glue contract.
    * @dev This function is used to get the balance of an array of specified collateral tokens for the glue contract.
    *
    * @param collaterals An array of addresses representing the collateral tokens.
    * @return balances An array containing the corresponding balances.
    *
    * Use cases:
    * - Retrieving the balance of an array of specified collateral tokens for the glue contract.
    */
    function getBalances(address[] calldata collaterals) external view returns (uint256[] memory balances);
    
    /**
    * @notice Retrieves the balance of the sticky NFTs for the glue contract.
    * @dev This function is used to get the balance of the sticky NFTs for the glue contract.
    *
    * @return stickyAmount The balance of the sticky NFTs.
    *
    * Use cases:
    * - Retrieving the balance of the sticky NFTs for the glue contract.
    */
    function getStickySupplyStored() external view returns (uint256 stickyAmount);

    /**
    * @notice Retrieves the settings contract address.
    * @dev This function is used to get the settings contract address.
    *
    * @return settings The address of the settings contract.
    *
    * Use cases:
    * - Retrieving the settings contract address.
    */
    function getSettings() external pure returns (address settings);

    /**
    * @notice Retrieves the address of the GlueStick factory contract.
    * @dev This function is used to get the address of the GlueStick factory contract.
    *
    * @return glueStick The address of the GlueStick factory contract.
    *
    * Use cases:
    * - Retrieving the address of the GlueStick factory contract.
    */
    function getGlueStick() external view returns (address glueStick);

    /**
    * @notice Retrieves the address of the sticky token.
    * @dev This function is used to get the address of the sticky token.
    *
    * @return stickyAsset The address of the sticky NFT collection.
    *
    * Use cases:
    * - Retrieving the address of the sticky token.
    */
    function getStickyAsset() external view returns (address stickyAsset);

    /**
    * @notice Retrieves if the glue is expanded with active Hooks.
    * @dev This function is used to get if the glue is expanded with active Hooks:
    * - BIO.HOOK: The glue is expanded with active Hooks.
    * - BIO.NO_HOOK: The glue is not expanded with active Hooks.
    * - BIO.UNCHECKED: The glue didn't have learned yet (before the first unglue interaction).
    *
    * @return hooksStatus The bio of the hooks status.
    *
    * Use cases:
    * - Knowing if the glue is expanded with active Hooks for external interactions.
    */
    function isExpanded() external view returns (BIO hooksStatus);
    
    /**
    * @notice Retrieves if the Sticky Asset is natively not burnable and 
    * if the sticky token is permanently stored in the contract.
    * @dev This function is used to get if the Sticky Asset is natively not burnable, 
    * and if the sticky token is permanently stored in the contract.
    *
    * @return noNativeBurn A boolean representing if the sticky asset is natively not burnable.
    * @return stickySupplyGlued A boolean representing if the sticky token is permanently stored in the contract.
    *
    * Use cases:
    * - Knowing if the Sticky Asset is natively not burnable and if the sticky token is permanently stored in the contract.
    */
    function getSelfLearning() external view returns (bool noNativeBurn, bool stickySupplyGlued);

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▄▄▖ ▗▄▄▖  ▗▄▖ ▗▄▄▖  ▗▄▄▖
▐▌   ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌   
▐▛▀▀▘▐▛▀▚▖▐▛▀▚▖▐▌ ▐▌▐▛▀▚▖ ▝▀▚▖
▐▙▄▄▖▐▌ ▐▌▐▌ ▐▌▝▚▄▞▘▐▌ ▐▌▗▄▄▞▘
01100101 01110010 01110010 
01101111 01110010 01110011
*/
    
    /**
    * @dev Error thrown when no assets are selected for ungluing
    */
    error NoAssetsSelected();
    
    /**
    * @dev Error thrown when an invalid GlueStick factory address is provided
    */
    error InvalidGlueStickAddress();
    
    /**
    * @dev Error thrown when an invalid asset address is provided
    * @param asset The address of the invalid asset
    */
    error InvalidAsset(address asset);
    
    /**
    * @dev Error thrown when no collateral is selected for ungluing
    */
    error NoCollateralSelected();
    
    /**
    * @dev Error thrown when no tokens are transferred during an operation
    */
    error NoAssetsTransferred();
    
    /**
    * @dev Error thrown when the contract fails to process an NFT collection operation
    */
    error FailedToProcessCollection();
    
    /**
    * @dev Error thrown when an unauthorized caller attempts an operation
    */
    error Unauthorized();
    
    /**
    * @dev Error thrown when a zero amount is provided where a non-zero amount is required
    */
    error ZeroAmount();

/**
--------------------------------------------------------------------------------------------------------
▗▄▄▄▖▗▖  ▗▖▗▄▄▄▖▗▖  ▗▖▗▄▄▄▖▗▄▄▖
▐▌   ▐▌  ▐▌▐▌   ▐▛▚▖▐▌  █ ▐▌   
▐▛▀▀▘▐▌  ▐▌▐▛▀▀▘▐▌ ▝▜▌  █  ▝▀▚▖
▐▙▄▄▖ ▝▚▞▘ ▐▙▄▄▖▐▌  ▐▌  █ ▗▄▄▞▘
01000101 01010110 01000101 
01001110 01010100 01010011
*/
    
    /**
    * @notice Emitted when NFTs are unglued and collateral is withdrawn
    * @param recipient The address receiving the withdrawn collateral
    * @param realAmount The number of NFTs that were processed
    * @param beforeTotalSupply The total supply before the operation
    * @param afterTotalSupply The total supply after the operation
    * @param supplyDelta The supply delta
    */
    event unglued(address indexed recipient, uint256 realAmount, uint256 beforeTotalSupply, uint256 afterTotalSupply, uint256 supplyDelta);
    
    /**
    * @notice Emitted when a flash loan is executed
    * @param collateral The address of the borrowed asset
    * @param amount The amount that was borrowed
    * @param receiver The address that received the loan
    */
    event GlueLoan(address indexed collateral, uint256 amount, address receiver);
}

// SPDX-License-Identifier: MIT
/**
                                                    
███╗   ███╗ █████╗ ████████╗██╗  ██╗
████╗ ████║██╔══██╗╚══██╔══╝██║  ██║
██╔████╔██║███████║   ██║   ███████║
██║╚██╔╝██║██╔══██║   ██║   ██╔══██║
██║ ╚═╝ ██║██║  ██║   ██║   ██║  ██║
╚═╝     ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝
███████╗ ██████╗ ██████╗            
██╔════╝██╔═══██╗██╔══██╗           
█████╗  ██║   ██║██████╔╝           
██╔══╝  ██║   ██║██╔══██╗           
██║     ╚██████╔╝██║  ██║           
╚═╝      ╚═════╝ ╚═╝  ╚═╝           
 ██████╗ ██╗     ██╗   ██╗███████╗  
██╔════╝ ██║     ██║   ██║██╔════╝  
██║  ███╗██║     ██║   ██║█████╗    
██║   ██║██║     ██║   ██║██╔══╝    
╚██████╔╝███████╗╚██████╔╝███████╗  
 ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝  
                                                                                                                                                                                                             
 @title GluedMath
 @author Implementation by @BasedToschi
 @notice Glued Math Basics is a library for advanced fixed-point math operations (Original version by Uniswap Labs).
 @notice Glued Math Expanded introduces a new function to adjust the decimal places between different tokens with different decimals or ETH.
 @dev Implements multiplication and division with overflow protection and precision retention.
 @dev This library is used to handle the decimal places of the tokens.

*/

pragma solidity ^0.8.28;

library GluedMath {

/**
    
██████╗  █████╗ ███████╗██╗ ██████╗    ███╗   ███╗ █████╗ ████████╗██╗  ██╗
██╔══██╗██╔══██╗██╔════╝██║██╔════╝    ████╗ ████║██╔══██╗╚══██╔══╝██║  ██║
██████╔╝███████║███████╗██║██║         ██╔████╔██║███████║   ██║   ███████║
██╔══██╗██╔══██║╚════██║██║██║         ██║╚██╔╝██║██╔══██║   ██║   ██╔══██║
██████╔╝██║  ██║███████║██║╚██████╗    ██║ ╚═╝ ██║██║  ██║   ██║   ██║  ██║
╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝ ╚═════╝    ╚═╝     ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝

*/
    /**
     * @notice Performs a multiply-divide operation with full precision.
     * @dev Calculates floor(a * b / denominator) with full precision, using 512-bit intermediate values.
     * Throws if the result overflows a uint256 or if the denominator is zero.
     *
     * @param a The multiplicand.
     * @param b The multiplier.
     * @param denominator The divisor.
     * @return result The result of the operation.
     *
     * Use case: When you need to calculate the result of a multiply-divide operation with full precision.
     */
    function md512(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
        
        unchecked {

            // Calculate the product of a and b
            uint256 prod0; 
            uint256 prod1;

            // Calculate the product of a and b with overflow protection
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // If the denominator is zero or the result overflows, revert
            require(denominator > prod1, "GluedMath: denominator is zero or result overflows");

            // If the product of a and b is less than the denominator, return the result
            if (prod1 == 0) {
                assembly {
                    result := div(prod0, denominator)
                }

                // Return the result
                return result;
            }

            // Calculate the remainder of the product of a and b divided by the denominator
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Calculate the twos of the denominator
            uint256 twos = (0 - denominator) & denominator;

            // Calculate the inverse of the denominator
            assembly {
                denominator := div(denominator, twos)
                prod0 := div(prod0, twos)
            }

            // Calculate the inverse of the denominator
            uint256 inv = (3 * denominator) ^ 2;
            inv *= 2 - denominator * inv; // inverse mod 2^8
            inv *= 2 - denominator * inv; // inverse mod 2^16
            inv *= 2 - denominator * inv; // inverse mod 2^32
            inv *= 2 - denominator * inv; // inverse mod 2^64
            inv *= 2 - denominator * inv; // inverse mod 2^128
            inv *= 2 - denominator * inv; // inverse mod 2^256

            // Calculate the result
            result = prod0 * inv;

            // Return the result
            return result;
        }
    }

    /**
     * @notice Performs a multiply-divide operation with full precision and rounding up.
     * @dev Calculates ceil(a * b / denominator) with full precision, using 512-bit intermediate values.
     * Throws if the result overflows a uint256 or if the denominator is zero.
     *
     * @param a The multiplicand.
     * @param b The multiplier.
     * @param denominator The divisor.
     * @return result The result of the operation, rounded up to the nearest integer.
     *
     * Use case: When you need to calculate the result of a multiply-divide operation with full precision and rounding up.
     */
    function md512Up(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {

        unchecked {

            // Calculate the result
            result = md512(a, b, denominator);

            // If the remainder of the product of a and b divided by the denominator is greater than 0, increment the result
            if (mulmod(a, b, denominator) > 0) {
                require(result < type(uint256).max, "GluedMath: result overflows");
                result++;
            }
        }
    }

/**
    
███████╗██╗  ██╗██████╗  █████╗ ███╗   ██╗██████╗ ███████╗██████╗     ███╗   ███╗ █████╗ ████████╗██╗  ██╗
██╔════╝╚██╗██╔╝██╔══██╗██╔══██╗████╗  ██║██╔══██╗██╔════╝██╔══██╗    ████╗ ████║██╔══██╗╚══██╔══╝██║  ██║
█████╗   ╚███╔╝ ██████╔╝███████║██╔██╗ ██║██║  ██║█████╗  ██║  ██║    ██╔████╔██║███████║   ██║   ███████║
██╔══╝   ██╔██╗ ██╔═══╝ ██╔══██║██║╚██╗██║██║  ██║██╔══╝  ██║  ██║    ██║╚██╔╝██║██╔══██║   ██║   ██╔══██║
███████╗██╔╝ ██╗██║     ██║  ██║██║ ╚████║██████╔╝███████╗██████╔╝    ██║ ╚═╝ ██║██║  ██║   ██║   ██║  ██║
╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝  ╚═╝╚═╝  ╚═══╝╚═════╝ ╚══════╝╚═════╝     ╚═╝     ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝

*/

    /**
     * @notice Gets the decimals of a token
     * @dev If the token is ETH, you can use the address(0) as the token address.
     *
     * @param token The address of the token
     * @return decimals The number of decimals of the token
     *
     * Use case: When you need to get the decimals of a token
     */
    function getDecimals(address token) internal view returns (uint256 decimals) {
        
        // If the token is ETH, return 18
        if (token == address(0)) {
            return 18;
        }
        
        // Get the decimals of the token
        (bool success, bytes memory data) = token.staticcall(abi.encodeWithSignature("decimals()"));

        // If the call failed, revert
        require(success && data.length >= 32, "decimals() call failed");

        // Return the decimals of the token
        return uint256(uint8(bytes1(data)));
    }
    
    /**
     * @notice Adjusts decimal places between different token decimals. With this function,
     * you can get the right ammount of tokenOut from a given tokenIn address and amount
     * espressed in tokenIn's decimals.
     * @dev If one of the tokens is ETH, you can use the address(0) as the token address.
     *
     * @param amount The amount to adjust
     * @param tokenIn The address of the input token
     * @param tokenOut The address of the output token
     * @return adjustedAmount The adjusted amount with correct decimal places
     *
     * Use case: When you need to adjust the decimal places operating with two different tokens
     */
    function adjustDecimals(uint256 amount, address tokenIn, address tokenOut) internal view returns (uint256 adjustedAmount) {

        // Get the decimals of the input and output tokens
        uint256 decimalsIn = tokenIn == address(0) ? 18 : getDecimals(tokenIn);
        uint256 decimalsOut = tokenOut == address(0) ? 18 : getDecimals(tokenOut);
        
        // If the decimals are the same, return the amount
        if (decimalsIn == decimalsOut) return amount;
        
        // If input token is 0 decimal and output has decimals, special handling
        if (decimalsIn == 0 && decimalsOut > 0) {

            // Mltiply by 10^decimalsOut to ensure proper scaling
            return amount * (10 ** decimalsOut);
        }
        
        // If output token is 0 decimal and input has decimals, handle precision loss
        if (decimalsOut == 0 && decimalsIn > 0) {

            // Round up if there's any fractional part to avoid returning 0 for small amounts
            uint256 divisor = 10 ** decimalsIn;

            // Ceiling division
            return (amount + divisor - 1) / divisor;
        }
        
        // If the decimals of the input token are greater than the decimals of the output token, divide the amount
        return decimalsIn > decimalsOut
            ? amount / (10 ** (decimalsIn - decimalsOut))
            : amount * (10 ** (decimalsOut - decimalsIn));
    }

    /**
    * @dev You can combine md512 + adjustDecimals
    *
    * Example:
    *
    * uint256 result = adjustDecimals(md512(a, b, denominator), tokenIn, tokenOut);
    *
    */
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "evmVersion": "cancun",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_glueStickAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[],"name":"FailedToProcessCollection","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"InvalidAsset","type":"error"},{"inputs":[],"name":"InvalidGlueStickAddress","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NoAssetsSelected","type":"error"},{"inputs":[],"name":"NoAssetsTransferred","type":"error"},{"inputs":[],"name":"NoCollateralSelected","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateral","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"}],"name":"GlueLoan","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"realAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"beforeTotalSupply","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"afterTotalSupply","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"supplyDelta","type":"uint256"}],"name":"unglued","type":"event"},{"inputs":[{"internalType":"uint256","name":"stickyAmount","type":"uint256"},{"internalType":"address[]","name":"collaterals","type":"address[]"}],"name":"collateralByAmount","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateral","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"flashLoan","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAdjustedTotalSupply","outputs":[{"internalType":"uint256","name":"adjustedTotalSupply","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"collaterals","type":"address[]"}],"name":"getBalances","outputs":[{"internalType":"uint256[]","name":"balances","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFlashLoanFee","outputs":[{"internalType":"uint256","name":"flashLoanFee","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getFlashLoanFeeCalculated","outputs":[{"internalType":"uint256","name":"fee","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getGlueStick","outputs":[{"internalType":"address","name":"glueStick","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProtocolFee","outputs":[{"internalType":"uint256","name":"protocolFee","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getSelfLearning","outputs":[{"internalType":"bool","name":"noNativeBurn","type":"bool"},{"internalType":"bool","name":"stickySupplyGlued","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSettings","outputs":[{"internalType":"address","name":"settings","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getStickyAsset","outputs":[{"internalType":"address","name":"stickyAsset","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStickySupplyStored","outputs":[{"internalType":"uint256","name":"stickyAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"stickyAmount","type":"uint256"}],"name":"getSupplyDelta","outputs":[{"internalType":"uint256","name":"supplyDelta","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateral","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"getTotalHookSize","outputs":[{"internalType":"uint256","name":"hookSize","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isExpanded","outputs":[{"internalType":"enum IGlueERC721.BIO","name":"hooksStatus","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"collateral","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"loanHandler","outputs":[{"internalType":"bool","name":"loanSent","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"collaterals","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unglue","outputs":[{"internalType":"uint256","name":"supplyDelta","type":"uint256"},{"internalType":"uint256","name":"realAmount","type":"uint256"},{"internalType":"uint256","name":"beforeTotalSupply","type":"uint256"},{"internalType":"uint256","name":"afterTotalSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.