ETH Price: $4,216.44 (-2.85%)

Transaction Decoder

Block:
16362442 at Jan-08-2023 01:43:23 PM +UTC
Transaction Fee:
0.003089818946526208 ETH $13.03
Gas Used:
192,992 Gas / 16.010088224 Gwei

Emitted Events:

142 Registry.ExecutionCompleted( middlewareID=0, bridgeID=12, inputAmount=78760000 )
143 TetherToken.Transfer( from=[Sender] 0xc5cb539016d7bb5001de0dab5574f1686973d7da, to=HyphenImplV2, value=78760000 )
144 TetherToken.Approval( owner=HyphenImplV2, spender=TransparentUpgradeableProxy, value=78760000 )
145 TransparentUpgradeableProxy.0xf28044030a28cf7d3fb8e8f7bbaa42aee92214081fd522b3a38afb279577db89( 0xf28044030a28cf7d3fb8e8f7bbaa42aee92214081fd522b3a38afb279577db89, 0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7, 0x0000000000000000000000000000000000000000000000000000001d31e87112 )
146 TetherToken.Transfer( from=HyphenImplV2, to=TransparentUpgradeableProxy, value=78760000 )
147 TransparentUpgradeableProxy.0x522e11fa05593b306c8df10d2b0b8e01eec48f9d0a9427a7a93f21ff90d66fb1( 0x522e11fa05593b306c8df10d2b0b8e01eec48f9d0a9427a7a93f21ff90d66fb1, 0x0000000000000000000000001aba89fc7ff67d27ccaa51893c46fd1e5fee924b, 0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7, 0x000000000000000000000000c5cb539016d7bb5001de0dab5574f1686973d7da, 000000000000000000000000000000000000000000000000000000000000000a, 0000000000000000000000000000000000000000000000000000000004b274e8, 000000000000000000000000000000000000000000000000000000000000aca8, 0000000000000000000000000000000000000000000000000000000000000080, 0000000000000000000000000000000000000000000000000000000000000006, 534f434b45540000000000000000000000000000000000000000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
0x2A5c2568...18310180b
(Biconomy: Liquidity Pool)
(beaverbuild)
210.065863790098991549 Eth210.066153278098991549 Eth0.000289488
0xc5CB5390...86973d7dA
0.006623569058872695 Eth
Nonce: 27
0.003533750112346487 Eth
Nonce: 28
0.003089818946526208
0xdAC17F95...13D831ec7
0xebaB24F1...54e15F7b9
(Biconomy: Liquidity Providers)

Execution Trace

Registry.outboundTransferTo( _userRequest=[{name:receiverAddress, type:address, order:1, indexed:false, value:0xc5CB539016d7BB5001De0dAb5574F1686973d7dA, valueString:0xc5CB539016d7BB5001De0dAb5574F1686973d7dA}, {name:toChainId, type:uint256, order:2, indexed:false, value:10, valueString:10}, {name:amount, type:uint256, order:3, indexed:false, value:78760000, valueString:78760000}, {name:middlewareRequest, type:tuple, order:4, indexed:false, value:[{name:id, type:uint256, order:1, indexed:false, value:0, valueString:0}, {name:optionalNativeAmount, type:uint256, order:2, indexed:false, value:0, valueString:0}, {name:inputToken, type:address, order:3, indexed:false, value:0xdAC17F958D2ee523a2206206994597C13D831ec7, valueString:0xdAC17F958D2ee523a2206206994597C13D831ec7}, {name:data, type:bytes, order:4, indexed:false, value:0x0000000000000000000000000000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000000000000000000000000000}], valueString:[{name:id, type:uint256, order:1, indexed:false, value:0, valueString:0}, {name:optionalNativeAmount, type:uint256, order:2, indexed:false, value:0, valueString:0}, {name:inputToken, type:address, order:3, indexed:false, value:0xdAC17F958D2ee523a2206206994597C13D831ec7, valueString:0xdAC17F958D2ee523a2206206994597C13D831ec7}, {name:data, type:bytes, order:4, indexed:false, value:0x0000000000000000000000000000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000000000000000000000000000}]}, {name:bridgeRequest, type:tuple, order:5, indexed:false, value:[{name:id, type:uint256, order:1, indexed:false, value:12, valueString:12}, {name:optionalNativeAmount, type:uint256, order:2, indexed:false, value:0, valueString:0}, {name:inputToken, type:address, order:3, indexed:false, value:0xdAC17F958D2ee523a2206206994597C13D831ec7, valueString:0xdAC17F958D2ee523a2206206994597C13D831ec7}, {name:data, type:bytes, order:4, indexed:false, value:0x0000000000000000000000000000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000000000000000000000000000}], valueString:[{name:id, type:uint256, order:1, indexed:false, value:12, valueString:12}, {name:optionalNativeAmount, type:uint256, order:2, indexed:false, value:0, valueString:0}, {name:inputToken, type:address, order:3, indexed:false, value:0xdAC17F958D2ee523a2206206994597C13D831ec7, valueString:0xdAC17F958D2ee523a2206206994597C13D831ec7}, {name:data, type:bytes, order:4, indexed:false, value:0x0000000000000000000000000000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000000000000000000000000000}]}] )
  • HyphenImplV2.outboundTransferTo( _amount=78760000, _from=0xc5CB539016d7BB5001De0dAb5574F1686973d7dA, _receiverAddress=0xc5CB539016d7BB5001De0dAb5574F1686973d7dA, _token=0xdAC17F958D2ee523a2206206994597C13D831ec7, _toChainId=10, 0x0000000000000000000000000000000000000000000000000000000000000000 )
    • TetherToken.transferFrom( _from=0xc5CB539016d7BB5001De0dAb5574F1686973d7dA, _to=0x1Aba89fC7ff67D27ccaa51893c46FD1e5fEE924B, _value=78760000 )
    • TetherToken.allowance( _owner=0x1Aba89fC7ff67D27ccaa51893c46FD1e5fEE924B, _spender=0x2A5c2568b10A0E826BfA892Cf21BA7218310180b ) => ( remaining=0 )
    • TetherToken.approve( _spender=0x2A5c2568b10A0E826BfA892Cf21BA7218310180b, _value=78760000 )
    • TransparentUpgradeableProxy.55d73595( )
      • LiquidityPool.depositErc20( toChainId=10, tokenAddress=0xdAC17F958D2ee523a2206206994597C13D831ec7, receiver=0xc5CB539016d7BB5001De0dAb5574F1686973d7dA, amount=78760000, tag=SOCKET )
        • TransparentUpgradeableProxy.ba8dbea2( )
          • TokenManager.tokensInfo( 0xdAC17F958D2ee523a2206206994597C13D831ec7 ) => ( transferOverhead=82491, supportedToken=True, equilibriumFee=7500000, maxFee=50000000, tokenConfig=[{name:min, type:uint256, order:1, indexed:false, value:100000000, valueString:100000000}, {name:max, type:uint256, order:2, indexed:false, value:120000000000, valueString:120000000000}] )
          • TransparentUpgradeableProxy.c0e4d627( )
            • TokenManager.getDepositConfig( toChainId=10, tokenAddress=0xdAC17F958D2ee523a2206206994597C13D831ec7 ) => ( [{name:min, type:uint256, order:1, indexed:false, value:10000000, valueString:10000000}, {name:max, type:uint256, order:2, indexed:false, value:60000000000, valueString:60000000000}] )
            • TransparentUpgradeableProxy.ca2ba943( )
              • LiquidityProviders.getCurrentLiquidity( tokenAddress=0xdAC17F958D2ee523a2206206994597C13D831ec7 ) => ( 125312608466 )
              • TransparentUpgradeableProxy.d1b4f192( )
                • LiquidityProviders.totalLPFees( 0xdAC17F958D2ee523a2206206994597C13D831ec7 ) => ( 742934974 )
                • TransparentUpgradeableProxy.b3524e30( )
                • TransparentUpgradeableProxy.53403fb5( )
                  • LiquidityProviders.increaseCurrentLiquidity( tokenAddress=0xdAC17F958D2ee523a2206206994597C13D831ec7, amount=78760000 )
                  • TetherToken.transferFrom( _from=0x1Aba89fC7ff67D27ccaa51893c46FD1e5fEE924B, _to=0x2A5c2568b10A0E826BfA892Cf21BA7218310180b, _value=78760000 )
                    outboundTransferTo[Registry (ln:107)]
                    File 1 of 9: Registry
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.4;
                    import "@openzeppelin/contracts/access/Ownable.sol";
                    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
                    import "./helpers/errors.sol";
                    import "./ImplBase.sol";
                    import "./MiddlewareImplBase.sol";
                    /**
                    // @title Movr Regisrtry Contract.
                    // @notice This is the main contract that is called using fund movr.
                    // This contains all the bridge and middleware ids. 
                    // RouteIds signify which bridge to be used. 
                    // Middleware Id signifies which aggregator will be used for swapping if required. 
                    */
                    contract Registry is Ownable {
                        using SafeERC20 for IERC20;
                        address private constant NATIVE_TOKEN_ADDRESS =
                            address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
                        ///@notice RouteData stores information for a route
                        struct RouteData {
                            address route;
                            bool isEnabled;
                            bool isMiddleware;
                        }
                        RouteData[] public routes;
                        modifier onlyExistingRoute(uint256 _routeId) {
                            require(
                                routes[_routeId].route != address(0),
                                MovrErrors.ROUTE_NOT_FOUND
                            );
                            _;
                        }
                        constructor(address _owner) Ownable() {
                            // first route is for direct bridging
                            routes.push(RouteData(NATIVE_TOKEN_ADDRESS, true, true));
                            transferOwnership(_owner);
                        }
                        // Function to receive Ether. msg.data must be empty
                        receive() external payable {}
                        //
                        // Events
                        //
                        event NewRouteAdded(
                            uint256 routeID,
                            address route,
                            bool isEnabled,
                            bool isMiddleware
                        );
                        event RouteDisabled(uint256 routeID);
                        event ExecutionCompleted(
                            uint256 middlewareID,
                            uint256 bridgeID,
                            uint256 inputAmount
                        );
                        /**
                        // @param id route id of middleware to be used
                        // @param optionalNativeAmount is the amount of native asset that the route requires 
                        // @param inputToken token address which will be swapped to
                        // BridgeRequest inputToken 
                        // @param data to be used by middleware
                        */
                        struct MiddlewareRequest {
                            uint256 id;
                            uint256 optionalNativeAmount;
                            address inputToken;
                            bytes data;
                        }
                        /**
                        // @param id route id of bridge to be used
                        // @param optionalNativeAmount optinal native amount, to be used
                        // when bridge needs native token along with ERC20    
                        // @param inputToken token addresss which will be bridged 
                        // @param data bridgeData to be used by bridge
                        */
                        struct BridgeRequest {
                            uint256 id;
                            uint256 optionalNativeAmount;
                            address inputToken;
                            bytes data;
                        }
                        /**
                        // @param receiverAddress Recipient address to recieve funds on destination chain
                        // @param toChainId Destination ChainId
                        // @param amount amount to be swapped if middlewareId is 0  it will be
                        // the amount to be bridged
                        // @param middlewareRequest middleware Requestdata
                        // @param bridgeRequest bridge request data
                        */
                        struct UserRequest {
                            address receiverAddress;
                            uint256 toChainId;
                            uint256 amount;
                            MiddlewareRequest middlewareRequest;
                            BridgeRequest bridgeRequest;
                        }
                        /**
                        // @notice function responsible for calling the respective implementation 
                        // depending on the bridge to be used
                        // If the middlewareId is 0 then no swap is required,
                        // we can directly bridge the source token to wherever required,
                        // else, we first call the Swap Impl Base for swapping to the required 
                        // token and then start the bridging
                        // @dev It is required for isMiddleWare to be true for route 0 as it is a special case
                        // @param _userRequest calldata follows the input data struct
                        */
                        function outboundTransferTo(UserRequest calldata _userRequest)
                            external
                            payable
                        {
                            require(_userRequest.amount != 0, MovrErrors.INVALID_AMT);
                            // make sure bridge ID is not 0
                            require(
                                _userRequest.bridgeRequest.id != 0,
                                MovrErrors.INVALID_BRIDGE_ID
                            );
                            // make sure bridge input is provided
                            require(
                                _userRequest.bridgeRequest.inputToken != address(0),
                                MovrErrors.ADDRESS_0_PROVIDED
                            );
                            // load middleware info and validate
                            RouteData memory middlewareInfo = routes[
                                _userRequest.middlewareRequest.id
                            ];
                            require(
                                middlewareInfo.route != address(0) &&
                                    middlewareInfo.isEnabled &&
                                    middlewareInfo.isMiddleware,
                                MovrErrors.ROUTE_NOT_ALLOWED
                            );
                            // load bridge info and validate
                            RouteData memory bridgeInfo = routes[_userRequest.bridgeRequest.id];
                            require(
                                bridgeInfo.route != address(0) &&
                                    bridgeInfo.isEnabled &&
                                    !bridgeInfo.isMiddleware,
                                MovrErrors.ROUTE_NOT_ALLOWED
                            );
                            emit ExecutionCompleted(
                                _userRequest.middlewareRequest.id,
                                _userRequest.bridgeRequest.id,
                                _userRequest.amount
                            );
                            // if middlewareID is 0 it means we dont want to perform a action before bridging
                            // and directly want to move for bridging
                            if (_userRequest.middlewareRequest.id == 0) {
                                // perform the bridging
                                ImplBase(bridgeInfo.route).outboundTransferTo{value: msg.value}(
                                    _userRequest.amount,
                                    msg.sender,
                                    _userRequest.receiverAddress,
                                    _userRequest.bridgeRequest.inputToken,
                                    _userRequest.toChainId,
                                    _userRequest.bridgeRequest.data
                                );
                                return;
                            }
                            // we first perform an action using the middleware
                            // we determine if the input asset is a native asset, if yes we pass
                            // the amount as value, else we pass the optionalNativeAmount
                            uint256 _amountOut = MiddlewareImplBase(middlewareInfo.route)
                                .performAction{
                                value: _userRequest.middlewareRequest.inputToken ==
                                    NATIVE_TOKEN_ADDRESS
                                    ? _userRequest.amount +
                                        _userRequest.middlewareRequest.optionalNativeAmount
                                    : _userRequest.middlewareRequest.optionalNativeAmount
                            }(
                                msg.sender,
                                _userRequest.middlewareRequest.inputToken,
                                _userRequest.amount,
                                address(this),
                                _userRequest.middlewareRequest.data
                            );
                            // we mutate this variable if the input asset to bridge Impl is NATIVE
                            uint256 nativeInput = _userRequest.bridgeRequest.optionalNativeAmount;
                            // if the input asset is ERC20, we need to grant the bridge implementation approval
                            if (_userRequest.bridgeRequest.inputToken != NATIVE_TOKEN_ADDRESS) {
                                IERC20(_userRequest.bridgeRequest.inputToken).safeIncreaseAllowance(
                                        bridgeInfo.route,
                                        _amountOut
                                    );
                            } else {
                                // if the input asset is native we need to set it as value
                                nativeInput =
                                    _amountOut +
                                    _userRequest.bridgeRequest.optionalNativeAmount;
                            }
                            // send off to bridge
                            ImplBase(bridgeInfo.route).outboundTransferTo{value: nativeInput}(
                                _amountOut,
                                address(this),
                                _userRequest.receiverAddress,
                                _userRequest.bridgeRequest.inputToken,
                                _userRequest.toChainId,
                                _userRequest.bridgeRequest.data
                            );
                        }
                        //
                        // Route management functions
                        //
                        /// @notice add routes to the registry.
                        function addRoutes(RouteData[] calldata _routes)
                            external
                            onlyOwner
                            returns (uint256[] memory)
                        {
                            require(_routes.length != 0, MovrErrors.EMPTY_INPUT);
                            uint256[] memory _routeIds = new uint256[](_routes.length);
                            for (uint256 i = 0; i < _routes.length; i++) {
                                require(
                                    _routes[i].route != address(0),
                                    MovrErrors.ADDRESS_0_PROVIDED
                                );
                                routes.push(_routes[i]);
                                _routeIds[i] = routes.length - 1;
                                emit NewRouteAdded(
                                    i,
                                    _routes[i].route,
                                    _routes[i].isEnabled,
                                    _routes[i].isMiddleware
                                );
                            }
                            return _routeIds;
                        }
                        ///@notice disables the route  if required.
                        function disableRoute(uint256 _routeId)
                            external
                            onlyOwner
                            onlyExistingRoute(_routeId)
                        {
                            routes[_routeId].isEnabled = false;
                            emit RouteDisabled(_routeId);
                        }
                        function rescueFunds(
                            address _token,
                            address _receiverAddress,
                            uint256 _amount
                        ) external onlyOwner {
                            IERC20(_token).safeTransfer(_receiverAddress, _amount);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/Context.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract Ownable is Context {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor () {
                            address msgSender = _msgSender();
                            _owner = msgSender;
                            emit OwnershipTransferred(address(0), msgSender);
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP.
                     */
                    interface IERC20 {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                        /**
                         * @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);
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../IERC20.sol";
                    import "../../../utils/Address.sol";
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 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 {
                        using Address for address;
                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                        /**
                         * @dev Deprecated. This function has issues similar to the ones found in
                         * {IERC20-approve}, and its usage is discouraged.
                         *
                         * Whenever possible, use {safeIncreaseAllowance} and
                         * {safeDecreaseAllowance} instead.
                         */
                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            // solhint-disable-next-line max-line-length
                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender) + value;
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            unchecked {
                                uint256 oldAllowance = token.allowance(address(this), spender);
                                require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                                uint256 newAllowance = oldAllowance - value;
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        }
                        /**
                         * @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).
                         */
                        function _callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                            // the target address contains contract code and also asserts for success in the low-level call.
                            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                            if (returndata.length > 0) { // Return data is optional
                                // solhint-disable-next-line max-line-length
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity >=0.8.0;
                    library MovrErrors {
                        string internal constant ADDRESS_0_PROVIDED = "ADDRESS_0_PROVIDED";
                        string internal constant EMPTY_INPUT = "EMPTY_INPUT";
                        string internal constant LENGTH_MISMATCH = "LENGTH_MISMATCH";
                        string internal constant INVALID_VALUE = "INVALID_VALUE";
                        string internal constant INVALID_AMT = "INVALID_AMT";
                        string internal constant IMPL_NOT_FOUND = "IMPL_NOT_FOUND";
                        string internal constant ROUTE_NOT_FOUND = "ROUTE_NOT_FOUND";
                        string internal constant IMPL_NOT_ALLOWED = "IMPL_NOT_ALLOWED";
                        string internal constant ROUTE_NOT_ALLOWED = "ROUTE_NOT_ALLOWED";
                        string internal constant INVALID_CHAIN_DATA = "INVALID_CHAIN_DATA";
                        string internal constant CHAIN_NOT_SUPPORTED = "CHAIN_NOT_SUPPORTED";
                        string internal constant TOKEN_NOT_SUPPORTED = "TOKEN_NOT_SUPPORTED";
                        string internal constant NOT_IMPLEMENTED = "NOT_IMPLEMENTED";
                        string internal constant INVALID_SENDER = "INVALID_SENDER";
                        string internal constant INVALID_BRIDGE_ID = "INVALID_BRIDGE_ID";
                        string internal constant MIDDLEWARE_ACTION_FAILED =
                            "MIDDLEWARE_ACTION_FAILED";
                        string internal constant VALUE_SHOULD_BE_ZERO = "VALUE_SHOULD_BE_ZERO";
                        string internal constant VALUE_SHOULD_NOT_BE_ZERO = "VALUE_SHOULD_NOT_BE_ZERO";
                        string internal constant VALUE_NOT_ENOUGH = "VALUE_NOT_ENOUGH";
                        string internal constant VALUE_NOT_EQUAL_TO_AMOUNT = "VALUE_NOT_EQUAL_TO_AMOUNT";
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.4;
                    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import "@openzeppelin/contracts/access/Ownable.sol";
                    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
                    import "./helpers/errors.sol";
                    /**
                    @title Abstract Implementation Contract.
                    @notice All Bridge Implementation will follow this interface. 
                    */
                    abstract contract ImplBase is Ownable {
                        using SafeERC20 for IERC20;
                        address public registry;
                        address public constant NATIVE_TOKEN_ADDRESS =
                            address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
                        event UpdateRegistryAddress(address indexed registryAddress);
                        constructor(address _registry) Ownable() {
                            registry = _registry;
                        }
                        modifier onlyRegistry() {
                            require(msg.sender == registry, MovrErrors.INVALID_SENDER);
                            _;
                        }
                        function updateRegistryAddress(address newRegistry) external onlyOwner {
                            registry = newRegistry;
                            emit UpdateRegistryAddress(newRegistry);
                        }
                        function rescueFunds(
                            address token,
                            address userAddress,
                            uint256 amount
                        ) external onlyOwner {
                            IERC20(token).safeTransfer(userAddress, amount);
                        }
                        function outboundTransferTo(
                            uint256 _amount,
                            address _from,
                            address _receiverAddress,
                            address _token,
                            uint256 _toChainId,
                            bytes memory _extraData
                        ) external payable virtual;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.4;
                    import "@openzeppelin/contracts/access/Ownable.sol";
                    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
                    import "./helpers/errors.sol";
                    /**
                    // @title Abstract Contract for middleware services.
                    // @notice All middleware services will follow this interface. 
                    */
                    abstract contract MiddlewareImplBase is Ownable {
                        using SafeERC20 for IERC20;
                        address public immutable registry;
                        /// @notice only registry address is required.
                        constructor(address _registry) Ownable() {
                            registry = _registry;
                        }
                        modifier onlyRegistry {
                            require(msg.sender == registry, MovrErrors.INVALID_SENDER);
                            _;
                        }
                        function performAction(
                            address from,
                            address fromToken,
                            uint256 amount,
                            address receiverAddress,
                            bytes memory data
                        ) external payable virtual returns (uint256);
                        function rescueFunds(
                            address token,
                            address userAddress,
                            uint256 amount
                        ) external onlyOwner {
                            IERC20(token).safeTransfer(userAddress, amount);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract Context {
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies on extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                            uint256 size;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { size := extcodesize(account) }
                            return size > 0;
                        }
                        /**
                         * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                            (bool success, ) = recipient.call{ value: amount }("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                        /**
                         * @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, it is bubbled up by this
                         * function (like regular Solidity function calls).
                         *
                         * 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.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                         * `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, 0, errorMessage);
                        }
                        /**
                         * @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`.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                            require(address(this).balance >= value, "Address: insufficient balance for call");
                            require(isContract(target), "Address: call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.call{ value: value }(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                            return functionStaticCall(target, data, "Address: low-level static call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                            require(isContract(target), "Address: static call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.staticcall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            require(isContract(target), "Address: delegate call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.delegatecall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                            if (success) {
                                return returndata;
                            } else {
                                // 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
                                    // solhint-disable-next-line no-inline-assembly
                                    assembly {
                                        let returndata_size := mload(returndata)
                                        revert(add(32, returndata), returndata_size)
                                    }
                                } else {
                                    revert(errorMessage);
                                }
                            }
                        }
                    }
                    

                    File 2 of 9: HyphenImplV2
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.4;
                    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
                    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
                    import "../../helpers/errors.sol";
                    import "../../ImplBase.sol";
                    import "../../interfaces/hyphen.sol";
                    /**
                    // @title Hyphen V2 Implementation.
                    // Called by the registry if the selected bridge is Hyphen Bridge.
                    // @dev Follows the interface of ImplBase.
                    // @author Movr Network.
                    */
                    contract HyphenImplV2 is ImplBase, ReentrancyGuard {
                        using SafeERC20 for IERC20;
                        HyphenLiquidityPoolManager public immutable liquidityPoolManager;
                        string constant tag = "SOCKET";
                        /// @notice Liquidity pool manager address and registry address required.
                        constructor(
                            HyphenLiquidityPoolManager _liquidityPoolManager,
                            address _registry
                        ) ImplBase(_registry) {
                            liquidityPoolManager = _liquidityPoolManager;
                        }
                        /**
                        // @notice Function responsible for cross chain transfer of supported assets from l2
                        // to supported l2 and l1 chains. 
                        // @dev Liquidity should be checked before calling this function. 
                        // @param _amount amount to be sent.
                        // @param _from senders address.
                        // @param _receiverAddress receivers address.
                        // @param _token token address on the source chain. 
                        // @param _toChainId destination chain id
                        // param _data extra data that is required, not required in the case of Hyphen. 
                        */
                        function outboundTransferTo(
                            uint256 _amount,
                            address _from,
                            address _receiverAddress,
                            address _token,
                            uint256 _toChainId,
                            bytes memory // _data
                        ) external payable override onlyRegistry nonReentrant {
                            if (_token == NATIVE_TOKEN_ADDRESS) {
                                // check if value passed is not 0
                                require(msg.value != 0, MovrErrors.VALUE_SHOULD_NOT_BE_ZERO);
                                liquidityPoolManager.depositNative{value: _amount}(
                                    _receiverAddress,
                                    _toChainId,
                                    tag
                                );
                                return;
                            }
                            require(msg.value == 0, MovrErrors.VALUE_SHOULD_BE_ZERO);
                            IERC20(_token).safeTransferFrom(_from, address(this), _amount);
                            IERC20(_token).safeIncreaseAllowance(
                                address(liquidityPoolManager),
                                _amount
                            );
                            liquidityPoolManager.depositErc20(
                                _toChainId,
                                _token,
                                _receiverAddress,
                                _amount,
                                tag
                            );
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP.
                     */
                    interface IERC20 {
                        /**
                         * @dev Emitted when `value` tokens are moved from one account (`from`) to
                         * another (`to`).
                         *
                         * Note that `value` may be zero.
                         */
                        event Transfer(address indexed from, address indexed to, uint256 value);
                        /**
                         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                         * a call to {approve}. `value` is the new allowance.
                         */
                        event Approval(address indexed owner, address indexed spender, uint256 value);
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `to`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address to, uint256 amount) external returns (bool);
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                        /**
                         * @dev Moves `amount` tokens from `from` to `to` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(
                            address from,
                            address to,
                            uint256 amount
                        ) external returns (bool);
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
                    pragma solidity ^0.8.0;
                    import "../IERC20.sol";
                    import "../../../utils/Address.sol";
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 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 {
                        using Address for address;
                        function safeTransfer(
                            IERC20 token,
                            address to,
                            uint256 value
                        ) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                        function safeTransferFrom(
                            IERC20 token,
                            address from,
                            address to,
                            uint256 value
                        ) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                        /**
                         * @dev Deprecated. This function has issues similar to the ones found in
                         * {IERC20-approve}, and its usage is discouraged.
                         *
                         * Whenever possible, use {safeIncreaseAllowance} and
                         * {safeDecreaseAllowance} instead.
                         */
                        function safeApprove(
                            IERC20 token,
                            address spender,
                            uint256 value
                        ) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            require(
                                (value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                        function safeIncreaseAllowance(
                            IERC20 token,
                            address spender,
                            uint256 value
                        ) internal {
                            uint256 newAllowance = token.allowance(address(this), spender) + value;
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        function safeDecreaseAllowance(
                            IERC20 token,
                            address spender,
                            uint256 value
                        ) internal {
                            unchecked {
                                uint256 oldAllowance = token.allowance(address(this), spender);
                                require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                                uint256 newAllowance = oldAllowance - value;
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        }
                        /**
                         * @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).
                         */
                        function _callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                            // the target address contains contract code and also asserts for success in the low-level call.
                            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                            if (returndata.length > 0) {
                                // Return data is optional
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Contract module that helps prevent reentrant calls to a function.
                     *
                     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
                     * available, which can be applied to functions to make sure there are no nested
                     * (reentrant) calls to them.
                     *
                     * Note that because there is a single `nonReentrant` guard, functions marked as
                     * `nonReentrant` may not call one another. This can be worked around by making
                     * those functions `private`, and then adding `external` `nonReentrant` entry
                     * points to them.
                     *
                     * TIP: If you would like to learn more about reentrancy and alternative ways
                     * to protect against it, check out our blog post
                     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
                     */
                    abstract contract ReentrancyGuard {
                        // Booleans are more expensive than uint256 or any type that takes up a full
                        // word because each write operation emits an extra SLOAD to first read the
                        // slot's contents, replace the bits taken up by the boolean, and then write
                        // back. This is the compiler's defense against contract upgrades and
                        // pointer aliasing, and it cannot be disabled.
                        // The values being non-zero value makes deployment a bit more expensive,
                        // but in exchange the refund on every call to nonReentrant will be lower in
                        // amount. Since refunds are capped to a percentage of the total
                        // transaction's gas, it is best to keep them low in cases like this one, to
                        // increase the likelihood of the full refund coming into effect.
                        uint256 private constant _NOT_ENTERED = 1;
                        uint256 private constant _ENTERED = 2;
                        uint256 private _status;
                        constructor() {
                            _status = _NOT_ENTERED;
                        }
                        /**
                         * @dev Prevents a contract from calling itself, directly or indirectly.
                         * Calling a `nonReentrant` function from another `nonReentrant`
                         * function is not supported. It is possible to prevent this from happening
                         * by making the `nonReentrant` function external, and making it call a
                         * `private` function that does the actual work.
                         */
                        modifier nonReentrant() {
                            // On the first call to nonReentrant, _notEntered will be true
                            require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                            // Any calls to nonReentrant after this point will fail
                            _status = _ENTERED;
                            _;
                            // By storing the original value once again, a refund is triggered (see
                            // https://eips.ethereum.org/EIPS/eip-2200)
                            _status = _NOT_ENTERED;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity >=0.8.0;
                    library MovrErrors {
                        string internal constant ADDRESS_0_PROVIDED = "ADDRESS_0_PROVIDED";
                        string internal constant EMPTY_INPUT = "EMPTY_INPUT";
                        string internal constant LENGTH_MISMATCH = "LENGTH_MISMATCH";
                        string internal constant INVALID_VALUE = "INVALID_VALUE";
                        string internal constant INVALID_AMT = "INVALID_AMT";
                        string internal constant IMPL_NOT_FOUND = "IMPL_NOT_FOUND";
                        string internal constant ROUTE_NOT_FOUND = "ROUTE_NOT_FOUND";
                        string internal constant IMPL_NOT_ALLOWED = "IMPL_NOT_ALLOWED";
                        string internal constant ROUTE_NOT_ALLOWED = "ROUTE_NOT_ALLOWED";
                        string internal constant INVALID_CHAIN_DATA = "INVALID_CHAIN_DATA";
                        string internal constant CHAIN_NOT_SUPPORTED = "CHAIN_NOT_SUPPORTED";
                        string internal constant TOKEN_NOT_SUPPORTED = "TOKEN_NOT_SUPPORTED";
                        string internal constant NOT_IMPLEMENTED = "NOT_IMPLEMENTED";
                        string internal constant INVALID_SENDER = "INVALID_SENDER";
                        string internal constant INVALID_BRIDGE_ID = "INVALID_BRIDGE_ID";
                        string internal constant MIDDLEWARE_ACTION_FAILED =
                            "MIDDLEWARE_ACTION_FAILED";
                        string internal constant VALUE_SHOULD_BE_ZERO = "VALUE_SHOULD_BE_ZERO";
                        string internal constant VALUE_SHOULD_NOT_BE_ZERO = "VALUE_SHOULD_NOT_BE_ZERO";
                        string internal constant VALUE_NOT_ENOUGH = "VALUE_NOT_ENOUGH";
                        string internal constant VALUE_NOT_EQUAL_TO_AMOUNT = "VALUE_NOT_EQUAL_TO_AMOUNT";
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.4;
                    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import "@openzeppelin/contracts/access/Ownable.sol";
                    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
                    import "./helpers/errors.sol";
                    /**
                    @title Abstract Implementation Contract.
                    @notice All Bridge Implementation will follow this interface. 
                    */
                    abstract contract ImplBase is Ownable {
                        using SafeERC20 for IERC20;
                        address public registry;
                        address public constant NATIVE_TOKEN_ADDRESS =
                            address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
                        event UpdateRegistryAddress(address indexed registryAddress);
                        constructor(address _registry) Ownable() {
                            registry = _registry;
                        }
                        modifier onlyRegistry() {
                            require(msg.sender == registry, MovrErrors.INVALID_SENDER);
                            _;
                        }
                        function updateRegistryAddress(address newRegistry) external onlyOwner {
                            registry = newRegistry;
                            emit UpdateRegistryAddress(newRegistry);
                        }
                        function rescueFunds(
                            address token,
                            address userAddress,
                            uint256 amount
                        ) external onlyOwner {
                            IERC20(token).safeTransfer(userAddress, amount);
                        }
                        function outboundTransferTo(
                            uint256 _amount,
                            address _from,
                            address _receiverAddress,
                            address _token,
                            uint256 _toChainId,
                            bytes memory _extraData
                        ) external payable virtual;
                    }
                    // SPDX-License-Identifier: Apache-2.0
                    pragma solidity >=0.8.0;
                    interface HyphenLiquidityPoolManager {
                        function depositErc20(
                            uint256 toChainId,
                            address tokenAddress,
                            address receiver,
                            uint256 amount,
                            string calldata tag
                        ) external;
                        function depositNative(
                            address receiver,
                            uint256 toChainId,
                            string calldata tag
                        ) external payable;
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
                    pragma solidity ^0.8.1;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         *
                         * [IMPORTANT]
                         * ====
                         * You shouldn't rely on `isContract` to protect against flash loan attacks!
                         *
                         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                         * constructor.
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies on extcodesize/address.code.length, which returns 0
                            // for contracts in construction, since the code is only stored at the end
                            // of the constructor execution.
                            return account.code.length > 0;
                        }
                        /**
                         * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            (bool success, ) = recipient.call{value: amount}("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                        /**
                         * @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, it is bubbled up by this
                         * function (like regular Solidity function calls).
                         *
                         * 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.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionCall(target, data, "Address: low-level call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                         * `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(
                            address target,
                            bytes memory data,
                            string memory errorMessage
                        ) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, 0, errorMessage);
                        }
                        /**
                         * @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`.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(
                            address target,
                            bytes memory data,
                            uint256 value
                        ) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(
                            address target,
                            bytes memory data,
                            uint256 value,
                            string memory errorMessage
                        ) internal returns (bytes memory) {
                            require(address(this).balance >= value, "Address: insufficient balance for call");
                            require(isContract(target), "Address: call to non-contract");
                            (bool success, bytes memory returndata) = target.call{value: value}(data);
                            return verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                            return functionStaticCall(target, data, "Address: low-level static call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(
                            address target,
                            bytes memory data,
                            string memory errorMessage
                        ) internal view returns (bytes memory) {
                            require(isContract(target), "Address: static call to non-contract");
                            (bool success, bytes memory returndata) = target.staticcall(data);
                            return verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(
                            address target,
                            bytes memory data,
                            string memory errorMessage
                        ) internal returns (bytes memory) {
                            require(isContract(target), "Address: delegate call to non-contract");
                            (bool success, bytes memory returndata) = target.delegatecall(data);
                            return verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
                         * revert reason using the provided one.
                         *
                         * _Available since v4.3._
                         */
                        function verifyCallResult(
                            bool success,
                            bytes memory returndata,
                            string memory errorMessage
                        ) internal pure returns (bytes memory) {
                            if (success) {
                                return returndata;
                            } else {
                                // 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 {
                                        let returndata_size := mload(returndata)
                                        revert(add(32, returndata), returndata_size)
                                    }
                                } else {
                                    revert(errorMessage);
                                }
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
                    pragma solidity ^0.8.0;
                    import "../utils/Context.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract Ownable is Context {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor() {
                            _transferOwnership(_msgSender());
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            _transferOwnership(address(0));
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            _transferOwnership(newOwner);
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Internal function without access restriction.
                         */
                        function _transferOwnership(address newOwner) internal virtual {
                            address oldOwner = _owner;
                            _owner = newOwner;
                            emit OwnershipTransferred(oldOwner, newOwner);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract Context {
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            return msg.data;
                        }
                    }
                    

                    File 3 of 9: TetherToken
                    pragma solidity ^0.4.17;
                    
                    /**
                     * @title SafeMath
                     * @dev Math operations with safety checks that throw on error
                     */
                    library SafeMath {
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            if (a == 0) {
                                return 0;
                            }
                            uint256 c = a * b;
                            assert(c / a == b);
                            return c;
                        }
                    
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            // assert(b > 0); // Solidity automatically throws when dividing by 0
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                            return c;
                        }
                    
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            assert(b <= a);
                            return a - b;
                        }
                    
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            assert(c >= a);
                            return c;
                        }
                    }
                    
                    /**
                     * @title Ownable
                     * @dev The Ownable contract has an owner address, and provides basic authorization control
                     * functions, this simplifies the implementation of "user permissions".
                     */
                    contract Ownable {
                        address public owner;
                    
                        /**
                          * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                          * account.
                          */
                        function Ownable() public {
                            owner = msg.sender;
                        }
                    
                        /**
                          * @dev Throws if called by any account other than the owner.
                          */
                        modifier onlyOwner() {
                            require(msg.sender == owner);
                            _;
                        }
                    
                        /**
                        * @dev Allows the current owner to transfer control of the contract to a newOwner.
                        * @param newOwner The address to transfer ownership to.
                        */
                        function transferOwnership(address newOwner) public onlyOwner {
                            if (newOwner != address(0)) {
                                owner = newOwner;
                            }
                        }
                    
                    }
                    
                    /**
                     * @title ERC20Basic
                     * @dev Simpler version of ERC20 interface
                     * @dev see https://github.com/ethereum/EIPs/issues/20
                     */
                    contract ERC20Basic {
                        uint public _totalSupply;
                        function totalSupply() public constant returns (uint);
                        function balanceOf(address who) public constant returns (uint);
                        function transfer(address to, uint value) public;
                        event Transfer(address indexed from, address indexed to, uint value);
                    }
                    
                    /**
                     * @title ERC20 interface
                     * @dev see https://github.com/ethereum/EIPs/issues/20
                     */
                    contract ERC20 is ERC20Basic {
                        function allowance(address owner, address spender) public constant returns (uint);
                        function transferFrom(address from, address to, uint value) public;
                        function approve(address spender, uint value) public;
                        event Approval(address indexed owner, address indexed spender, uint value);
                    }
                    
                    /**
                     * @title Basic token
                     * @dev Basic version of StandardToken, with no allowances.
                     */
                    contract BasicToken is Ownable, ERC20Basic {
                        using SafeMath for uint;
                    
                        mapping(address => uint) public balances;
                    
                        // additional variables for use if transaction fees ever became necessary
                        uint public basisPointsRate = 0;
                        uint public maximumFee = 0;
                    
                        /**
                        * @dev Fix for the ERC20 short address attack.
                        */
                        modifier onlyPayloadSize(uint size) {
                            require(!(msg.data.length < size + 4));
                            _;
                        }
                    
                        /**
                        * @dev transfer token for a specified address
                        * @param _to The address to transfer to.
                        * @param _value The amount to be transferred.
                        */
                        function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) {
                            uint fee = (_value.mul(basisPointsRate)).div(10000);
                            if (fee > maximumFee) {
                                fee = maximumFee;
                            }
                            uint sendAmount = _value.sub(fee);
                            balances[msg.sender] = balances[msg.sender].sub(_value);
                            balances[_to] = balances[_to].add(sendAmount);
                            if (fee > 0) {
                                balances[owner] = balances[owner].add(fee);
                                Transfer(msg.sender, owner, fee);
                            }
                            Transfer(msg.sender, _to, sendAmount);
                        }
                    
                        /**
                        * @dev Gets the balance of the specified address.
                        * @param _owner The address to query the the balance of.
                        * @return An uint representing the amount owned by the passed address.
                        */
                        function balanceOf(address _owner) public constant returns (uint balance) {
                            return balances[_owner];
                        }
                    
                    }
                    
                    /**
                     * @title Standard ERC20 token
                     *
                     * @dev Implementation of the basic standard token.
                     * @dev https://github.com/ethereum/EIPs/issues/20
                     * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
                     */
                    contract StandardToken is BasicToken, ERC20 {
                    
                        mapping (address => mapping (address => uint)) public allowed;
                    
                        uint public constant MAX_UINT = 2**256 - 1;
                    
                        /**
                        * @dev Transfer tokens from one address to another
                        * @param _from address The address which you want to send tokens from
                        * @param _to address The address which you want to transfer to
                        * @param _value uint the amount of tokens to be transferred
                        */
                        function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
                            var _allowance = allowed[_from][msg.sender];
                    
                            // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
                            // if (_value > _allowance) throw;
                    
                            uint fee = (_value.mul(basisPointsRate)).div(10000);
                            if (fee > maximumFee) {
                                fee = maximumFee;
                            }
                            if (_allowance < MAX_UINT) {
                                allowed[_from][msg.sender] = _allowance.sub(_value);
                            }
                            uint sendAmount = _value.sub(fee);
                            balances[_from] = balances[_from].sub(_value);
                            balances[_to] = balances[_to].add(sendAmount);
                            if (fee > 0) {
                                balances[owner] = balances[owner].add(fee);
                                Transfer(_from, owner, fee);
                            }
                            Transfer(_from, _to, sendAmount);
                        }
                    
                        /**
                        * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
                        * @param _spender The address which will spend the funds.
                        * @param _value The amount of tokens to be spent.
                        */
                        function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                    
                            // To change the approve amount you first have to reduce the addresses`
                            //  allowance to zero by calling `approve(_spender, 0)` if it is not
                            //  already 0 to mitigate the race condition described here:
                            //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                            require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
                    
                            allowed[msg.sender][_spender] = _value;
                            Approval(msg.sender, _spender, _value);
                        }
                    
                        /**
                        * @dev Function to check the amount of tokens than an owner allowed to a spender.
                        * @param _owner address The address which owns the funds.
                        * @param _spender address The address which will spend the funds.
                        * @return A uint specifying the amount of tokens still available for the spender.
                        */
                        function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                            return allowed[_owner][_spender];
                        }
                    
                    }
                    
                    
                    /**
                     * @title Pausable
                     * @dev Base contract which allows children to implement an emergency stop mechanism.
                     */
                    contract Pausable is Ownable {
                      event Pause();
                      event Unpause();
                    
                      bool public paused = false;
                    
                    
                      /**
                       * @dev Modifier to make a function callable only when the contract is not paused.
                       */
                      modifier whenNotPaused() {
                        require(!paused);
                        _;
                      }
                    
                      /**
                       * @dev Modifier to make a function callable only when the contract is paused.
                       */
                      modifier whenPaused() {
                        require(paused);
                        _;
                      }
                    
                      /**
                       * @dev called by the owner to pause, triggers stopped state
                       */
                      function pause() onlyOwner whenNotPaused public {
                        paused = true;
                        Pause();
                      }
                    
                      /**
                       * @dev called by the owner to unpause, returns to normal state
                       */
                      function unpause() onlyOwner whenPaused public {
                        paused = false;
                        Unpause();
                      }
                    }
                    
                    contract BlackList is Ownable, BasicToken {
                    
                        /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
                        function getBlackListStatus(address _maker) external constant returns (bool) {
                            return isBlackListed[_maker];
                        }
                    
                        function getOwner() external constant returns (address) {
                            return owner;
                        }
                    
                        mapping (address => bool) public isBlackListed;
                        
                        function addBlackList (address _evilUser) public onlyOwner {
                            isBlackListed[_evilUser] = true;
                            AddedBlackList(_evilUser);
                        }
                    
                        function removeBlackList (address _clearedUser) public onlyOwner {
                            isBlackListed[_clearedUser] = false;
                            RemovedBlackList(_clearedUser);
                        }
                    
                        function destroyBlackFunds (address _blackListedUser) public onlyOwner {
                            require(isBlackListed[_blackListedUser]);
                            uint dirtyFunds = balanceOf(_blackListedUser);
                            balances[_blackListedUser] = 0;
                            _totalSupply -= dirtyFunds;
                            DestroyedBlackFunds(_blackListedUser, dirtyFunds);
                        }
                    
                        event DestroyedBlackFunds(address _blackListedUser, uint _balance);
                    
                        event AddedBlackList(address _user);
                    
                        event RemovedBlackList(address _user);
                    
                    }
                    
                    contract UpgradedStandardToken is StandardToken{
                        // those methods are called by the legacy contract
                        // and they must ensure msg.sender to be the contract address
                        function transferByLegacy(address from, address to, uint value) public;
                        function transferFromByLegacy(address sender, address from, address spender, uint value) public;
                        function approveByLegacy(address from, address spender, uint value) public;
                    }
                    
                    contract TetherToken is Pausable, StandardToken, BlackList {
                    
                        string public name;
                        string public symbol;
                        uint public decimals;
                        address public upgradedAddress;
                        bool public deprecated;
                    
                        //  The contract can be initialized with a number of tokens
                        //  All the tokens are deposited to the owner address
                        //
                        // @param _balance Initial supply of the contract
                        // @param _name Token Name
                        // @param _symbol Token symbol
                        // @param _decimals Token decimals
                        function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
                            _totalSupply = _initialSupply;
                            name = _name;
                            symbol = _symbol;
                            decimals = _decimals;
                            balances[owner] = _initialSupply;
                            deprecated = false;
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function transfer(address _to, uint _value) public whenNotPaused {
                            require(!isBlackListed[msg.sender]);
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
                            } else {
                                return super.transfer(_to, _value);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
                            require(!isBlackListed[_from]);
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
                            } else {
                                return super.transferFrom(_from, _to, _value);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function balanceOf(address who) public constant returns (uint) {
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).balanceOf(who);
                            } else {
                                return super.balanceOf(who);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
                            } else {
                                return super.approve(_spender, _value);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                            if (deprecated) {
                                return StandardToken(upgradedAddress).allowance(_owner, _spender);
                            } else {
                                return super.allowance(_owner, _spender);
                            }
                        }
                    
                        // deprecate current contract in favour of a new one
                        function deprecate(address _upgradedAddress) public onlyOwner {
                            deprecated = true;
                            upgradedAddress = _upgradedAddress;
                            Deprecate(_upgradedAddress);
                        }
                    
                        // deprecate current contract if favour of a new one
                        function totalSupply() public constant returns (uint) {
                            if (deprecated) {
                                return StandardToken(upgradedAddress).totalSupply();
                            } else {
                                return _totalSupply;
                            }
                        }
                    
                        // Issue a new amount of tokens
                        // these tokens are deposited into the owner address
                        //
                        // @param _amount Number of tokens to be issued
                        function issue(uint amount) public onlyOwner {
                            require(_totalSupply + amount > _totalSupply);
                            require(balances[owner] + amount > balances[owner]);
                    
                            balances[owner] += amount;
                            _totalSupply += amount;
                            Issue(amount);
                        }
                    
                        // Redeem tokens.
                        // These tokens are withdrawn from the owner address
                        // if the balance must be enough to cover the redeem
                        // or the call will fail.
                        // @param _amount Number of tokens to be issued
                        function redeem(uint amount) public onlyOwner {
                            require(_totalSupply >= amount);
                            require(balances[owner] >= amount);
                    
                            _totalSupply -= amount;
                            balances[owner] -= amount;
                            Redeem(amount);
                        }
                    
                        function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
                            // Ensure transparency by hardcoding limit beyond which fees can never be added
                            require(newBasisPoints < 20);
                            require(newMaxFee < 50);
                    
                            basisPointsRate = newBasisPoints;
                            maximumFee = newMaxFee.mul(10**decimals);
                    
                            Params(basisPointsRate, maximumFee);
                        }
                    
                        // Called when new token are issued
                        event Issue(uint amount);
                    
                        // Called when tokens are redeemed
                        event Redeem(uint amount);
                    
                        // Called when contract is deprecated
                        event Deprecate(address newAddress);
                    
                        // Called if contract ever adds fees
                        event Params(uint feeBasisPoints, uint maxFee);
                    }

                    File 4 of 9: TransparentUpgradeableProxy
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
                    import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
                    import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
                    // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
                    contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
                        constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../Proxy.sol";
                    import "./ERC1967Upgrade.sol";
                    /**
                     * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
                     * implementation address that can be changed. This address is stored in storage in the location specified by
                     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
                     * implementation behind the proxy.
                     */
                    contract ERC1967Proxy is Proxy, ERC1967Upgrade {
                        /**
                         * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
                         *
                         * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
                         * function call, and allows initializating the storage of the proxy like a Solidity constructor.
                         */
                        constructor(address _logic, bytes memory _data) payable {
                            assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                            _upgradeToAndCall(_logic, _data, false);
                        }
                        /**
                         * @dev Returns the current implementation address.
                         */
                        function _implementation() internal view virtual override returns (address impl) {
                            return ERC1967Upgrade._getImplementation();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../ERC1967/ERC1967Proxy.sol";
                    /**
                     * @dev This contract implements a proxy that is upgradeable by an admin.
                     *
                     * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
                     * clashing], which can potentially be used in an attack, this contract uses the
                     * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
                     * things that go hand in hand:
                     *
                     * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
                     * that call matches one of the admin functions exposed by the proxy itself.
                     * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
                     * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
                     * "admin cannot fallback to proxy target".
                     *
                     * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
                     * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
                     * to sudden errors when trying to call a function from the proxy implementation.
                     *
                     * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
                     * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
                     */
                    contract TransparentUpgradeableProxy is ERC1967Proxy {
                        /**
                         * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
                         * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
                         */
                        constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                            assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                            _changeAdmin(admin_);
                        }
                        /**
                         * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
                         */
                        modifier ifAdmin() {
                            if (msg.sender == _getAdmin()) {
                                _;
                            } else {
                                _fallback();
                            }
                        }
                        /**
                         * @dev Returns the current admin.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
                         *
                         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                         * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                         */
                        function admin() external ifAdmin returns (address admin_) {
                            admin_ = _getAdmin();
                        }
                        /**
                         * @dev Returns the current implementation.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
                         *
                         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                         * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                         */
                        function implementation() external ifAdmin returns (address implementation_) {
                            implementation_ = _implementation();
                        }
                        /**
                         * @dev Changes the admin of the proxy.
                         *
                         * Emits an {AdminChanged} event.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
                         */
                        function changeAdmin(address newAdmin) external virtual ifAdmin {
                            _changeAdmin(newAdmin);
                        }
                        /**
                         * @dev Upgrade the implementation of the proxy.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
                         */
                        function upgradeTo(address newImplementation) external ifAdmin {
                            _upgradeToAndCall(newImplementation, bytes(""), false);
                        }
                        /**
                         * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
                         * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
                         * proxied contract.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
                         */
                        function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                            _upgradeToAndCall(newImplementation, data, true);
                        }
                        /**
                         * @dev Returns the current admin.
                         */
                        function _admin() internal view virtual returns (address) {
                            return _getAdmin();
                        }
                        /**
                         * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
                         */
                        function _beforeFallback() internal virtual override {
                            require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                            super._beforeFallback();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "./TransparentUpgradeableProxy.sol";
                    import "../../access/Ownable.sol";
                    /**
                     * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
                     * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
                     */
                    contract ProxyAdmin is Ownable {
                        /**
                         * @dev Returns the current implementation of `proxy`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                            // We need to manually run the static call since the getter cannot be flagged as view
                            // bytes4(keccak256("implementation()")) == 0x5c60da1b
                            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
                            require(success);
                            return abi.decode(returndata, (address));
                        }
                        /**
                         * @dev Returns the current admin of `proxy`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                            // We need to manually run the static call since the getter cannot be flagged as view
                            // bytes4(keccak256("admin()")) == 0xf851a440
                            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
                            require(success);
                            return abi.decode(returndata, (address));
                        }
                        /**
                         * @dev Changes the admin of `proxy` to `newAdmin`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the current admin of `proxy`.
                         */
                        function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
                            proxy.changeAdmin(newAdmin);
                        }
                        /**
                         * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
                            proxy.upgradeTo(implementation);
                        }
                        /**
                         * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
                         * {TransparentUpgradeableProxy-upgradeToAndCall}.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
                            proxy.upgradeToAndCall{value: msg.value}(implementation, data);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
                     * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
                     * be specified by overriding the virtual {_implementation} function.
                     *
                     * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
                     * different contract through the {_delegate} function.
                     *
                     * The success and return data of the delegated call will be returned back to the caller of the proxy.
                     */
                    abstract contract Proxy {
                        /**
                         * @dev Delegates the current call to `implementation`.
                         *
                         * This function does not return to its internall call site, it will return directly to the external caller.
                         */
                        function _delegate(address implementation) internal virtual {
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                // Copy msg.data. We take full control of memory in this inline assembly
                                // block because it will not return to Solidity code. We overwrite the
                                // Solidity scratch pad at memory position 0.
                                calldatacopy(0, 0, calldatasize())
                                // Call the implementation.
                                // out and outsize are 0 because we don't know the size yet.
                                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                                // Copy the returned data.
                                returndatacopy(0, 0, returndatasize())
                                switch result
                                // delegatecall returns 0 on error.
                                case 0 { revert(0, returndatasize()) }
                                default { return(0, returndatasize()) }
                            }
                        }
                        /**
                         * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
                         * and {_fallback} should delegate.
                         */
                        function _implementation() internal view virtual returns (address);
                        /**
                         * @dev Delegates the current call to the address returned by `_implementation()`.
                         *
                         * This function does not return to its internall call site, it will return directly to the external caller.
                         */
                        function _fallback() internal virtual {
                            _beforeFallback();
                            _delegate(_implementation());
                        }
                        /**
                         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
                         * function in the contract matches the call data.
                         */
                        fallback () external payable virtual {
                            _fallback();
                        }
                        /**
                         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
                         * is empty.
                         */
                        receive () external payable virtual {
                            _fallback();
                        }
                        /**
                         * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
                         * call, or as part of the Solidity `fallback` or `receive` functions.
                         *
                         * If overriden should call `super._beforeFallback()`.
                         */
                        function _beforeFallback() internal virtual {
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.2;
                    import "../beacon/IBeacon.sol";
                    import "../../utils/Address.sol";
                    import "../../utils/StorageSlot.sol";
                    /**
                     * @dev This abstract contract provides getters and event emitting update functions for
                     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
                     *
                     * _Available since v4.1._
                     *
                     * @custom:oz-upgrades-unsafe-allow delegatecall
                     */
                    abstract contract ERC1967Upgrade {
                        // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
                        bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
                        /**
                         * @dev Storage slot with the address of the current implementation.
                         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                         * validated in the constructor.
                         */
                        bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                        /**
                         * @dev Emitted when the implementation is upgraded.
                         */
                        event Upgraded(address indexed implementation);
                        /**
                         * @dev Returns the current implementation address.
                         */
                        function _getImplementation() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                        }
                        /**
                         * @dev Stores a new address in the EIP1967 implementation slot.
                         */
                        function _setImplementation(address newImplementation) private {
                            require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                            StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                        }
                        /**
                         * @dev Perform implementation upgrade
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeTo(address newImplementation) internal {
                            _setImplementation(newImplementation);
                            emit Upgraded(newImplementation);
                        }
                        /**
                         * @dev Perform implementation upgrade with additional setup call.
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
                            _setImplementation(newImplementation);
                            emit Upgraded(newImplementation);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(newImplementation, data);
                            }
                        }
                        /**
                         * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
                            address oldImplementation = _getImplementation();
                            // Initial upgrade and setup call
                            _setImplementation(newImplementation);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(newImplementation, data);
                            }
                            // Perform rollback test if not already in progress
                            StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
                            if (!rollbackTesting.value) {
                                // Trigger rollback using upgradeTo from the new implementation
                                rollbackTesting.value = true;
                                Address.functionDelegateCall(
                                    newImplementation,
                                    abi.encodeWithSignature(
                                        "upgradeTo(address)",
                                        oldImplementation
                                    )
                                );
                                rollbackTesting.value = false;
                                // Check rollback was effective
                                require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                                // Finally reset to the new implementation and log the upgrade
                                _setImplementation(newImplementation);
                                emit Upgraded(newImplementation);
                            }
                        }
                        /**
                         * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
                         * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
                         *
                         * Emits a {BeaconUpgraded} event.
                         */
                        function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
                            _setBeacon(newBeacon);
                            emit BeaconUpgraded(newBeacon);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                            }
                        }
                        /**
                         * @dev Storage slot with the admin of the contract.
                         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                         * validated in the constructor.
                         */
                        bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                        /**
                         * @dev Emitted when the admin account has changed.
                         */
                        event AdminChanged(address previousAdmin, address newAdmin);
                        /**
                         * @dev Returns the current admin.
                         */
                        function _getAdmin() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
                        }
                        /**
                         * @dev Stores a new address in the EIP1967 admin slot.
                         */
                        function _setAdmin(address newAdmin) private {
                            require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                            StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
                        }
                        /**
                         * @dev Changes the admin of the proxy.
                         *
                         * Emits an {AdminChanged} event.
                         */
                        function _changeAdmin(address newAdmin) internal {
                            emit AdminChanged(_getAdmin(), newAdmin);
                            _setAdmin(newAdmin);
                        }
                        /**
                         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                         * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
                         */
                        bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                        /**
                         * @dev Emitted when the beacon is upgraded.
                         */
                        event BeaconUpgraded(address indexed beacon);
                        /**
                         * @dev Returns the current beacon.
                         */
                        function _getBeacon() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
                        }
                        /**
                         * @dev Stores a new beacon in the EIP1967 beacon slot.
                         */
                        function _setBeacon(address newBeacon) private {
                            require(
                                Address.isContract(newBeacon),
                                "ERC1967: new beacon is not a contract"
                            );
                            require(
                                Address.isContract(IBeacon(newBeacon).implementation()),
                                "ERC1967: beacon implementation is not a contract"
                            );
                            StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev This is the interface that {BeaconProxy} expects of its beacon.
                     */
                    interface IBeacon {
                        /**
                         * @dev Must return an address that can be used as a delegate call target.
                         *
                         * {BeaconProxy} will check that this address is a contract.
                         */
                        function implementation() external view returns (address);
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies on extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                            uint256 size;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { size := extcodesize(account) }
                            return size > 0;
                        }
                        /**
                         * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                            (bool success, ) = recipient.call{ value: amount }("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                        /**
                         * @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, it is bubbled up by this
                         * function (like regular Solidity function calls).
                         *
                         * 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.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                         * `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, 0, errorMessage);
                        }
                        /**
                         * @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`.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                            require(address(this).balance >= value, "Address: insufficient balance for call");
                            require(isContract(target), "Address: call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.call{ value: value }(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                            return functionStaticCall(target, data, "Address: low-level static call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                            require(isContract(target), "Address: static call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.staticcall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            require(isContract(target), "Address: delegate call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.delegatecall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                            if (success) {
                                return returndata;
                            } else {
                                // 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
                                    // solhint-disable-next-line no-inline-assembly
                                    assembly {
                                        let returndata_size := mload(returndata)
                                        revert(add(32, returndata), returndata_size)
                                    }
                                } else {
                                    revert(errorMessage);
                                }
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Library for reading and writing primitive types to specific storage slots.
                     *
                     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
                     * This library helps with reading and writing to such slots without the need for inline assembly.
                     *
                     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
                     *
                     * Example usage to set ERC1967 implementation slot:
                     * ```
                     * contract ERC1967 {
                     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                     *
                     *     function _getImplementation() internal view returns (address) {
                     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                     *     }
                     *
                     *     function _setImplementation(address newImplementation) internal {
                     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                     *     }
                     * }
                     * ```
                     *
                     * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
                     */
                    library StorageSlot {
                        struct AddressSlot {
                            address value;
                        }
                        struct BooleanSlot {
                            bool value;
                        }
                        struct Bytes32Slot {
                            bytes32 value;
                        }
                        struct Uint256Slot {
                            uint256 value;
                        }
                        /**
                         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                         */
                        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                         */
                        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                         */
                        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                         */
                        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/Context.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract Ownable is Context {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor () {
                            address msgSender = _msgSender();
                            _owner = msgSender;
                            emit OwnershipTransferred(address(0), msgSender);
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract Context {
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../ERC1967/ERC1967Upgrade.sol";
                    /**
                     * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
                     * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
                     * continuation of the upgradability.
                     *
                     * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
                     *
                     * _Available since v4.1._
                     */
                    abstract contract UUPSUpgradeable is ERC1967Upgrade {
                        function upgradeTo(address newImplementation) external virtual {
                            _authorizeUpgrade(newImplementation);
                            _upgradeToAndCallSecure(newImplementation, bytes(""), false);
                        }
                        function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
                            _authorizeUpgrade(newImplementation);
                            _upgradeToAndCallSecure(newImplementation, data, true);
                        }
                        function _authorizeUpgrade(address newImplementation) internal virtual;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.2;
                    import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
                    abstract contract Proxiable is UUPSUpgradeable {
                        function _authorizeUpgrade(address newImplementation) internal override {
                            _beforeUpgrade(newImplementation);
                        }
                        function _beforeUpgrade(address newImplementation) internal virtual;
                    }
                    contract ChildOfProxiable is Proxiable {
                        function _beforeUpgrade(address newImplementation) internal virtual override {}
                    }
                    

                    File 5 of 9: TransparentUpgradeableProxy
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
                    import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
                    import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
                    // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
                    contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
                        constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../Proxy.sol";
                    import "./ERC1967Upgrade.sol";
                    /**
                     * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
                     * implementation address that can be changed. This address is stored in storage in the location specified by
                     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
                     * implementation behind the proxy.
                     */
                    contract ERC1967Proxy is Proxy, ERC1967Upgrade {
                        /**
                         * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
                         *
                         * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
                         * function call, and allows initializating the storage of the proxy like a Solidity constructor.
                         */
                        constructor(address _logic, bytes memory _data) payable {
                            assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                            _upgradeToAndCall(_logic, _data, false);
                        }
                        /**
                         * @dev Returns the current implementation address.
                         */
                        function _implementation() internal view virtual override returns (address impl) {
                            return ERC1967Upgrade._getImplementation();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../ERC1967/ERC1967Proxy.sol";
                    /**
                     * @dev This contract implements a proxy that is upgradeable by an admin.
                     *
                     * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
                     * clashing], which can potentially be used in an attack, this contract uses the
                     * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
                     * things that go hand in hand:
                     *
                     * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
                     * that call matches one of the admin functions exposed by the proxy itself.
                     * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
                     * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
                     * "admin cannot fallback to proxy target".
                     *
                     * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
                     * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
                     * to sudden errors when trying to call a function from the proxy implementation.
                     *
                     * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
                     * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
                     */
                    contract TransparentUpgradeableProxy is ERC1967Proxy {
                        /**
                         * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
                         * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
                         */
                        constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                            assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                            _changeAdmin(admin_);
                        }
                        /**
                         * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
                         */
                        modifier ifAdmin() {
                            if (msg.sender == _getAdmin()) {
                                _;
                            } else {
                                _fallback();
                            }
                        }
                        /**
                         * @dev Returns the current admin.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
                         *
                         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                         * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                         */
                        function admin() external ifAdmin returns (address admin_) {
                            admin_ = _getAdmin();
                        }
                        /**
                         * @dev Returns the current implementation.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
                         *
                         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                         * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                         */
                        function implementation() external ifAdmin returns (address implementation_) {
                            implementation_ = _implementation();
                        }
                        /**
                         * @dev Changes the admin of the proxy.
                         *
                         * Emits an {AdminChanged} event.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
                         */
                        function changeAdmin(address newAdmin) external virtual ifAdmin {
                            _changeAdmin(newAdmin);
                        }
                        /**
                         * @dev Upgrade the implementation of the proxy.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
                         */
                        function upgradeTo(address newImplementation) external ifAdmin {
                            _upgradeToAndCall(newImplementation, bytes(""), false);
                        }
                        /**
                         * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
                         * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
                         * proxied contract.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
                         */
                        function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                            _upgradeToAndCall(newImplementation, data, true);
                        }
                        /**
                         * @dev Returns the current admin.
                         */
                        function _admin() internal view virtual returns (address) {
                            return _getAdmin();
                        }
                        /**
                         * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
                         */
                        function _beforeFallback() internal virtual override {
                            require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                            super._beforeFallback();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "./TransparentUpgradeableProxy.sol";
                    import "../../access/Ownable.sol";
                    /**
                     * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
                     * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
                     */
                    contract ProxyAdmin is Ownable {
                        /**
                         * @dev Returns the current implementation of `proxy`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                            // We need to manually run the static call since the getter cannot be flagged as view
                            // bytes4(keccak256("implementation()")) == 0x5c60da1b
                            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
                            require(success);
                            return abi.decode(returndata, (address));
                        }
                        /**
                         * @dev Returns the current admin of `proxy`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                            // We need to manually run the static call since the getter cannot be flagged as view
                            // bytes4(keccak256("admin()")) == 0xf851a440
                            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
                            require(success);
                            return abi.decode(returndata, (address));
                        }
                        /**
                         * @dev Changes the admin of `proxy` to `newAdmin`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the current admin of `proxy`.
                         */
                        function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
                            proxy.changeAdmin(newAdmin);
                        }
                        /**
                         * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
                            proxy.upgradeTo(implementation);
                        }
                        /**
                         * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
                         * {TransparentUpgradeableProxy-upgradeToAndCall}.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
                            proxy.upgradeToAndCall{value: msg.value}(implementation, data);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
                     * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
                     * be specified by overriding the virtual {_implementation} function.
                     *
                     * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
                     * different contract through the {_delegate} function.
                     *
                     * The success and return data of the delegated call will be returned back to the caller of the proxy.
                     */
                    abstract contract Proxy {
                        /**
                         * @dev Delegates the current call to `implementation`.
                         *
                         * This function does not return to its internall call site, it will return directly to the external caller.
                         */
                        function _delegate(address implementation) internal virtual {
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                // Copy msg.data. We take full control of memory in this inline assembly
                                // block because it will not return to Solidity code. We overwrite the
                                // Solidity scratch pad at memory position 0.
                                calldatacopy(0, 0, calldatasize())
                                // Call the implementation.
                                // out and outsize are 0 because we don't know the size yet.
                                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                                // Copy the returned data.
                                returndatacopy(0, 0, returndatasize())
                                switch result
                                // delegatecall returns 0 on error.
                                case 0 { revert(0, returndatasize()) }
                                default { return(0, returndatasize()) }
                            }
                        }
                        /**
                         * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
                         * and {_fallback} should delegate.
                         */
                        function _implementation() internal view virtual returns (address);
                        /**
                         * @dev Delegates the current call to the address returned by `_implementation()`.
                         *
                         * This function does not return to its internall call site, it will return directly to the external caller.
                         */
                        function _fallback() internal virtual {
                            _beforeFallback();
                            _delegate(_implementation());
                        }
                        /**
                         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
                         * function in the contract matches the call data.
                         */
                        fallback () external payable virtual {
                            _fallback();
                        }
                        /**
                         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
                         * is empty.
                         */
                        receive () external payable virtual {
                            _fallback();
                        }
                        /**
                         * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
                         * call, or as part of the Solidity `fallback` or `receive` functions.
                         *
                         * If overriden should call `super._beforeFallback()`.
                         */
                        function _beforeFallback() internal virtual {
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.2;
                    import "../beacon/IBeacon.sol";
                    import "../../utils/Address.sol";
                    import "../../utils/StorageSlot.sol";
                    /**
                     * @dev This abstract contract provides getters and event emitting update functions for
                     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
                     *
                     * _Available since v4.1._
                     *
                     * @custom:oz-upgrades-unsafe-allow delegatecall
                     */
                    abstract contract ERC1967Upgrade {
                        // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
                        bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
                        /**
                         * @dev Storage slot with the address of the current implementation.
                         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                         * validated in the constructor.
                         */
                        bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                        /**
                         * @dev Emitted when the implementation is upgraded.
                         */
                        event Upgraded(address indexed implementation);
                        /**
                         * @dev Returns the current implementation address.
                         */
                        function _getImplementation() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                        }
                        /**
                         * @dev Stores a new address in the EIP1967 implementation slot.
                         */
                        function _setImplementation(address newImplementation) private {
                            require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                            StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                        }
                        /**
                         * @dev Perform implementation upgrade
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeTo(address newImplementation) internal {
                            _setImplementation(newImplementation);
                            emit Upgraded(newImplementation);
                        }
                        /**
                         * @dev Perform implementation upgrade with additional setup call.
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
                            _setImplementation(newImplementation);
                            emit Upgraded(newImplementation);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(newImplementation, data);
                            }
                        }
                        /**
                         * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
                            address oldImplementation = _getImplementation();
                            // Initial upgrade and setup call
                            _setImplementation(newImplementation);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(newImplementation, data);
                            }
                            // Perform rollback test if not already in progress
                            StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
                            if (!rollbackTesting.value) {
                                // Trigger rollback using upgradeTo from the new implementation
                                rollbackTesting.value = true;
                                Address.functionDelegateCall(
                                    newImplementation,
                                    abi.encodeWithSignature(
                                        "upgradeTo(address)",
                                        oldImplementation
                                    )
                                );
                                rollbackTesting.value = false;
                                // Check rollback was effective
                                require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                                // Finally reset to the new implementation and log the upgrade
                                _setImplementation(newImplementation);
                                emit Upgraded(newImplementation);
                            }
                        }
                        /**
                         * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
                         * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
                         *
                         * Emits a {BeaconUpgraded} event.
                         */
                        function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
                            _setBeacon(newBeacon);
                            emit BeaconUpgraded(newBeacon);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                            }
                        }
                        /**
                         * @dev Storage slot with the admin of the contract.
                         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                         * validated in the constructor.
                         */
                        bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                        /**
                         * @dev Emitted when the admin account has changed.
                         */
                        event AdminChanged(address previousAdmin, address newAdmin);
                        /**
                         * @dev Returns the current admin.
                         */
                        function _getAdmin() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
                        }
                        /**
                         * @dev Stores a new address in the EIP1967 admin slot.
                         */
                        function _setAdmin(address newAdmin) private {
                            require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                            StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
                        }
                        /**
                         * @dev Changes the admin of the proxy.
                         *
                         * Emits an {AdminChanged} event.
                         */
                        function _changeAdmin(address newAdmin) internal {
                            emit AdminChanged(_getAdmin(), newAdmin);
                            _setAdmin(newAdmin);
                        }
                        /**
                         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                         * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
                         */
                        bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                        /**
                         * @dev Emitted when the beacon is upgraded.
                         */
                        event BeaconUpgraded(address indexed beacon);
                        /**
                         * @dev Returns the current beacon.
                         */
                        function _getBeacon() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
                        }
                        /**
                         * @dev Stores a new beacon in the EIP1967 beacon slot.
                         */
                        function _setBeacon(address newBeacon) private {
                            require(
                                Address.isContract(newBeacon),
                                "ERC1967: new beacon is not a contract"
                            );
                            require(
                                Address.isContract(IBeacon(newBeacon).implementation()),
                                "ERC1967: beacon implementation is not a contract"
                            );
                            StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev This is the interface that {BeaconProxy} expects of its beacon.
                     */
                    interface IBeacon {
                        /**
                         * @dev Must return an address that can be used as a delegate call target.
                         *
                         * {BeaconProxy} will check that this address is a contract.
                         */
                        function implementation() external view returns (address);
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies on extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                            uint256 size;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { size := extcodesize(account) }
                            return size > 0;
                        }
                        /**
                         * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                            (bool success, ) = recipient.call{ value: amount }("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                        /**
                         * @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, it is bubbled up by this
                         * function (like regular Solidity function calls).
                         *
                         * 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.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                         * `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, 0, errorMessage);
                        }
                        /**
                         * @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`.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                            require(address(this).balance >= value, "Address: insufficient balance for call");
                            require(isContract(target), "Address: call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.call{ value: value }(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                            return functionStaticCall(target, data, "Address: low-level static call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                            require(isContract(target), "Address: static call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.staticcall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            require(isContract(target), "Address: delegate call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.delegatecall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                            if (success) {
                                return returndata;
                            } else {
                                // 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
                                    // solhint-disable-next-line no-inline-assembly
                                    assembly {
                                        let returndata_size := mload(returndata)
                                        revert(add(32, returndata), returndata_size)
                                    }
                                } else {
                                    revert(errorMessage);
                                }
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Library for reading and writing primitive types to specific storage slots.
                     *
                     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
                     * This library helps with reading and writing to such slots without the need for inline assembly.
                     *
                     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
                     *
                     * Example usage to set ERC1967 implementation slot:
                     * ```
                     * contract ERC1967 {
                     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                     *
                     *     function _getImplementation() internal view returns (address) {
                     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                     *     }
                     *
                     *     function _setImplementation(address newImplementation) internal {
                     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                     *     }
                     * }
                     * ```
                     *
                     * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
                     */
                    library StorageSlot {
                        struct AddressSlot {
                            address value;
                        }
                        struct BooleanSlot {
                            bool value;
                        }
                        struct Bytes32Slot {
                            bytes32 value;
                        }
                        struct Uint256Slot {
                            uint256 value;
                        }
                        /**
                         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                         */
                        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                         */
                        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                         */
                        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                         */
                        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/Context.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract Ownable is Context {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor () {
                            address msgSender = _msgSender();
                            _owner = msgSender;
                            emit OwnershipTransferred(address(0), msgSender);
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract Context {
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../ERC1967/ERC1967Upgrade.sol";
                    /**
                     * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
                     * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
                     * continuation of the upgradability.
                     *
                     * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
                     *
                     * _Available since v4.1._
                     */
                    abstract contract UUPSUpgradeable is ERC1967Upgrade {
                        function upgradeTo(address newImplementation) external virtual {
                            _authorizeUpgrade(newImplementation);
                            _upgradeToAndCallSecure(newImplementation, bytes(""), false);
                        }
                        function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
                            _authorizeUpgrade(newImplementation);
                            _upgradeToAndCallSecure(newImplementation, data, true);
                        }
                        function _authorizeUpgrade(address newImplementation) internal virtual;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.2;
                    import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
                    abstract contract Proxiable is UUPSUpgradeable {
                        function _authorizeUpgrade(address newImplementation) internal override {
                            _beforeUpgrade(newImplementation);
                        }
                        function _beforeUpgrade(address newImplementation) internal virtual;
                    }
                    contract ChildOfProxiable is Proxiable {
                        function _beforeUpgrade(address newImplementation) internal virtual override {}
                    }
                    

                    File 6 of 9: LiquidityPool
                    // $$\\   $$\\                     $$\\                                 $$$$$$$\\                      $$\\
                    // $$ |  $$ |                    $$ |                                $$  __$$\\                     $$ |
                    // $$ |  $$ |$$\\   $$\\  $$$$$$\\  $$$$$$$\\   $$$$$$\\  $$$$$$$\\        $$ |  $$ | $$$$$$\\   $$$$$$\\  $$ |
                    // $$$$$$$$ |$$ |  $$ |$$  __$$\\ $$  __$$\\ $$  __$$\\ $$  __$$\\       $$$$$$$  |$$  __$$\\ $$  __$$\\ $$ |
                    // $$  __$$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |$$$$$$$$ |$$ |  $$ |      $$  ____/ $$ /  $$ |$$ /  $$ |$$ |
                    // $$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$   ____|$$ |  $$ |      $$ |      $$ |  $$ |$$ |  $$ |$$ |
                    // $$ |  $$ |\\$$$$$$$ |$$$$$$$  |$$ |  $$ |\\$$$$$$$\\ $$ |  $$ |      $$ |      \\$$$$$$  |\\$$$$$$  |$$ |
                    // \\__|  \\__| \\____$$ |$$  ____/ \\__|  \\__| \\_______|\\__|  \\__|      \\__|       \\______/  \\______/ \\__|
                    //           $$\\   $$ |$$ |
                    //           \\$$$$$$  |$$ |
                    //            \\______/ \\__|
                    //
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    pragma abicoder v2;
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
                    import "./metatx/ERC2771ContextUpgradeable.sol";
                    import "../security/Pausable.sol";
                    import "./structures/TokenConfig.sol";
                    import "./interfaces/IExecutorManager.sol";
                    import "./interfaces/ILiquidityProviders.sol";
                    import "../interfaces/IERC20Permit.sol";
                    import "./interfaces/ITokenManager.sol";
                    import "./interfaces/ISwapAdaptor.sol";
                    contract LiquidityPool is
                        Initializable,
                        ReentrancyGuardUpgradeable,
                        Pausable,
                        OwnableUpgradeable,
                        ERC2771ContextUpgradeable
                    {
                        address private constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                        uint256 private constant BASE_DIVISOR = 10000000000; // Basis Points * 100 for better accuracy
                        uint256 private constant TOKEN_PRICE_BASE_DIVISOR = 10**28;
                        uint256 public baseGas;
                        IExecutorManager private executorManager;
                        ITokenManager public tokenManager;
                        ILiquidityProviders public liquidityProviders;
                        struct PermitRequest {
                            uint256 nonce;
                            uint256 expiry;
                            bool allowed;
                            uint8 v;
                            bytes32 r;
                            bytes32 s;
                        }
                        mapping(bytes32 => bool) public processedHash;
                        mapping(address => uint256) public gasFeeAccumulatedByToken;
                        // Gas fee accumulated by token address => executor address
                        mapping(address => mapping(address => uint256)) public gasFeeAccumulated;
                        // Incentive Pool amount per token address
                        mapping(address => uint256) public incentivePool;
                        mapping(string => address) public swapAdaptorMap;
                        event AssetSent(
                            address indexed asset,
                            uint256 indexed amount,
                            uint256 indexed transferredAmount,
                            address target,
                            bytes depositHash,
                            uint256 fromChainId,
                            uint256 lpFee,
                            uint256 transferFee,
                            uint256 gasFee
                        );
                        event Received(address indexed from, uint256 indexed amount);
                        event Deposit(
                            address indexed from,
                            address indexed tokenAddress,
                            address indexed receiver,
                            uint256 toChainId,
                            uint256 amount,
                            uint256 reward,
                            string tag
                        );
                        event GasFeeWithdraw(address indexed tokenAddress, address indexed owner, uint256 indexed amount);
                        event LiquidityProvidersChanged(address indexed liquidityProvidersAddress);
                        event TokenManagerChanged(address indexed tokenManagerAddress);
                        event BaseGasUpdated(uint256 indexed baseGas);
                        event EthReceived(address, uint256);
                        event DepositAndSwap(
                            address indexed from,
                            address indexed tokenAddress,
                            address indexed receiver,
                            uint256 toChainId,
                            uint256 amount,
                            uint256 reward,
                            string tag,
                            SwapRequest[] swapRequests
                        );
                        event SwapAdaptorChanged(string indexed name, address indexed liquidityProvidersAddress);
                        event GasFeeCalculated(
                            uint256 indexed gasUsed,
                            uint256 indexed gasPrice,
                            uint256 indexed nativeTokenPriceInTransferredToken,
                            uint256 tokenGasBaseFee,
                            uint256 gasFeeInTransferredToken
                        );
                        // MODIFIERS
                        modifier onlyExecutor() {
                            require(executorManager.getExecutorStatus(_msgSender()), "Only executor is allowed");
                            _;
                        }
                        modifier onlyLiquidityProviders() {
                            require(_msgSender() == address(liquidityProviders), "Only liquidityProviders is allowed");
                            _;
                        }
                        modifier tokenChecks(address tokenAddress) {
                            (, bool supportedToken, , , ) = tokenManager.tokensInfo(tokenAddress);
                            require(supportedToken, "Token not supported");
                            _;
                        }
                        function initialize(
                            address _executorManagerAddress,
                            address _pauser,
                            address _trustedForwarder,
                            address _tokenManager,
                            address _liquidityProviders
                        ) public initializer {
                            require(_executorManagerAddress != address(0), "ExecutorManager cannot be 0x0");
                            require(_trustedForwarder != address(0), "TrustedForwarder cannot be 0x0");
                            require(_liquidityProviders != address(0), "LiquidityProviders cannot be 0x0");
                            __ERC2771Context_init(_trustedForwarder);
                            __ReentrancyGuard_init();
                            __Ownable_init();
                            __Pausable_init(_pauser);
                            executorManager = IExecutorManager(_executorManagerAddress);
                            tokenManager = ITokenManager(_tokenManager);
                            liquidityProviders = ILiquidityProviders(_liquidityProviders);
                            baseGas = 21000;
                        }
                        function setSwapAdaptor(string calldata name, address _swapAdaptor) external onlyOwner {
                            swapAdaptorMap[name] = _swapAdaptor;
                            emit SwapAdaptorChanged(name, _swapAdaptor);
                        }
                        function setTrustedForwarder(address trustedForwarder) external onlyOwner {
                            _setTrustedForwarder(trustedForwarder);
                        }
                        function setLiquidityProviders(address _liquidityProviders) external onlyOwner {
                            require(_liquidityProviders != address(0), "LiquidityProviders can't be 0");
                            liquidityProviders = ILiquidityProviders(_liquidityProviders);
                            emit LiquidityProvidersChanged(_liquidityProviders);
                        }
                        function setTokenManager(address _tokenManager) external onlyOwner {
                            require(_tokenManager != address(0), "TokenManager can't be 0");
                            tokenManager = ITokenManager(_tokenManager);
                            emit TokenManagerChanged(_tokenManager);
                        }
                        function setBaseGas(uint128 gas) external onlyOwner {
                            baseGas = gas;
                            emit BaseGasUpdated(baseGas);
                        }
                        function getExecutorManager() external view returns (address) {
                            return address(executorManager);
                        }
                        function setExecutorManager(address _executorManagerAddress) external onlyOwner {
                            require(_executorManagerAddress != address(0), "Executor Manager cannot be 0");
                            executorManager = IExecutorManager(_executorManagerAddress);
                        }
                        function getCurrentLiquidity(address tokenAddress) public view returns (uint256 currentLiquidity) {
                            uint256 liquidityPoolBalance = liquidityProviders.getCurrentLiquidity(tokenAddress);
                            currentLiquidity =
                                liquidityPoolBalance -
                                liquidityProviders.totalLPFees(tokenAddress) -
                                gasFeeAccumulatedByToken[tokenAddress] -
                                incentivePool[tokenAddress];
                        }
                        /**
                         * @dev Function used to deposit tokens into pool to initiate a cross chain token transfer.
                         * @param toChainId Chain id where funds needs to be transfered
                         * @param tokenAddress ERC20 Token address that needs to be transfered
                         * @param receiver Address on toChainId where tokens needs to be transfered
                         * @param amount Amount of token being transfered
                         */
                        function depositErc20(
                            uint256 toChainId,
                            address tokenAddress,
                            address receiver,
                            uint256 amount,
                            string calldata tag
                        ) public tokenChecks(tokenAddress) whenNotPaused nonReentrant {
                            address sender = _msgSender();
                            uint256 rewardAmount = _depositErc20(sender, toChainId, tokenAddress, receiver, amount);
                            // Emit (amount + reward amount) in event
                            emit Deposit(sender, tokenAddress, receiver, toChainId, amount + rewardAmount, rewardAmount, tag);
                        }
                        /**
                         * @dev Function used to deposit tokens into pool to initiate a cross chain token swap And transfer .
                         * @param toChainId Chain id where funds needs to be transfered
                         * @param tokenAddress ERC20 Token address that needs to be transfered
                         * @param receiver Address on toChainId where tokens needs to be transfered
                         * @param amount Amount of token being transfered
                         * @param tag Dapp unique identifier
                         * @param swapRequest information related to token swap on exit chain
                         */
                        function depositAndSwapErc20(
                            address tokenAddress,
                            address receiver,
                            uint256 toChainId,
                            uint256 amount,
                            string calldata tag,
                            SwapRequest[] calldata swapRequest
                        ) external tokenChecks(tokenAddress) whenNotPaused nonReentrant {
                            uint256 totalPercentage = 0;
                            {
                                uint256 swapArrayLength = swapRequest.length;
                                unchecked {
                                    for (uint256 index = 0; index < swapArrayLength; ++index) {
                                        totalPercentage += swapRequest[index].percentage;
                                    }
                                }
                            }
                            require(totalPercentage <= 100 * BASE_DIVISOR, "Total percentage cannot be > 100");
                            address sender = _msgSender();
                            uint256 rewardAmount = _depositErc20(sender, toChainId, tokenAddress, receiver, amount);
                            // Emit (amount + reward amount) in event
                            emit DepositAndSwap(
                                sender,
                                tokenAddress,
                                receiver,
                                toChainId,
                                amount + rewardAmount,
                                rewardAmount,
                                tag,
                                swapRequest
                            );
                        }
                        function _depositErc20(
                            address sender,
                            uint256 toChainId,
                            address tokenAddress,
                            address receiver,
                            uint256 amount
                        ) internal returns (uint256) {
                            require(toChainId != block.chainid, "To chain must be different than current chain");
                            require(tokenAddress != NATIVE, "wrong function");
                            TokenConfig memory config = tokenManager.getDepositConfig(toChainId, tokenAddress);
                            require(config.min <= amount && config.max >= amount, "Deposit amount not in Cap limit");
                            require(receiver != address(0), "Receiver address cannot be 0");
                            require(amount != 0, "Amount cannot be 0");
                            uint256 rewardAmount = getRewardAmount(amount, tokenAddress);
                            if (rewardAmount != 0) {
                                incentivePool[tokenAddress] = incentivePool[tokenAddress] - rewardAmount;
                            }
                            liquidityProviders.increaseCurrentLiquidity(tokenAddress, amount);
                            SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(tokenAddress), sender, address(this), amount);
                            return rewardAmount;
                        }
                        function getRewardAmount(uint256 amount, address tokenAddress) public view returns (uint256 rewardAmount) {
                            uint256 currentLiquidity = getCurrentLiquidity(tokenAddress);
                            uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress);
                            if (currentLiquidity < providedLiquidity) {
                                uint256 liquidityDifference = providedLiquidity - currentLiquidity;
                                if (amount >= liquidityDifference) {
                                    rewardAmount = incentivePool[tokenAddress];
                                } else {
                                    // Multiply by 10000000000 to avoid 0 reward amount for small amount and liquidity difference
                                    rewardAmount = (amount * incentivePool[tokenAddress] * 10000000000) / liquidityDifference;
                                    rewardAmount = rewardAmount / 10000000000;
                                }
                            }
                        }
                        /**
                         * DAI permit and Deposit.
                         */
                        function permitAndDepositErc20(
                            address tokenAddress,
                            address receiver,
                            uint256 amount,
                            uint256 toChainId,
                            PermitRequest calldata permitOptions,
                            string calldata tag
                        ) external {
                            IERC20Permit(tokenAddress).permit(
                                _msgSender(),
                                address(this),
                                permitOptions.nonce,
                                permitOptions.expiry,
                                permitOptions.allowed,
                                permitOptions.v,
                                permitOptions.r,
                                permitOptions.s
                            );
                            depositErc20(toChainId, tokenAddress, receiver, amount, tag);
                        }
                        /**
                         * EIP2612 and Deposit.
                         */
                        function permitEIP2612AndDepositErc20(
                            address tokenAddress,
                            address receiver,
                            uint256 amount,
                            uint256 toChainId,
                            PermitRequest calldata permitOptions,
                            string calldata tag
                        ) external {
                            IERC20Permit(tokenAddress).permit(
                                _msgSender(),
                                address(this),
                                amount,
                                permitOptions.expiry,
                                permitOptions.v,
                                permitOptions.r,
                                permitOptions.s
                            );
                            depositErc20(toChainId, tokenAddress, receiver, amount, tag);
                        }
                        /**
                         * @dev Function used to deposit native token into pool to initiate a cross chain token transfer.
                         * @param receiver Address on toChainId where tokens needs to be transfered
                         * @param toChainId Chain id where funds needs to be transfered
                         */
                        function depositNative(
                            address receiver,
                            uint256 toChainId,
                            string calldata tag
                        ) external payable whenNotPaused nonReentrant {
                            uint256 rewardAmount = _depositNative(receiver, toChainId);
                            emit Deposit(_msgSender(), NATIVE, receiver, toChainId, msg.value + rewardAmount, rewardAmount, tag);
                        }
                        function depositNativeAndSwap(
                            address receiver,
                            uint256 toChainId,
                            string calldata tag,
                            SwapRequest[] calldata swapRequest
                        ) external payable whenNotPaused nonReentrant {
                            uint256 totalPercentage = 0;
                            {
                                uint256 swapArrayLength = swapRequest.length;
                                unchecked {
                                    for (uint256 index = 0; index < swapArrayLength; ++index) {
                                        totalPercentage += swapRequest[index].percentage;
                                    }
                                }
                            }
                            require(totalPercentage <= 100 * BASE_DIVISOR, "Total percentage cannot be > 100");
                            uint256 rewardAmount = _depositNative(receiver, toChainId); // TODO: check if need to pass msg.value
                            emit DepositAndSwap(
                                _msgSender(),
                                NATIVE,
                                receiver,
                                toChainId,
                                msg.value + rewardAmount,
                                rewardAmount,
                                tag,
                                swapRequest
                            );
                        }
                        function _depositNative(address receiver, uint256 toChainId) internal returns (uint256) {
                            require(toChainId != block.chainid, "To chain must be different than current chain");
                            require(
                                tokenManager.getDepositConfig(toChainId, NATIVE).min <= msg.value &&
                                    tokenManager.getDepositConfig(toChainId, NATIVE).max >= msg.value,
                                "Deposit amount not in Cap limit"
                            );
                            require(receiver != address(0), "Receiver address cannot be 0");
                            require(msg.value != 0, "Amount cannot be 0");
                            uint256 rewardAmount = getRewardAmount(msg.value, NATIVE);
                            if (rewardAmount != 0) {
                                incentivePool[NATIVE] = incentivePool[NATIVE] - rewardAmount;
                            }
                            liquidityProviders.increaseCurrentLiquidity(NATIVE, msg.value);
                            return rewardAmount;
                        }
                        function sendFundsToUser(
                            address tokenAddress,
                            uint256 amount,
                            address payable receiver,
                            bytes calldata depositHash,
                            uint256 tokenGasPrice,
                            uint256 fromChainId
                        ) external nonReentrant onlyExecutor whenNotPaused {
                            uint256 initialGas = gasleft();
                            TokenConfig memory config = tokenManager.getTransferConfig(tokenAddress);
                            require(config.min <= amount && config.max >= amount, "Withdraw amount not in Cap limit");
                            require(receiver != address(0), "Bad receiver address");
                            (bytes32 hashSendTransaction, bool status) = checkHashStatus(tokenAddress, amount, receiver, depositHash);
                            require(!status, "Already Processed");
                            processedHash[hashSendTransaction] = true;
                            // uint256 amountToTransfer, uint256 lpFee, uint256 transferFeeAmount, uint256 gasFee
                            uint256[4] memory transferDetails = getAmountToTransfer(initialGas, tokenAddress, amount, tokenGasPrice);
                            liquidityProviders.decreaseCurrentLiquidity(tokenAddress, transferDetails[0]);
                            if (tokenAddress == NATIVE) {
                                (bool success, ) = receiver.call{value: transferDetails[0]}("");
                                require(success, "Native Transfer Failed");
                            } else {
                                SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), receiver, transferDetails[0]);
                            }
                            emit AssetSent(
                                tokenAddress,
                                amount,
                                transferDetails[0],
                                receiver,
                                depositHash,
                                fromChainId,
                                transferDetails[1],
                                transferDetails[2],
                                transferDetails[3]
                            );
                        }
                        /**
                         * @dev Internal function to calculate amount of token that needs to be transfered afetr deducting all required fees.
                         * Fee to be deducted includes gas fee, lp fee and incentive pool amount if needed.
                         * @param initialGas Gas provided initially before any calculations began
                         * @param tokenAddress Token address for which calculation needs to be done
                         * @param amount Amount of token to be transfered before deducting the fee
                         * @param tokenGasPrice Gas price in the token being transfered to be used to calculate gas fee
                         * @return [ amountToTransfer, lpFee, transferFeeAmount, gasFee ]
                         */
                        function getAmountToTransfer(
                            uint256 initialGas,
                            address tokenAddress,
                            uint256 amount,
                            uint256 tokenGasPrice
                        ) internal returns (uint256[4] memory) {
                            TokenInfo memory tokenInfo = tokenManager.getTokensInfo(tokenAddress);
                            uint256 transferFeePerc = _getTransferFee(tokenAddress, amount, tokenInfo);
                            uint256 lpFee;
                            if (transferFeePerc > tokenInfo.equilibriumFee) {
                                // Here add some fee to incentive pool also
                                lpFee = (amount * tokenInfo.equilibriumFee) / BASE_DIVISOR;
                                unchecked {
                                    incentivePool[tokenAddress] += (amount * (transferFeePerc - tokenInfo.equilibriumFee)) / BASE_DIVISOR;
                                }
                            } else {
                                lpFee = (amount * transferFeePerc) / BASE_DIVISOR;
                            }
                            uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR;
                            liquidityProviders.addLPFee(tokenAddress, lpFee);
                            uint256 totalGasUsed = initialGas + tokenInfo.transferOverhead + baseGas - gasleft();
                            uint256 gasFee = totalGasUsed * tokenGasPrice;
                            gasFeeAccumulatedByToken[tokenAddress] += gasFee;
                            gasFeeAccumulated[tokenAddress][_msgSender()] += gasFee;
                            uint256 amountToTransfer = amount - (transferFeeAmount + gasFee);
                            return [amountToTransfer, lpFee, transferFeeAmount, gasFee];
                        }
                        function sendFundsToUserV2(
                            address tokenAddress,
                            uint256 amount,
                            address payable receiver,
                            bytes calldata depositHash,
                            uint256 nativeTokenPriceInTransferredToken,
                            uint256 fromChainId,
                            uint256 tokenGasBaseFee
                        ) external nonReentrant onlyExecutor whenNotPaused {
                            uint256[4] memory transferDetails = _calculateAmountAndDecreaseAvailableLiquidity(
                                tokenAddress,
                                amount,
                                receiver,
                                depositHash,
                                nativeTokenPriceInTransferredToken,
                                tokenGasBaseFee
                            );
                            if (tokenAddress == NATIVE) {
                                (bool success, ) = receiver.call{value: transferDetails[0]}("");
                                require(success, "Native Transfer Failed");
                            } else {
                                SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), receiver, transferDetails[0]);
                            }
                            emit AssetSent(
                                tokenAddress,
                                amount,
                                transferDetails[0],
                                receiver,
                                depositHash,
                                fromChainId,
                                transferDetails[1],
                                transferDetails[2],
                                transferDetails[3]
                            );
                        }
                        function swapAndSendFundsToUser(
                            address tokenAddress,
                            uint256 amount,
                            address payable receiver,
                            bytes calldata depositHash,
                            uint256 nativeTokenPriceInTransferredToken,
                            uint256 tokenGasBaseFee,
                            uint256 fromChainId,
                            uint256 swapGasOverhead,
                            SwapRequest[] calldata swapRequests,
                            string memory swapAdaptor
                        ) external nonReentrant onlyExecutor whenNotPaused {
                            require(swapRequests.length > 0, "Wrong method call");
                            require(swapAdaptorMap[swapAdaptor] != address(0), "Swap adaptor not found");
                            uint256[4] memory transferDetails = _calculateAmountAndDecreaseAvailableLiquidity(
                                tokenAddress,
                                amount,
                                receiver,
                                depositHash,
                                nativeTokenPriceInTransferredToken,
                                tokenGasBaseFee
                            );
                            if (tokenAddress == NATIVE) {
                                (bool success, ) = swapAdaptorMap[swapAdaptor].call{value: transferDetails[0]}("");
                                require(success, "Native Transfer to Adaptor Failed");
                                ISwapAdaptor(swapAdaptorMap[swapAdaptor]).swapNative(transferDetails[0], receiver, swapRequests);
                            } else {
                                {
                                    uint256 gasBeforeApproval = gasleft();
                                    SafeERC20Upgradeable.safeApprove(
                                        IERC20Upgradeable(tokenAddress),
                                        address(swapAdaptorMap[swapAdaptor]),
                                        0
                                    );
                                    SafeERC20Upgradeable.safeApprove(
                                        IERC20Upgradeable(tokenAddress),
                                        address(swapAdaptorMap[swapAdaptor]),
                                        transferDetails[0]
                                    );
                                    swapGasOverhead += (gasBeforeApproval - gasleft());
                                }
                                {
                                    uint256 swapGasFee = calculateGasFee(
                                        tokenAddress,
                                        nativeTokenPriceInTransferredToken,
                                        swapGasOverhead,
                                        0,
                                        _msgSender()
                                    );
                                    transferDetails[0] -= swapGasFee; // Deduct swap gas fee from amount to be sent
                                    transferDetails[3] += swapGasFee; // Add swap gas fee to gas fee
                                }
                                ISwapAdaptor(swapAdaptorMap[swapAdaptor]).swap(tokenAddress, transferDetails[0], receiver, swapRequests);
                            }
                            emit AssetSent(
                                tokenAddress,
                                amount,
                                transferDetails[0],
                                receiver,
                                depositHash,
                                fromChainId,
                                transferDetails[1],
                                transferDetails[2],
                                transferDetails[3]
                            );
                        }
                        function _calculateAmountAndDecreaseAvailableLiquidity(
                            address tokenAddress,
                            uint256 amount,
                            address payable receiver,
                            bytes calldata depositHash,
                            uint256 nativeTokenPriceInTransferredToken,
                            uint256 tokenGasBaseFee
                        ) internal returns (uint256[4] memory) {
                            uint256 initialGas = gasleft();
                            TokenConfig memory config = tokenManager.getTransferConfig(tokenAddress);
                            require(config.min <= amount && config.max >= amount, "Withdraw amount not in Cap limit");
                            require(receiver != address(0), "Bad receiver address");
                            (bytes32 hashSendTransaction, bool status) = checkHashStatus(tokenAddress, amount, receiver, depositHash);
                            require(!status, "Already Processed");
                            processedHash[hashSendTransaction] = true;
                            // uint256 amountToTransfer, uint256 lpFee, uint256 transferFeeAmount, uint256 gasFee
                            uint256[4] memory transferDetails = getAmountToTransferV2(
                                initialGas,
                                tokenAddress,
                                amount,
                                nativeTokenPriceInTransferredToken,
                                tokenGasBaseFee
                            );
                            liquidityProviders.decreaseCurrentLiquidity(tokenAddress, transferDetails[0]);
                            return transferDetails;
                        }
                        /**
                         * @dev Internal function to calculate amount of token that needs to be transfered afetr deducting all required fees.
                         * Fee to be deducted includes gas fee, lp fee and incentive pool amount if needed.
                         * @param initialGas Gas provided initially before any calculations began
                         * @param tokenAddress Token address for which calculation needs to be done
                         * @param amount Amount of token to be transfered before deducting the fee
                         * @param nativeTokenPriceInTransferredToken Price of native token in terms of the token being transferred (multiplied base div), used to calculate gas fee
                         * @return [ amountToTransfer, lpFee, transferFeeAmount, gasFee ]
                         */
                        function getAmountToTransferV2(
                            uint256 initialGas,
                            address tokenAddress,
                            uint256 amount,
                            uint256 nativeTokenPriceInTransferredToken,
                            uint256 tokenGasBaseFee
                        ) internal returns (uint256[4] memory) {
                            TokenInfo memory tokenInfo = tokenManager.getTokensInfo(tokenAddress);
                            uint256 transferFeePerc = _getTransferFee(tokenAddress, amount, tokenInfo);
                            uint256 lpFee;
                            if (transferFeePerc > tokenInfo.equilibriumFee) {
                                // Here add some fee to incentive pool also
                                lpFee = (amount * tokenInfo.equilibriumFee) / BASE_DIVISOR;
                                unchecked {
                                    incentivePool[tokenAddress] += (amount * (transferFeePerc - tokenInfo.equilibriumFee)) / BASE_DIVISOR;
                                }
                            } else {
                                lpFee = (amount * transferFeePerc) / BASE_DIVISOR;
                            }
                            uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR;
                            liquidityProviders.addLPFee(tokenAddress, lpFee);
                            uint256 totalGasUsed = initialGas + tokenInfo.transferOverhead + baseGas - gasleft();
                            uint256 gasFee = calculateGasFee(
                                tokenAddress,
                                nativeTokenPriceInTransferredToken,
                                totalGasUsed,
                                tokenGasBaseFee,
                                _msgSender()
                            );
                            require(transferFeeAmount + gasFee <= amount, "Insufficient funds to cover transfer fee");
                            unchecked {
                                uint256 amountToTransfer = amount - (transferFeeAmount + gasFee);
                                return [amountToTransfer, lpFee, transferFeeAmount, gasFee];
                            }
                        }
                        function calculateGasFee(
                            address tokenAddress,
                            uint256 nativeTokenPriceInTransferredToken,
                            uint256 gasUsed,
                            uint256 tokenGasBaseFee,
                            address sender
                        ) internal returns (uint256) {
                            uint256 gasFee = (gasUsed * nativeTokenPriceInTransferredToken * tx.gasprice) /
                                TOKEN_PRICE_BASE_DIVISOR +
                                tokenGasBaseFee;
                            emit GasFeeCalculated(gasUsed, tx.gasprice, nativeTokenPriceInTransferredToken, tokenGasBaseFee, gasFee);
                            gasFeeAccumulatedByToken[tokenAddress] += gasFee;
                            gasFeeAccumulated[tokenAddress][sender] += gasFee;
                            return gasFee;
                        }
                        function _getTransferFee(
                            address tokenAddress,
                            uint256 amount,
                            TokenInfo memory tokenInfo
                        ) private view returns (uint256) {
                            uint256 currentLiquidity = getCurrentLiquidity(tokenAddress);
                            uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress);
                            uint256 resultingLiquidity = currentLiquidity - amount;
                            // We return a constant value in excess state
                            if (resultingLiquidity > providedLiquidity) {
                                return tokenManager.excessStateTransferFeePerc(tokenAddress);
                            }
                            // Fee is represented in basis points * 10 for better accuracy
                            uint256 numerator = providedLiquidity * providedLiquidity * tokenInfo.equilibriumFee * tokenInfo.maxFee; // F(max) * F(e) * L(e) ^ 2
                            uint256 denominator = tokenInfo.equilibriumFee *
                                providedLiquidity *
                                providedLiquidity +
                                (tokenInfo.maxFee - tokenInfo.equilibriumFee) *
                                resultingLiquidity *
                                resultingLiquidity; // F(e) * L(e) ^ 2 + (F(max) - F(e)) * L(r) ^ 2
                            uint256 fee;
                            if (denominator == 0) {
                                fee = 0;
                            } else {
                                fee = numerator / denominator;
                            }
                            return fee;
                        }
                        function getTransferFee(address tokenAddress, uint256 amount) external view returns (uint256) {
                            return _getTransferFee(tokenAddress, amount, tokenManager.getTokensInfo(tokenAddress));
                        }
                        function checkHashStatus(
                            address tokenAddress,
                            uint256 amount,
                            address payable receiver,
                            bytes calldata depositHash
                        ) public view returns (bytes32 hashSendTransaction, bool status) {
                            hashSendTransaction = keccak256(abi.encode(tokenAddress, amount, receiver, keccak256(depositHash)));
                            status = processedHash[hashSendTransaction];
                        }
                        function withdrawErc20GasFee(address tokenAddress) external onlyExecutor whenNotPaused nonReentrant {
                            require(tokenAddress != NATIVE, "Can't withdraw native token fee");
                            // uint256 gasFeeAccumulated = gasFeeAccumulatedByToken[tokenAddress];
                            uint256 _gasFeeAccumulated = gasFeeAccumulated[tokenAddress][_msgSender()];
                            require(_gasFeeAccumulated != 0, "Gas Fee earned is 0");
                            gasFeeAccumulatedByToken[tokenAddress] = gasFeeAccumulatedByToken[tokenAddress] - _gasFeeAccumulated;
                            gasFeeAccumulated[tokenAddress][_msgSender()] = 0;
                            SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), _msgSender(), _gasFeeAccumulated);
                            emit GasFeeWithdraw(tokenAddress, _msgSender(), _gasFeeAccumulated);
                        }
                        function withdrawNativeGasFee() external onlyExecutor whenNotPaused nonReentrant {
                            uint256 _gasFeeAccumulated = gasFeeAccumulated[NATIVE][_msgSender()];
                            require(_gasFeeAccumulated != 0, "Gas Fee earned is 0");
                            gasFeeAccumulatedByToken[NATIVE] = gasFeeAccumulatedByToken[NATIVE] - _gasFeeAccumulated;
                            gasFeeAccumulated[NATIVE][_msgSender()] = 0;
                            (bool success, ) = payable(_msgSender()).call{value: _gasFeeAccumulated}("");
                            require(success, "Native Transfer Failed");
                            emit GasFeeWithdraw(address(this), _msgSender(), _gasFeeAccumulated);
                        }
                        function transfer(
                            address _tokenAddress,
                            address receiver,
                            uint256 _tokenAmount
                        ) external whenNotPaused onlyLiquidityProviders nonReentrant {
                            require(receiver != address(0), "Invalid receiver");
                            if (_tokenAddress == NATIVE) {
                                require(address(this).balance >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE");
                                (bool success, ) = receiver.call{value: _tokenAmount}("");
                                require(success, "ERR__NATIVE_TRANSFER_FAILED");
                            } else {
                                IERC20Upgradeable baseToken = IERC20Upgradeable(_tokenAddress);
                                require(baseToken.balanceOf(address(this)) >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE");
                                SafeERC20Upgradeable.safeTransfer(baseToken, receiver, _tokenAmount);
                            }
                        }
                        function _msgSender()
                            internal
                            view
                            virtual
                            override(ContextUpgradeable, ERC2771ContextUpgradeable)
                            returns (address sender)
                        {
                            return ERC2771ContextUpgradeable._msgSender();
                        }
                        function _msgData()
                            internal
                            view
                            virtual
                            override(ContextUpgradeable, ERC2771ContextUpgradeable)
                            returns (bytes calldata)
                        {
                            return ERC2771ContextUpgradeable._msgData();
                        }
                        receive() external payable {
                            emit EthReceived(_msgSender(), msg.value);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @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 a proxied contract can't have 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.
                     *
                     * 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.
                     */
                    abstract contract Initializable {
                        /**
                         * @dev Indicates that the contract has been initialized.
                         */
                        bool private _initialized;
                        /**
                         * @dev Indicates that the contract is in the process of being initialized.
                         */
                        bool private _initializing;
                        /**
                         * @dev Modifier to protect an initializer function from being invoked twice.
                         */
                        modifier initializer() {
                            require(_initializing || !_initialized, "Initializable: contract is already initialized");
                            bool isTopLevelCall = !_initializing;
                            if (isTopLevelCall) {
                                _initializing = true;
                                _initialized = true;
                            }
                            _;
                            if (isTopLevelCall) {
                                _initializing = false;
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/ContextUpgradeable.sol";
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        function __Ownable_init() internal initializer {
                            __Context_init_unchained();
                            __Ownable_init_unchained();
                        }
                        function __Ownable_init_unchained() internal initializer {
                            _setOwner(_msgSender());
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            _setOwner(address(0));
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            _setOwner(newOwner);
                        }
                        function _setOwner(address newOwner) private {
                            address oldOwner = _owner;
                            _owner = newOwner;
                            emit OwnershipTransferred(oldOwner, newOwner);
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module that helps prevent reentrant calls to a function.
                     *
                     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
                     * available, which can be applied to functions to make sure there are no nested
                     * (reentrant) calls to them.
                     *
                     * Note that because there is a single `nonReentrant` guard, functions marked as
                     * `nonReentrant` may not call one another. This can be worked around by making
                     * those functions `private`, and then adding `external` `nonReentrant` entry
                     * points to them.
                     *
                     * TIP: If you would like to learn more about reentrancy and alternative ways
                     * to protect against it, check out our blog post
                     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
                     */
                    abstract contract ReentrancyGuardUpgradeable is Initializable {
                        // Booleans are more expensive than uint256 or any type that takes up a full
                        // word because each write operation emits an extra SLOAD to first read the
                        // slot's contents, replace the bits taken up by the boolean, and then write
                        // back. This is the compiler's defense against contract upgrades and
                        // pointer aliasing, and it cannot be disabled.
                        // The values being non-zero value makes deployment a bit more expensive,
                        // but in exchange the refund on every call to nonReentrant will be lower in
                        // amount. Since refunds are capped to a percentage of the total
                        // transaction's gas, it is best to keep them low in cases like this one, to
                        // increase the likelihood of the full refund coming into effect.
                        uint256 private constant _NOT_ENTERED = 1;
                        uint256 private constant _ENTERED = 2;
                        uint256 private _status;
                        function __ReentrancyGuard_init() internal initializer {
                            __ReentrancyGuard_init_unchained();
                        }
                        function __ReentrancyGuard_init_unchained() internal initializer {
                            _status = _NOT_ENTERED;
                        }
                        /**
                         * @dev Prevents a contract from calling itself, directly or indirectly.
                         * Calling a `nonReentrant` function from another `nonReentrant`
                         * function is not supported. It is possible to prevent this from happening
                         * by making the `nonReentrant` function external, and make it call a
                         * `private` function that does the actual work.
                         */
                        modifier nonReentrant() {
                            // On the first call to nonReentrant, _notEntered will be true
                            require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                            // Any calls to nonReentrant after this point will fail
                            _status = _ENTERED;
                            _;
                            // By storing the original value once again, a refund is triggered (see
                            // https://eips.ethereum.org/EIPS/eip-2200)
                            _status = _NOT_ENTERED;
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../IERC20Upgradeable.sol";
                    import "../../../utils/AddressUpgradeable.sol";
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 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 SafeERC20Upgradeable {
                        using AddressUpgradeable for address;
                        function safeTransfer(
                            IERC20Upgradeable token,
                            address to,
                            uint256 value
                        ) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                        function safeTransferFrom(
                            IERC20Upgradeable token,
                            address from,
                            address to,
                            uint256 value
                        ) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                        /**
                         * @dev Deprecated. This function has issues similar to the ones found in
                         * {IERC20-approve}, and its usage is discouraged.
                         *
                         * Whenever possible, use {safeIncreaseAllowance} and
                         * {safeDecreaseAllowance} instead.
                         */
                        function safeApprove(
                            IERC20Upgradeable token,
                            address spender,
                            uint256 value
                        ) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            require(
                                (value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                        function safeIncreaseAllowance(
                            IERC20Upgradeable token,
                            address spender,
                            uint256 value
                        ) internal {
                            uint256 newAllowance = token.allowance(address(this), spender) + value;
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        function safeDecreaseAllowance(
                            IERC20Upgradeable token,
                            address spender,
                            uint256 value
                        ) internal {
                            unchecked {
                                uint256 oldAllowance = token.allowance(address(this), spender);
                                require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                                uint256 newAllowance = oldAllowance - value;
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        }
                        /**
                         * @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).
                         */
                        function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                            // the target address contains contract code and also asserts for success in the low-level call.
                            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                            if (returndata.length > 0) {
                                // Return data is optional
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP.
                     */
                    interface IERC20Upgradeable {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(
                            address sender,
                            address recipient,
                            uint256 amount
                        ) external returns (bool);
                        /**
                         * @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);
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    /**
                     * @dev Context variant with ERC2771 support.
                     * Here _trustedForwarder is made internal instead of private
                     * so it can be changed via Child contracts with a setter method.
                     */
                    abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable {
                        event TrustedForwarderChanged(address indexed _tf);
                        address internal _trustedForwarder;
                        function __ERC2771Context_init(address trustedForwarder) internal initializer {
                            __Context_init_unchained();
                            __ERC2771Context_init_unchained(trustedForwarder);
                        }
                        function __ERC2771Context_init_unchained(address trustedForwarder) internal initializer {
                            _trustedForwarder = trustedForwarder;
                        }
                        function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
                            return forwarder == _trustedForwarder;
                        }
                        function _msgSender() internal view virtual override returns (address sender) {
                            if (isTrustedForwarder(msg.sender)) {
                                // The assembly code is more direct than the Solidity version using `abi.decode`.
                                assembly {
                                    sender := shr(96, calldataload(sub(calldatasize(), 20)))
                                }
                            } else {
                                return super._msgSender();
                            }
                        }
                        function _msgData() internal view virtual override returns (bytes calldata) {
                            if (isTrustedForwarder(msg.sender)) {
                                return msg.data[:msg.data.length - 20];
                            } else {
                                return super._msgData();
                            }
                        }
                        function _setTrustedForwarder(address _tf) internal virtual {
                            require(_tf != address(0), "TrustedForwarder can't be 0");
                            _trustedForwarder = _tf;
                            emit TrustedForwarderChanged(_tf);
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
                    /**
                     * @dev Contract module which allows children to implement an emergency stop
                     * mechanism that can be triggered by an authorized account.
                     *
                     * This module is used through inheritance. It will make available the
                     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
                     * the functions of your contract. Note that they will not be pausable by
                     * simply including this module, only once the modifiers are put in place.
                     */
                    abstract contract Pausable is Initializable, PausableUpgradeable {
                        address private _pauser;
                        event PauserChanged(address indexed previousPauser, address indexed newPauser);
                        /**
                         * @dev The pausable constructor sets the original `pauser` of the contract to the sender
                         * account & Initializes the contract in unpaused state..
                         */
                        function __Pausable_init(address pauser) internal initializer {
                            require(pauser != address(0), "Pauser Address cannot be 0");
                            __Pausable_init();
                            _pauser = pauser;
                        }
                        /**
                         * @return true if `msg.sender` is the owner of the contract.
                         */
                        function isPauser(address pauser) public view returns (bool) {
                            return pauser == _pauser;
                        }
                        /**
                         * @dev Throws if called by any account other than the pauser.
                         */
                        modifier onlyPauser() {
                            require(isPauser(msg.sender), "Only pauser is allowed to perform this operation");
                            _;
                        }
                        /**
                         * @dev Allows the current pauser to transfer control of the contract to a newPauser.
                         * @param newPauser The address to transfer pauserShip to.
                         */
                        function changePauser(address newPauser) public onlyPauser whenNotPaused {
                            _changePauser(newPauser);
                        }
                        /**
                         * @dev Transfers control of the contract to a newPauser.
                         * @param newPauser The address to transfer ownership to.
                         */
                        function _changePauser(address newPauser) internal {
                            require(newPauser != address(0));
                            emit PauserChanged(_pauser, newPauser);
                            _pauser = newPauser;
                        }
                        function renouncePauser() external virtual onlyPauser whenNotPaused {
                            emit PauserChanged(_pauser, address(0));
                            _pauser = address(0);
                        }
                        function pause() public onlyPauser {
                            _pause();
                        }
                        function unpause() public onlyPauser {
                            _unpause();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    struct TokenInfo {
                        uint256 transferOverhead;
                        bool supportedToken;
                        uint256 equilibriumFee; // Percentage fee Represented in basis points
                        uint256 maxFee; // Percentage fee Represented in basis points
                        TokenConfig tokenConfig;
                    }
                    struct TokenConfig {
                        uint256 min;
                        uint256 max;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    interface IExecutorManager {
                        function getExecutorStatus(address executor) external view returns (bool status);
                        function getAllExecutors() external view returns (address[] memory);
                        //Register new Executors
                        function addExecutors(address[] calldata executorArray) external;
                        // Register single executor
                        function addExecutor(address executorAddress) external;
                        //Remove registered Executors
                        function removeExecutors(address[] calldata executorArray) external;
                        // Remove Register single executor
                        function removeExecutor(address executorAddress) external;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    interface ILiquidityProviders {
                        function BASE_DIVISOR() external view returns (uint256);
                        function initialize(address _trustedForwarder, address _lpToken) external;
                        function addLPFee(address _token, uint256 _amount) external;
                        function addNativeLiquidity() external;
                        function addTokenLiquidity(address _token, uint256 _amount) external;
                        function claimFee(uint256 _nftId) external;
                        function getFeeAccumulatedOnNft(uint256 _nftId) external view returns (uint256);
                        function getSuppliedLiquidityByToken(address tokenAddress) external view returns (uint256);
                        function getTokenPriceInLPShares(address _baseToken) external view returns (uint256);
                        function getTotalLPFeeByToken(address tokenAddress) external view returns (uint256);
                        function getTotalReserveByToken(address tokenAddress) external view returns (uint256);
                        function getSuppliedLiquidity(uint256 _nftId) external view returns (uint256);
                        function increaseNativeLiquidity(uint256 _nftId) external;
                        function increaseTokenLiquidity(uint256 _nftId, uint256 _amount) external;
                        function isTrustedForwarder(address forwarder) external view returns (bool);
                        function owner() external view returns (address);
                        function paused() external view returns (bool);
                        function removeLiquidity(uint256 _nftId, uint256 amount) external;
                        function renounceOwnership() external;
                        function setLiquidityPool(address _liquidityPool) external;
                        function setLpToken(address _lpToken) external;
                        function setWhiteListPeriodManager(address _whiteListPeriodManager) external;
                        function sharesToTokenAmount(uint256 _shares, address _tokenAddress) external view returns (uint256);
                        function totalLPFees(address) external view returns (uint256);
                        function totalLiquidity(address) external view returns (uint256);
                        function totalReserve(address) external view returns (uint256);
                        function totalSharesMinted(address) external view returns (uint256);
                        function transferOwnership(address newOwner) external;
                        function whiteListPeriodManager() external view returns (address);
                        function increaseCurrentLiquidity(address tokenAddress, uint256 amount) external;
                        function decreaseCurrentLiquidity(address tokenAddress, uint256 amount) external;
                        function getCurrentLiquidity(address tokenAddress) external view returns (uint256);
                    }
                    // SPDX-License-Identifier: Apache-2.0
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
                    interface IERC20Detailed is IERC20Upgradeable {
                      function name() external view returns(string memory);
                      function decimals() external view returns(uint256);
                    }
                    interface IERC20Nonces is IERC20Detailed {
                      function nonces(address holder) external view returns(uint);
                    }
                    interface IERC20Permit is IERC20Nonces {
                      function permit(address holder, address spender, uint256 nonce, uint256 expiry,
                                      bool allowed, uint8 v, bytes32 r, bytes32 s) external;
                      function permit(address holder, address spender, uint256 value, uint256 expiry,
                                      uint8 v, bytes32 r, bytes32 s) external;
                    }// SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "../structures/TokenConfig.sol";
                    interface ITokenManager {
                        function getEquilibriumFee(address tokenAddress) external view returns (uint256);
                        function getMaxFee(address tokenAddress) external view returns (uint256);
                        function changeFee(
                            address tokenAddress,
                            uint256 _equilibriumFee,
                            uint256 _maxFee
                        ) external;
                        function tokensInfo(address tokenAddress)
                            external
                            view
                            returns (
                                uint256 transferOverhead,
                                bool supportedToken,
                                uint256 equilibriumFee,
                                uint256 maxFee,
                                TokenConfig memory config
                            );
                        function excessStateTransferFeePerc(address tokenAddress) external view returns (uint256);
                        function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory);
                        function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory);
                        function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory);
                        function changeExcessStateFee(address _tokenAddress, uint256 _excessStateFeePer) external;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "../structures/SwapRequest.sol";
                    interface ISwapAdaptor {
                        function swap(
                            address inputTokenAddress,
                            uint256 amountInMaximum,
                            address receiver,
                            SwapRequest[] calldata swapRequests
                        ) external returns (uint256 amountIn);
                        function swapNative(
                            uint256 amountInMaximum,
                            address receiver,
                            SwapRequest[] calldata swapRequests
                        ) external returns (uint256 amountOut);
                    }// SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract ContextUpgradeable is Initializable {
                        function __Context_init() internal initializer {
                            __Context_init_unchained();
                        }
                        function __Context_init_unchained() internal initializer {
                        }
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            return msg.data;
                        }
                        uint256[50] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library AddressUpgradeable {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies on extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                            uint256 size;
                            assembly {
                                size := extcodesize(account)
                            }
                            return size > 0;
                        }
                        /**
                         * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            (bool success, ) = recipient.call{value: amount}("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                        /**
                         * @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, it is bubbled up by this
                         * function (like regular Solidity function calls).
                         *
                         * 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.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionCall(target, data, "Address: low-level call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                         * `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(
                            address target,
                            bytes memory data,
                            string memory errorMessage
                        ) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, 0, errorMessage);
                        }
                        /**
                         * @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`.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(
                            address target,
                            bytes memory data,
                            uint256 value
                        ) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(
                            address target,
                            bytes memory data,
                            uint256 value,
                            string memory errorMessage
                        ) internal returns (bytes memory) {
                            require(address(this).balance >= value, "Address: insufficient balance for call");
                            require(isContract(target), "Address: call to non-contract");
                            (bool success, bytes memory returndata) = target.call{value: value}(data);
                            return verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                            return functionStaticCall(target, data, "Address: low-level static call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(
                            address target,
                            bytes memory data,
                            string memory errorMessage
                        ) internal view returns (bytes memory) {
                            require(isContract(target), "Address: static call to non-contract");
                            (bool success, bytes memory returndata) = target.staticcall(data);
                            return verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
                         * revert reason using the provided one.
                         *
                         * _Available since v4.3._
                         */
                        function verifyCallResult(
                            bool success,
                            bytes memory returndata,
                            string memory errorMessage
                        ) internal pure returns (bytes memory) {
                            if (success) {
                                return returndata;
                            } else {
                                // 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 {
                                        let returndata_size := mload(returndata)
                                        revert(add(32, returndata), returndata_size)
                                    }
                                } else {
                                    revert(errorMessage);
                                }
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/ContextUpgradeable.sol";
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module which allows children to implement an emergency stop
                     * mechanism that can be triggered by an authorized account.
                     *
                     * This module is used through inheritance. It will make available the
                     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
                     * the functions of your contract. Note that they will not be pausable by
                     * simply including this module, only once the modifiers are put in place.
                     */
                    abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
                        /**
                         * @dev Emitted when the pause is triggered by `account`.
                         */
                        event Paused(address account);
                        /**
                         * @dev Emitted when the pause is lifted by `account`.
                         */
                        event Unpaused(address account);
                        bool private _paused;
                        /**
                         * @dev Initializes the contract in unpaused state.
                         */
                        function __Pausable_init() internal initializer {
                            __Context_init_unchained();
                            __Pausable_init_unchained();
                        }
                        function __Pausable_init_unchained() internal initializer {
                            _paused = false;
                        }
                        /**
                         * @dev Returns true if the contract is paused, and false otherwise.
                         */
                        function paused() public view virtual returns (bool) {
                            return _paused;
                        }
                        /**
                         * @dev Modifier to make a function callable only when the contract is not paused.
                         *
                         * Requirements:
                         *
                         * - The contract must not be paused.
                         */
                        modifier whenNotPaused() {
                            require(!paused(), "Pausable: paused");
                            _;
                        }
                        /**
                         * @dev Modifier to make a function callable only when the contract is paused.
                         *
                         * Requirements:
                         *
                         * - The contract must be paused.
                         */
                        modifier whenPaused() {
                            require(paused(), "Pausable: not paused");
                            _;
                        }
                        /**
                         * @dev Triggers stopped state.
                         *
                         * Requirements:
                         *
                         * - The contract must not be paused.
                         */
                        function _pause() internal virtual whenNotPaused {
                            _paused = true;
                            emit Paused(_msgSender());
                        }
                        /**
                         * @dev Returns to normal state.
                         *
                         * Requirements:
                         *
                         * - The contract must be paused.
                         */
                        function _unpause() internal virtual whenPaused {
                            _paused = false;
                            emit Unpaused(_msgSender());
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    enum SwapOperation {ExactOutput, ExactInput}
                    struct SwapRequest {
                        address tokenAddress;
                        uint256 percentage;
                        uint256 amount;
                        SwapOperation operation;
                        bytes path;
                    }
                    

                    File 7 of 9: TransparentUpgradeableProxy
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
                    import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
                    import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
                    // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
                    contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
                        constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../Proxy.sol";
                    import "./ERC1967Upgrade.sol";
                    /**
                     * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
                     * implementation address that can be changed. This address is stored in storage in the location specified by
                     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
                     * implementation behind the proxy.
                     */
                    contract ERC1967Proxy is Proxy, ERC1967Upgrade {
                        /**
                         * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
                         *
                         * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
                         * function call, and allows initializating the storage of the proxy like a Solidity constructor.
                         */
                        constructor(address _logic, bytes memory _data) payable {
                            assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                            _upgradeToAndCall(_logic, _data, false);
                        }
                        /**
                         * @dev Returns the current implementation address.
                         */
                        function _implementation() internal view virtual override returns (address impl) {
                            return ERC1967Upgrade._getImplementation();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../ERC1967/ERC1967Proxy.sol";
                    /**
                     * @dev This contract implements a proxy that is upgradeable by an admin.
                     *
                     * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
                     * clashing], which can potentially be used in an attack, this contract uses the
                     * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
                     * things that go hand in hand:
                     *
                     * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
                     * that call matches one of the admin functions exposed by the proxy itself.
                     * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
                     * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
                     * "admin cannot fallback to proxy target".
                     *
                     * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
                     * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
                     * to sudden errors when trying to call a function from the proxy implementation.
                     *
                     * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
                     * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
                     */
                    contract TransparentUpgradeableProxy is ERC1967Proxy {
                        /**
                         * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
                         * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
                         */
                        constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                            assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                            _changeAdmin(admin_);
                        }
                        /**
                         * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
                         */
                        modifier ifAdmin() {
                            if (msg.sender == _getAdmin()) {
                                _;
                            } else {
                                _fallback();
                            }
                        }
                        /**
                         * @dev Returns the current admin.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
                         *
                         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                         * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                         */
                        function admin() external ifAdmin returns (address admin_) {
                            admin_ = _getAdmin();
                        }
                        /**
                         * @dev Returns the current implementation.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
                         *
                         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                         * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                         */
                        function implementation() external ifAdmin returns (address implementation_) {
                            implementation_ = _implementation();
                        }
                        /**
                         * @dev Changes the admin of the proxy.
                         *
                         * Emits an {AdminChanged} event.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
                         */
                        function changeAdmin(address newAdmin) external virtual ifAdmin {
                            _changeAdmin(newAdmin);
                        }
                        /**
                         * @dev Upgrade the implementation of the proxy.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
                         */
                        function upgradeTo(address newImplementation) external ifAdmin {
                            _upgradeToAndCall(newImplementation, bytes(""), false);
                        }
                        /**
                         * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
                         * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
                         * proxied contract.
                         *
                         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
                         */
                        function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                            _upgradeToAndCall(newImplementation, data, true);
                        }
                        /**
                         * @dev Returns the current admin.
                         */
                        function _admin() internal view virtual returns (address) {
                            return _getAdmin();
                        }
                        /**
                         * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
                         */
                        function _beforeFallback() internal virtual override {
                            require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                            super._beforeFallback();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "./TransparentUpgradeableProxy.sol";
                    import "../../access/Ownable.sol";
                    /**
                     * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
                     * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
                     */
                    contract ProxyAdmin is Ownable {
                        /**
                         * @dev Returns the current implementation of `proxy`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                            // We need to manually run the static call since the getter cannot be flagged as view
                            // bytes4(keccak256("implementation()")) == 0x5c60da1b
                            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
                            require(success);
                            return abi.decode(returndata, (address));
                        }
                        /**
                         * @dev Returns the current admin of `proxy`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                            // We need to manually run the static call since the getter cannot be flagged as view
                            // bytes4(keccak256("admin()")) == 0xf851a440
                            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
                            require(success);
                            return abi.decode(returndata, (address));
                        }
                        /**
                         * @dev Changes the admin of `proxy` to `newAdmin`.
                         *
                         * Requirements:
                         *
                         * - This contract must be the current admin of `proxy`.
                         */
                        function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
                            proxy.changeAdmin(newAdmin);
                        }
                        /**
                         * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
                            proxy.upgradeTo(implementation);
                        }
                        /**
                         * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
                         * {TransparentUpgradeableProxy-upgradeToAndCall}.
                         *
                         * Requirements:
                         *
                         * - This contract must be the admin of `proxy`.
                         */
                        function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
                            proxy.upgradeToAndCall{value: msg.value}(implementation, data);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
                     * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
                     * be specified by overriding the virtual {_implementation} function.
                     *
                     * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
                     * different contract through the {_delegate} function.
                     *
                     * The success and return data of the delegated call will be returned back to the caller of the proxy.
                     */
                    abstract contract Proxy {
                        /**
                         * @dev Delegates the current call to `implementation`.
                         *
                         * This function does not return to its internall call site, it will return directly to the external caller.
                         */
                        function _delegate(address implementation) internal virtual {
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                // Copy msg.data. We take full control of memory in this inline assembly
                                // block because it will not return to Solidity code. We overwrite the
                                // Solidity scratch pad at memory position 0.
                                calldatacopy(0, 0, calldatasize())
                                // Call the implementation.
                                // out and outsize are 0 because we don't know the size yet.
                                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                                // Copy the returned data.
                                returndatacopy(0, 0, returndatasize())
                                switch result
                                // delegatecall returns 0 on error.
                                case 0 { revert(0, returndatasize()) }
                                default { return(0, returndatasize()) }
                            }
                        }
                        /**
                         * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
                         * and {_fallback} should delegate.
                         */
                        function _implementation() internal view virtual returns (address);
                        /**
                         * @dev Delegates the current call to the address returned by `_implementation()`.
                         *
                         * This function does not return to its internall call site, it will return directly to the external caller.
                         */
                        function _fallback() internal virtual {
                            _beforeFallback();
                            _delegate(_implementation());
                        }
                        /**
                         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
                         * function in the contract matches the call data.
                         */
                        fallback () external payable virtual {
                            _fallback();
                        }
                        /**
                         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
                         * is empty.
                         */
                        receive () external payable virtual {
                            _fallback();
                        }
                        /**
                         * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
                         * call, or as part of the Solidity `fallback` or `receive` functions.
                         *
                         * If overriden should call `super._beforeFallback()`.
                         */
                        function _beforeFallback() internal virtual {
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.2;
                    import "../beacon/IBeacon.sol";
                    import "../../utils/Address.sol";
                    import "../../utils/StorageSlot.sol";
                    /**
                     * @dev This abstract contract provides getters and event emitting update functions for
                     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
                     *
                     * _Available since v4.1._
                     *
                     * @custom:oz-upgrades-unsafe-allow delegatecall
                     */
                    abstract contract ERC1967Upgrade {
                        // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
                        bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
                        /**
                         * @dev Storage slot with the address of the current implementation.
                         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                         * validated in the constructor.
                         */
                        bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                        /**
                         * @dev Emitted when the implementation is upgraded.
                         */
                        event Upgraded(address indexed implementation);
                        /**
                         * @dev Returns the current implementation address.
                         */
                        function _getImplementation() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                        }
                        /**
                         * @dev Stores a new address in the EIP1967 implementation slot.
                         */
                        function _setImplementation(address newImplementation) private {
                            require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                            StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                        }
                        /**
                         * @dev Perform implementation upgrade
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeTo(address newImplementation) internal {
                            _setImplementation(newImplementation);
                            emit Upgraded(newImplementation);
                        }
                        /**
                         * @dev Perform implementation upgrade with additional setup call.
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
                            _setImplementation(newImplementation);
                            emit Upgraded(newImplementation);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(newImplementation, data);
                            }
                        }
                        /**
                         * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
                         *
                         * Emits an {Upgraded} event.
                         */
                        function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
                            address oldImplementation = _getImplementation();
                            // Initial upgrade and setup call
                            _setImplementation(newImplementation);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(newImplementation, data);
                            }
                            // Perform rollback test if not already in progress
                            StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
                            if (!rollbackTesting.value) {
                                // Trigger rollback using upgradeTo from the new implementation
                                rollbackTesting.value = true;
                                Address.functionDelegateCall(
                                    newImplementation,
                                    abi.encodeWithSignature(
                                        "upgradeTo(address)",
                                        oldImplementation
                                    )
                                );
                                rollbackTesting.value = false;
                                // Check rollback was effective
                                require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                                // Finally reset to the new implementation and log the upgrade
                                _setImplementation(newImplementation);
                                emit Upgraded(newImplementation);
                            }
                        }
                        /**
                         * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
                         * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
                         *
                         * Emits a {BeaconUpgraded} event.
                         */
                        function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
                            _setBeacon(newBeacon);
                            emit BeaconUpgraded(newBeacon);
                            if (data.length > 0 || forceCall) {
                                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                            }
                        }
                        /**
                         * @dev Storage slot with the admin of the contract.
                         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                         * validated in the constructor.
                         */
                        bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                        /**
                         * @dev Emitted when the admin account has changed.
                         */
                        event AdminChanged(address previousAdmin, address newAdmin);
                        /**
                         * @dev Returns the current admin.
                         */
                        function _getAdmin() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
                        }
                        /**
                         * @dev Stores a new address in the EIP1967 admin slot.
                         */
                        function _setAdmin(address newAdmin) private {
                            require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                            StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
                        }
                        /**
                         * @dev Changes the admin of the proxy.
                         *
                         * Emits an {AdminChanged} event.
                         */
                        function _changeAdmin(address newAdmin) internal {
                            emit AdminChanged(_getAdmin(), newAdmin);
                            _setAdmin(newAdmin);
                        }
                        /**
                         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                         * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
                         */
                        bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                        /**
                         * @dev Emitted when the beacon is upgraded.
                         */
                        event BeaconUpgraded(address indexed beacon);
                        /**
                         * @dev Returns the current beacon.
                         */
                        function _getBeacon() internal view returns (address) {
                            return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
                        }
                        /**
                         * @dev Stores a new beacon in the EIP1967 beacon slot.
                         */
                        function _setBeacon(address newBeacon) private {
                            require(
                                Address.isContract(newBeacon),
                                "ERC1967: new beacon is not a contract"
                            );
                            require(
                                Address.isContract(IBeacon(newBeacon).implementation()),
                                "ERC1967: beacon implementation is not a contract"
                            );
                            StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev This is the interface that {BeaconProxy} expects of its beacon.
                     */
                    interface IBeacon {
                        /**
                         * @dev Must return an address that can be used as a delegate call target.
                         *
                         * {BeaconProxy} will check that this address is a contract.
                         */
                        function implementation() external view returns (address);
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies on extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                            uint256 size;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { size := extcodesize(account) }
                            return size > 0;
                        }
                        /**
                         * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                            (bool success, ) = recipient.call{ value: amount }("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                        /**
                         * @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, it is bubbled up by this
                         * function (like regular Solidity function calls).
                         *
                         * 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.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                         * `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, 0, errorMessage);
                        }
                        /**
                         * @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`.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                            require(address(this).balance >= value, "Address: insufficient balance for call");
                            require(isContract(target), "Address: call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.call{ value: value }(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                            return functionStaticCall(target, data, "Address: low-level static call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                            require(isContract(target), "Address: static call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.staticcall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a delegate call.
                         *
                         * _Available since v3.4._
                         */
                        function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                            require(isContract(target), "Address: delegate call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = target.delegatecall(data);
                            return _verifyCallResult(success, returndata, errorMessage);
                        }
                        function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                            if (success) {
                                return returndata;
                            } else {
                                // 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
                                    // solhint-disable-next-line no-inline-assembly
                                    assembly {
                                        let returndata_size := mload(returndata)
                                        revert(add(32, returndata), returndata_size)
                                    }
                                } else {
                                    revert(errorMessage);
                                }
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Library for reading and writing primitive types to specific storage slots.
                     *
                     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
                     * This library helps with reading and writing to such slots without the need for inline assembly.
                     *
                     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
                     *
                     * Example usage to set ERC1967 implementation slot:
                     * ```
                     * contract ERC1967 {
                     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                     *
                     *     function _getImplementation() internal view returns (address) {
                     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                     *     }
                     *
                     *     function _setImplementation(address newImplementation) internal {
                     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                     *     }
                     * }
                     * ```
                     *
                     * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
                     */
                    library StorageSlot {
                        struct AddressSlot {
                            address value;
                        }
                        struct BooleanSlot {
                            bool value;
                        }
                        struct Bytes32Slot {
                            bytes32 value;
                        }
                        struct Uint256Slot {
                            uint256 value;
                        }
                        /**
                         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                         */
                        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                         */
                        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                         */
                        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                        /**
                         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                         */
                        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                            assembly {
                                r.slot := slot
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/Context.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract Ownable is Context {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor () {
                            address msgSender = _msgSender();
                            _owner = msgSender;
                            emit OwnershipTransferred(address(0), msgSender);
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract Context {
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../ERC1967/ERC1967Upgrade.sol";
                    /**
                     * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
                     * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
                     * continuation of the upgradability.
                     *
                     * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
                     *
                     * _Available since v4.1._
                     */
                    abstract contract UUPSUpgradeable is ERC1967Upgrade {
                        function upgradeTo(address newImplementation) external virtual {
                            _authorizeUpgrade(newImplementation);
                            _upgradeToAndCallSecure(newImplementation, bytes(""), false);
                        }
                        function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
                            _authorizeUpgrade(newImplementation);
                            _upgradeToAndCallSecure(newImplementation, data, true);
                        }
                        function _authorizeUpgrade(address newImplementation) internal virtual;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.2;
                    import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
                    abstract contract Proxiable is UUPSUpgradeable {
                        function _authorizeUpgrade(address newImplementation) internal override {
                            _beforeUpgrade(newImplementation);
                        }
                        function _beforeUpgrade(address newImplementation) internal virtual;
                    }
                    contract ChildOfProxiable is Proxiable {
                        function _beforeUpgrade(address newImplementation) internal virtual override {}
                    }
                    

                    File 8 of 9: TokenManager
                    // $$$$$$$$\\        $$\\                                 $$\\      $$\\
                    // \\__$$  __|       $$ |                                $$$\\    $$$ |
                    //    $$ | $$$$$$\\  $$ |  $$\\  $$$$$$\\  $$$$$$$\\        $$$$\\  $$$$ | $$$$$$\\  $$$$$$$\\   $$$$$$\\   $$$$$$\\   $$$$$$\\   $$$$$$\\
                    //    $$ |$$  __$$\\ $$ | $$  |$$  __$$\\ $$  __$$\\       $$\\$$\\$$ $$ | \\____$$\\ $$  __$$\\  \\____$$\\ $$  __$$\\ $$  __$$\\ $$  __$$\\
                    //    $$ |$$ /  $$ |$$$$$$  / $$$$$$$$ |$$ |  $$ |      $$ \\$$$  $$ | $$$$$$$ |$$ |  $$ | $$$$$$$ |$$ /  $$ |$$$$$$$$ |$$ |  \\__|
                    //    $$ |$$ |  $$ |$$  _$$<  $$   ____|$$ |  $$ |      $$ |\\$  /$$ |$$  __$$ |$$ |  $$ |$$  __$$ |$$ |  $$ |$$   ____|$$ |
                    //    $$ |\\$$$$$$  |$$ | \\$$\\ \\$$$$$$$\\ $$ |  $$ |      $$ | \\_/ $$ |\\$$$$$$$ |$$ |  $$ |\\$$$$$$$ |\\$$$$$$$ |\\$$$$$$$\\ $$ |
                    //    \\__| \\______/ \\__|  \\__| \\_______|\\__|  \\__|      \\__|     \\__| \\_______|\\__|  \\__| \\_______| \\____$$ | \\_______|\\__|
                    //                                                                                                 $$\\   $$ |
                    //                                                                                                 \\$$$$$$  |
                    //                                                                                                  \\______/
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
                    import "../../security/Pausable.sol";
                    import "../metatx/ERC2771ContextUpgradeable.sol";
                    import "../interfaces/ITokenManager.sol";
                    contract TokenManager is ITokenManager, ERC2771ContextUpgradeable, OwnableUpgradeable, Pausable {
                        mapping(address => TokenInfo) public override tokensInfo;
                        // Excess State Transfer Fee Percentage
                        mapping(address => uint256) public override excessStateTransferFeePerc;
                        event FeeChanged(address indexed tokenAddress, uint256 indexed equilibriumFee, uint256 indexed maxFee);
                        event ExcessStateTransferFeePercChanged(address indexed tokenAddress, uint256 indexed fee);
                        modifier tokenChecks(address tokenAddress) {
                            require(tokenAddress != address(0), "Token address cannot be 0");
                            require(tokensInfo[tokenAddress].supportedToken, "Token not supported");
                            _;
                        }
                        /**
                         * First key is toChainId and second key is token address being deposited on current chain
                         */
                        mapping(uint256 => mapping(address => TokenConfig)) public depositConfig;
                        /**
                         * Store min/max amount of token to transfer based on token address
                         */
                        mapping(address => TokenConfig) public transferConfig;
                        function initialize(address trustedForwarder, address pauser) external initializer {
                            __ERC2771Context_init(trustedForwarder);
                            __Ownable_init();
                            __Pausable_init(pauser);
                        }
                        function getEquilibriumFee(address tokenAddress) public view override returns (uint256) {
                            return tokensInfo[tokenAddress].equilibriumFee;
                        }
                        function getMaxFee(address tokenAddress) public view override returns (uint256) {
                            return tokensInfo[tokenAddress].maxFee;
                        }
                        function changeFee(
                            address tokenAddress,
                            uint256 _equilibriumFee,
                            uint256 _maxFee
                        ) external override onlyOwner whenNotPaused {
                            require(_equilibriumFee != 0, "Equilibrium Fee cannot be 0");
                            require(_maxFee != 0, "Max Fee cannot be 0");
                            require(_equilibriumFee <= _maxFee && _maxFee <= 10000000000, "Max Fee cannot be greater than 100%");
                            tokensInfo[tokenAddress].equilibriumFee = _equilibriumFee;
                            tokensInfo[tokenAddress].maxFee = _maxFee;
                            emit FeeChanged(tokenAddress, tokensInfo[tokenAddress].equilibriumFee, tokensInfo[tokenAddress].maxFee);
                        }
                        function changeExcessStateFee(address _tokenAddress, uint256 _excessStateFeePer)
                            external
                            override
                            onlyOwner
                            whenNotPaused
                        {
                            require(_tokenAddress != address(0), "Token address cannot be 0");
                            require(_excessStateFeePer != 0, "Excess State Fee Percentage cannot be 0");
                            excessStateTransferFeePerc[_tokenAddress] = _excessStateFeePer;
                            emit ExcessStateTransferFeePercChanged(_tokenAddress, _excessStateFeePer);
                        }
                        function setTokenTransferOverhead(address tokenAddress, uint256 gasOverhead)
                            external
                            tokenChecks(tokenAddress)
                            onlyOwner
                        {
                            tokensInfo[tokenAddress].transferOverhead = gasOverhead;
                        }
                        /**
                         * Set DepositConfig for the given combination of toChainId, tokenAddress.
                         * This is used while depositing token in Liquidity Pool. Based on the destination chainid
                         * min and max deposit amount is checked.
                         */
                        function setDepositConfig(
                            uint256[] memory toChainId,
                            address[] memory tokenAddresses,
                            TokenConfig[] memory tokenConfig
                        ) external onlyOwner {
                            require(
                                (toChainId.length == tokenAddresses.length) && (tokenAddresses.length == tokenConfig.length),
                                " ERR_ARRAY_LENGTH_MISMATCH"
                            );
                            uint256 length = tokenConfig.length;
                            for (uint256 index; index < length; ) {
                                depositConfig[toChainId[index]][tokenAddresses[index]].min = tokenConfig[index].min;
                                depositConfig[toChainId[index]][tokenAddresses[index]].max = tokenConfig[index].max;
                                unchecked {
                                    ++index;
                                }
                            }
                        }
                        function addSupportedToken(
                            address tokenAddress,
                            uint256 minCapLimit,
                            uint256 maxCapLimit,
                            uint256 equilibriumFee,
                            uint256 maxFee,
                            uint256 transferOverhead
                        ) external onlyOwner {
                            require(tokenAddress != address(0), "Token address cannot be 0");
                            require(maxCapLimit > minCapLimit, "maxCapLimit > minCapLimit");
                            tokensInfo[tokenAddress].supportedToken = true;
                            transferConfig[tokenAddress].min = minCapLimit;
                            transferConfig[tokenAddress].max = maxCapLimit;
                            tokensInfo[tokenAddress].tokenConfig = transferConfig[tokenAddress];
                            tokensInfo[tokenAddress].equilibriumFee = equilibriumFee;
                            tokensInfo[tokenAddress].maxFee = maxFee;
                            tokensInfo[tokenAddress].transferOverhead = transferOverhead;
                        }
                        function removeSupportedToken(address tokenAddress) external tokenChecks(tokenAddress) onlyOwner {
                            tokensInfo[tokenAddress].supportedToken = false;
                        }
                        function updateTokenCap(
                            address tokenAddress,
                            uint256 minCapLimit,
                            uint256 maxCapLimit
                        ) external tokenChecks(tokenAddress) onlyOwner {
                            require(maxCapLimit > minCapLimit, "maxCapLimit > minCapLimit");
                            transferConfig[tokenAddress].min = minCapLimit;
                            transferConfig[tokenAddress].max = maxCapLimit;
                        }
                        function getTokensInfo(address tokenAddress) public view override returns (TokenInfo memory) {
                            TokenInfo memory tokenInfo = TokenInfo(
                                tokensInfo[tokenAddress].transferOverhead,
                                tokensInfo[tokenAddress].supportedToken,
                                tokensInfo[tokenAddress].equilibriumFee,
                                tokensInfo[tokenAddress].maxFee,
                                transferConfig[tokenAddress]
                            );
                            return tokenInfo;
                        }
                        function getDepositConfig(uint256 toChainId, address tokenAddress)
                            public
                            view
                            override
                            returns (TokenConfig memory)
                        {
                            return depositConfig[toChainId][tokenAddress];
                        }
                        function getTransferConfig(address tokenAddress) public view override returns (TokenConfig memory) {
                            return transferConfig[tokenAddress];
                        }
                        function _msgSender()
                            internal
                            view
                            virtual
                            override(ContextUpgradeable, ERC2771ContextUpgradeable)
                            returns (address sender)
                        {
                            return ERC2771ContextUpgradeable._msgSender();
                        }
                        function _msgData()
                            internal
                            view
                            virtual
                            override(ContextUpgradeable, ERC2771ContextUpgradeable)
                            returns (bytes calldata)
                        {
                            return ERC2771ContextUpgradeable._msgData();
                        }
                        function setTrustedForwarder(address _tf) external onlyOwner {
                            _setTrustedForwarder(_tf);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/ContextUpgradeable.sol";
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        function __Ownable_init() internal initializer {
                            __Context_init_unchained();
                            __Ownable_init_unchained();
                        }
                        function __Ownable_init_unchained() internal initializer {
                            _setOwner(_msgSender());
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            _setOwner(address(0));
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            _setOwner(newOwner);
                        }
                        function _setOwner(address newOwner) private {
                            address oldOwner = _owner;
                            _owner = newOwner;
                            emit OwnershipTransferred(oldOwner, newOwner);
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
                    /**
                     * @dev Contract module which allows children to implement an emergency stop
                     * mechanism that can be triggered by an authorized account.
                     *
                     * This module is used through inheritance. It will make available the
                     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
                     * the functions of your contract. Note that they will not be pausable by
                     * simply including this module, only once the modifiers are put in place.
                     */
                    abstract contract Pausable is Initializable, PausableUpgradeable {
                        address private _pauser;
                        event PauserChanged(address indexed previousPauser, address indexed newPauser);
                        /**
                         * @dev The pausable constructor sets the original `pauser` of the contract to the sender
                         * account & Initializes the contract in unpaused state..
                         */
                        function __Pausable_init(address pauser) internal initializer {
                            require(pauser != address(0), "Pauser Address cannot be 0");
                            __Pausable_init();
                            _pauser = pauser;
                        }
                        /**
                         * @return true if `msg.sender` is the owner of the contract.
                         */
                        function isPauser(address pauser) public view returns (bool) {
                            return pauser == _pauser;
                        }
                        /**
                         * @dev Throws if called by any account other than the pauser.
                         */
                        modifier onlyPauser() {
                            require(isPauser(msg.sender), "Only pauser is allowed to perform this operation");
                            _;
                        }
                        /**
                         * @dev Allows the current pauser to transfer control of the contract to a newPauser.
                         * @param newPauser The address to transfer pauserShip to.
                         */
                        function changePauser(address newPauser) public onlyPauser whenNotPaused {
                            _changePauser(newPauser);
                        }
                        /**
                         * @dev Transfers control of the contract to a newPauser.
                         * @param newPauser The address to transfer ownership to.
                         */
                        function _changePauser(address newPauser) internal {
                            require(newPauser != address(0));
                            emit PauserChanged(_pauser, newPauser);
                            _pauser = newPauser;
                        }
                        function renouncePauser() external virtual onlyPauser whenNotPaused {
                            emit PauserChanged(_pauser, address(0));
                            _pauser = address(0);
                        }
                        function pause() public onlyPauser {
                            _pause();
                        }
                        function unpause() public onlyPauser {
                            _unpause();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    /**
                     * @dev Context variant with ERC2771 support.
                     * Here _trustedForwarder is made internal instead of private
                     * so it can be changed via Child contracts with a setter method.
                     */
                    abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable {
                        event TrustedForwarderChanged(address indexed _tf);
                        address internal _trustedForwarder;
                        function __ERC2771Context_init(address trustedForwarder) internal initializer {
                            __Context_init_unchained();
                            __ERC2771Context_init_unchained(trustedForwarder);
                        }
                        function __ERC2771Context_init_unchained(address trustedForwarder) internal initializer {
                            _trustedForwarder = trustedForwarder;
                        }
                        function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
                            return forwarder == _trustedForwarder;
                        }
                        function _msgSender() internal view virtual override returns (address sender) {
                            if (isTrustedForwarder(msg.sender)) {
                                // The assembly code is more direct than the Solidity version using `abi.decode`.
                                assembly {
                                    sender := shr(96, calldataload(sub(calldatasize(), 20)))
                                }
                            } else {
                                return super._msgSender();
                            }
                        }
                        function _msgData() internal view virtual override returns (bytes calldata) {
                            if (isTrustedForwarder(msg.sender)) {
                                return msg.data[:msg.data.length - 20];
                            } else {
                                return super._msgData();
                            }
                        }
                        function _setTrustedForwarder(address _tf) internal virtual {
                            require(_tf != address(0), "TrustedForwarder can't be 0");
                            _trustedForwarder = _tf;
                            emit TrustedForwarderChanged(_tf);
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "../structures/TokenConfig.sol";
                    interface ITokenManager {
                        function getEquilibriumFee(address tokenAddress) external view returns (uint256);
                        function getMaxFee(address tokenAddress) external view returns (uint256);
                        function changeFee(
                            address tokenAddress,
                            uint256 _equilibriumFee,
                            uint256 _maxFee
                        ) external;
                        function tokensInfo(address tokenAddress)
                            external
                            view
                            returns (
                                uint256 transferOverhead,
                                bool supportedToken,
                                uint256 equilibriumFee,
                                uint256 maxFee,
                                TokenConfig memory config
                            );
                        function excessStateTransferFeePerc(address tokenAddress) external view returns (uint256);
                        function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory);
                        function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory);
                        function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory);
                        function changeExcessStateFee(address _tokenAddress, uint256 _excessStateFeePer) external;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract ContextUpgradeable is Initializable {
                        function __Context_init() internal initializer {
                            __Context_init_unchained();
                        }
                        function __Context_init_unchained() internal initializer {
                        }
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            return msg.data;
                        }
                        uint256[50] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @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 a proxied contract can't have 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.
                     *
                     * 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.
                     */
                    abstract contract Initializable {
                        /**
                         * @dev Indicates that the contract has been initialized.
                         */
                        bool private _initialized;
                        /**
                         * @dev Indicates that the contract is in the process of being initialized.
                         */
                        bool private _initializing;
                        /**
                         * @dev Modifier to protect an initializer function from being invoked twice.
                         */
                        modifier initializer() {
                            require(_initializing || !_initialized, "Initializable: contract is already initialized");
                            bool isTopLevelCall = !_initializing;
                            if (isTopLevelCall) {
                                _initializing = true;
                                _initialized = true;
                            }
                            _;
                            if (isTopLevelCall) {
                                _initializing = false;
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/ContextUpgradeable.sol";
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module which allows children to implement an emergency stop
                     * mechanism that can be triggered by an authorized account.
                     *
                     * This module is used through inheritance. It will make available the
                     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
                     * the functions of your contract. Note that they will not be pausable by
                     * simply including this module, only once the modifiers are put in place.
                     */
                    abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
                        /**
                         * @dev Emitted when the pause is triggered by `account`.
                         */
                        event Paused(address account);
                        /**
                         * @dev Emitted when the pause is lifted by `account`.
                         */
                        event Unpaused(address account);
                        bool private _paused;
                        /**
                         * @dev Initializes the contract in unpaused state.
                         */
                        function __Pausable_init() internal initializer {
                            __Context_init_unchained();
                            __Pausable_init_unchained();
                        }
                        function __Pausable_init_unchained() internal initializer {
                            _paused = false;
                        }
                        /**
                         * @dev Returns true if the contract is paused, and false otherwise.
                         */
                        function paused() public view virtual returns (bool) {
                            return _paused;
                        }
                        /**
                         * @dev Modifier to make a function callable only when the contract is not paused.
                         *
                         * Requirements:
                         *
                         * - The contract must not be paused.
                         */
                        modifier whenNotPaused() {
                            require(!paused(), "Pausable: paused");
                            _;
                        }
                        /**
                         * @dev Modifier to make a function callable only when the contract is paused.
                         *
                         * Requirements:
                         *
                         * - The contract must be paused.
                         */
                        modifier whenPaused() {
                            require(paused(), "Pausable: not paused");
                            _;
                        }
                        /**
                         * @dev Triggers stopped state.
                         *
                         * Requirements:
                         *
                         * - The contract must not be paused.
                         */
                        function _pause() internal virtual whenNotPaused {
                            _paused = true;
                            emit Paused(_msgSender());
                        }
                        /**
                         * @dev Returns to normal state.
                         *
                         * Requirements:
                         *
                         * - The contract must be paused.
                         */
                        function _unpause() internal virtual whenPaused {
                            _paused = false;
                            emit Unpaused(_msgSender());
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    struct TokenInfo {
                        uint256 transferOverhead;
                        bool supportedToken;
                        uint256 equilibriumFee; // Percentage fee Represented in basis points
                        uint256 maxFee; // Percentage fee Represented in basis points
                        TokenConfig tokenConfig;
                    }
                    struct TokenConfig {
                        uint256 min;
                        uint256 max;
                    }
                    

                    File 9 of 9: LiquidityProviders
                    // $$\\       $$\\                     $$\\       $$\\ $$\\   $$\\                     $$$$$$$\\                                $$\\       $$\\
                    // $$ |      \\__|                    \\__|      $$ |\\__|  $$ |                    $$  __$$\\                               \\__|      $$ |
                    // $$ |      $$\\  $$$$$$\\  $$\\   $$\\ $$\\  $$$$$$$ |$$\\ $$$$$$\\   $$\\   $$\\       $$ |  $$ | $$$$$$\\   $$$$$$\\ $$\\    $$\\ $$\\  $$$$$$$ | $$$$$$\\   $$$$$$\\   $$$$$$$\\
                    // $$ |      $$ |$$  __$$\\ $$ |  $$ |$$ |$$  __$$ |$$ |\\_$$  _|  $$ |  $$ |      $$$$$$$  |$$  __$$\\ $$  __$$\\\\$$\\  $$  |$$ |$$  __$$ |$$  __$$\\ $$  __$$\\ $$  _____|
                    // $$ |      $$ |$$ /  $$ |$$ |  $$ |$$ |$$ /  $$ |$$ |  $$ |    $$ |  $$ |      $$  ____/ $$ |  \\__|$$ /  $$ |\\$$\\$$  / $$ |$$ /  $$ |$$$$$$$$ |$$ |  \\__|\\$$$$$$\\
                    // $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |$$ |  $$ |$$ |  $$ |$$\\ $$ |  $$ |      $$ |      $$ |      $$ |  $$ | \\$$$  /  $$ |$$ |  $$ |$$   ____|$$ |       \\____$$\\
                    // $$$$$$$$\\ $$ |\\$$$$$$$ |\\$$$$$$  |$$ |\\$$$$$$$ |$$ |  \\$$$$  |\\$$$$$$$ |      $$ |      $$ |      \\$$$$$$  |  \\$  /   $$ |\\$$$$$$$ |\\$$$$$$$\\ $$ |      $$$$$$$  |
                    // \\________|\\__| \\____$$ | \\______/ \\__| \\_______|\\__|   \\____/  \\____$$ |      \\__|      \\__|       \\______/    \\_/    \\__| \\_______| \\_______|\\__|      \\_______/
                    //                     $$ |                                      $$\\   $$ |
                    //                     $$ |                                      \\$$$$$$  |
                    //                     \\__|                                       \\______/
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
                    import "./metatx/ERC2771ContextUpgradeable.sol";
                    import "../security/Pausable.sol";
                    import "./interfaces/ILPToken.sol";
                    import "./interfaces/ITokenManager.sol";
                    import "./interfaces/IWhiteListPeriodManager.sol";
                    import "./interfaces/ILiquidityPool.sol";
                    contract LiquidityProviders is
                        Initializable,
                        ReentrancyGuardUpgradeable,
                        ERC2771ContextUpgradeable,
                        OwnableUpgradeable,
                        Pausable
                    {
                        using SafeERC20Upgradeable for IERC20Upgradeable;
                        address internal constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                        uint256 public constant BASE_DIVISOR = 10**18;
                        ILPToken internal lpToken;
                        ILiquidityPool internal liquidityPool;
                        ITokenManager internal tokenManager;
                        IWhiteListPeriodManager internal whiteListPeriodManager;
                        event LiquidityAdded(address indexed tokenAddress, uint256 indexed amount, address indexed lp);
                        event LiquidityRemoved(address indexed tokenAddress, uint256 indexed amount, address indexed lp);
                        event FeeClaimed(address indexed tokenAddress, uint256 indexed fee, address indexed lp, uint256 sharesBurnt);
                        event FeeAdded(address indexed tokenAddress, uint256 indexed fee);
                        event EthReceived(address indexed sender, uint256 value);
                        event CurrentLiquidityChanged(address indexed token, uint256 indexed newValue);
                        // LP Fee Distribution
                        mapping(address => uint256) public totalReserve; // Include Liquidity + Fee accumulated
                        mapping(address => uint256) public totalLiquidity; // Include Liquidity only
                        mapping(address => uint256) public currentLiquidity; // Include current liquidity, updated on every in and out transfer
                        mapping(address => uint256) public totalLPFees;
                        mapping(address => uint256) public totalSharesMinted;
                        /**
                         * @dev Modifier for checking to validate a NFTId and it's ownership
                         * @param _tokenId token id to validate
                         * @param _transactor typically msgSender(), passed to verify against owner of _tokenId
                         */
                        modifier onlyValidLpToken(uint256 _tokenId, address _transactor) {
                            (address token, , ) = lpToken.tokenMetadata(_tokenId);
                            require(lpToken.exists(_tokenId), "ERR__TOKEN_DOES_NOT_EXIST");
                            require(lpToken.ownerOf(_tokenId) == _transactor, "ERR__TRANSACTOR_DOES_NOT_OWN_NFT");
                            _;
                        }
                        /**
                         * @dev Modifier for checking if msg.sender in liquiditypool
                         */
                        modifier onlyLiquidityPool() {
                            require(_msgSender() == address(liquidityPool), "ERR__UNAUTHORIZED");
                            _;
                        }
                        modifier tokenChecks(address tokenAddress) {
                            require(tokenAddress != address(0), "Token address cannot be 0");
                            require(_isSupportedToken(tokenAddress), "Token not supported");
                            _;
                        }
                        /**
                         * @dev initalizes the contract, acts as constructor
                         * @param _trustedForwarder address of trusted forwarder
                         */
                        function initialize(
                            address _trustedForwarder,
                            address _lpToken,
                            address _tokenManager,
                            address _pauser
                        ) public initializer {
                            __ERC2771Context_init(_trustedForwarder);
                            __Ownable_init();
                            __Pausable_init(_pauser);
                            __ReentrancyGuard_init();
                            _setLPToken(_lpToken);
                            _setTokenManager(_tokenManager);
                        }
                        function _isSupportedToken(address _token) internal view returns (bool) {
                            return tokenManager.getTokensInfo(_token).supportedToken;
                        }
                        function getTotalReserveByToken(address tokenAddress) public view returns (uint256) {
                            return totalReserve[tokenAddress];
                        }
                        function getSuppliedLiquidityByToken(address tokenAddress) public view returns (uint256) {
                            return totalLiquidity[tokenAddress];
                        }
                        function getTotalLPFeeByToken(address tokenAddress) public view returns (uint256) {
                            return totalLPFees[tokenAddress];
                        }
                        function getCurrentLiquidity(address tokenAddress) public view returns (uint256) {
                            return currentLiquidity[tokenAddress];
                        }
                        /**
                         * @dev To be called post initialization, used to set address of NFT Contract
                         * @param _lpToken address of lpToken
                         */
                        function setLpToken(address _lpToken) external onlyOwner {
                            _setLPToken(_lpToken);
                        }
                        /**
                         * Internal method to set LP token contract.
                         */
                        function _setLPToken(address _lpToken) internal {
                            lpToken = ILPToken(_lpToken);
                        }
                        function increaseCurrentLiquidity(address tokenAddress, uint256 amount) public onlyLiquidityPool {
                            _increaseCurrentLiquidity(tokenAddress, amount);
                        }
                        function decreaseCurrentLiquidity(address tokenAddress, uint256 amount) public onlyLiquidityPool {
                            _decreaseCurrentLiquidity(tokenAddress, amount);
                        }
                        function _increaseCurrentLiquidity(address tokenAddress, uint256 amount) private {
                            currentLiquidity[tokenAddress] += amount;
                            emit CurrentLiquidityChanged(tokenAddress, currentLiquidity[tokenAddress]);
                        }
                        function _decreaseCurrentLiquidity(address tokenAddress, uint256 amount) private {
                            currentLiquidity[tokenAddress] -= amount;
                            emit CurrentLiquidityChanged(tokenAddress, currentLiquidity[tokenAddress]);
                        }
                        /**
                         * Public method to set TokenManager contract.
                         */
                        function setTokenManager(address _tokenManager) external onlyOwner {
                            _setTokenManager(_tokenManager);
                        }
                        /**
                         * Internal method to set TokenManager contract.
                         */
                        function _setTokenManager(address _tokenManager) internal {
                            tokenManager = ITokenManager(_tokenManager);
                        }
                        function setTrustedForwarder(address _tf) external onlyOwner {
                            _setTrustedForwarder(_tf);
                        }
                        /**
                         * @dev To be called post initialization, used to set address of WhiteListPeriodManager Contract
                         * @param _whiteListPeriodManager address of WhiteListPeriodManager
                         */
                        function setWhiteListPeriodManager(address _whiteListPeriodManager) external onlyOwner {
                            whiteListPeriodManager = IWhiteListPeriodManager(_whiteListPeriodManager);
                        }
                        /**
                         * @dev To be called post initialization, used to set address of LiquidityPool Contract
                         * @param _liquidityPool address of LiquidityPool
                         */
                        function setLiquidityPool(address _liquidityPool) external onlyOwner {
                            liquidityPool = ILiquidityPool(_liquidityPool);
                        }
                        /**
                         * @dev Returns price of Base token in terms of LP Shares
                         * @param _baseToken address of baseToken
                         * @return Price of Base token in terms of LP Shares
                         */
                        function getTokenPriceInLPShares(address _baseToken) public view returns (uint256) {
                            uint256 reserve = totalReserve[_baseToken];
                            if (reserve > 0) {
                                return totalSharesMinted[_baseToken] / totalReserve[_baseToken];
                            }
                            return BASE_DIVISOR;
                        }
                        /**
                         * @dev Converts shares to token amount
                         */
                        function sharesToTokenAmount(uint256 _shares, address _tokenAddress) public view returns (uint256) {
                            return (_shares * totalReserve[_tokenAddress]) / totalSharesMinted[_tokenAddress];
                        }
                        /**
                         * @dev Returns the fee accumulated on a given NFT
                         * @param _nftId Id of NFT
                         * @return accumulated fee
                         */
                        function getFeeAccumulatedOnNft(uint256 _nftId) public view returns (uint256) {
                            require(lpToken.exists(_nftId), "ERR__INVALID_NFT");
                            (address _tokenAddress, uint256 nftSuppliedLiquidity, uint256 totalNFTShares) = lpToken.tokenMetadata(_nftId);
                            if (totalNFTShares == 0) {
                                return 0;
                            }
                            // Calculate rewards accumulated
                            uint256 eligibleLiquidity = sharesToTokenAmount(totalNFTShares, _tokenAddress);
                            uint256 lpFeeAccumulated;
                            // Handle edge cases where eligibleLiquidity is less than what was supplied by very small amount
                            if (nftSuppliedLiquidity > eligibleLiquidity) {
                                lpFeeAccumulated = 0;
                            } else {
                                unchecked {
                                    lpFeeAccumulated = eligibleLiquidity - nftSuppliedLiquidity;
                                }
                            }
                            return lpFeeAccumulated;
                        }
                        /**
                         * @dev Records fee being added to total reserve
                         * @param _token Address of Token for which LP fee is being added
                         * @param _amount Amount being added
                         */
                        function addLPFee(address _token, uint256 _amount) external onlyLiquidityPool tokenChecks(_token) whenNotPaused {
                            totalReserve[_token] += _amount;
                            totalLPFees[_token] += _amount;
                            emit FeeAdded(_token, _amount);
                        }
                        /**
                         * @dev Internal function to add liquidity to a new NFT
                         */
                        function _addLiquidity(address _token, uint256 _amount) internal {
                            require(_amount > 0, "ERR__AMOUNT_IS_0");
                            uint256 nftId = lpToken.mint(_msgSender());
                            LpTokenMetadata memory data = LpTokenMetadata(_token, 0, 0);
                            lpToken.updateTokenMetadata(nftId, data);
                            _increaseLiquidity(nftId, _amount);
                        }
                        /**
                         * @dev Function to mint a new NFT for a user, add native liquidity and store the
                         *      record in the newly minted NFT
                         */
                        function addNativeLiquidity() external payable nonReentrant tokenChecks(NATIVE) whenNotPaused {
                            require(address(liquidityPool) != address(0), "ERR__LIQUIDITY_POOL_NOT_SET");
                            (bool success, ) = address(liquidityPool).call{value: msg.value}("");
                            require(success, "ERR__NATIVE_TRANSFER_FAILED");
                            _addLiquidity(NATIVE, msg.value);
                        }
                        /**
                         * @dev Function to mint a new NFT for a user, add token liquidity and store the
                         *      record in the newly minted NFT
                         * @param _token Address of token for which liquidity is to be added
                         * @param _amount Amount of liquidity added
                         */
                        function addTokenLiquidity(address _token, uint256 _amount)
                            external
                            nonReentrant
                            tokenChecks(_token)
                            whenNotPaused
                        {
                            require(_token != NATIVE, "ERR__WRONG_FUNCTION");
                            require(
                                IERC20Upgradeable(_token).allowance(_msgSender(), address(this)) >= _amount,
                                "ERR__INSUFFICIENT_ALLOWANCE"
                            );
                            SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(_token), _msgSender(), address(liquidityPool), _amount);
                            _addLiquidity(_token, _amount);
                        }
                        /**
                         * @dev Internal helper function to increase liquidity in a given NFT
                         */
                        function _increaseLiquidity(uint256 _nftId, uint256 _amount) internal onlyValidLpToken(_nftId, _msgSender()) {
                            (address token, uint256 totalSuppliedLiquidity, uint256 totalShares) = lpToken.tokenMetadata(_nftId);
                            require(_amount != 0, "ERR__AMOUNT_IS_0");
                            whiteListPeriodManager.beforeLiquidityAddition(_msgSender(), token, _amount);
                            uint256 mintedSharesAmount;
                            // Adding liquidity in the pool for the first time
                            if (totalReserve[token] == 0 || totalSharesMinted[token] == 0) {
                                mintedSharesAmount = BASE_DIVISOR * _amount;
                            } else {
                                mintedSharesAmount = (_amount * totalSharesMinted[token]) / totalReserve[token];
                            }
                            require(mintedSharesAmount >= BASE_DIVISOR, "ERR__AMOUNT_BELOW_MIN_LIQUIDITY");
                            totalLiquidity[token] += _amount;
                            totalReserve[token] += _amount;
                            totalSharesMinted[token] += mintedSharesAmount;
                            LpTokenMetadata memory data = LpTokenMetadata(
                                token,
                                totalSuppliedLiquidity + _amount,
                                totalShares + mintedSharesAmount
                            );
                            lpToken.updateTokenMetadata(_nftId, data);
                            // Increase the current liquidity
                            _increaseCurrentLiquidity(token, _amount);
                            emit LiquidityAdded(token, _amount, _msgSender());
                        }
                        /**
                         * @dev Function to allow LPs to add ERC20 token liquidity to existing NFT
                         * @param _nftId ID of NFT for updating the balances
                         * @param _amount Token amount to be added
                         */
                        function increaseTokenLiquidity(uint256 _nftId, uint256 _amount) external nonReentrant whenNotPaused {
                            (address token, , ) = lpToken.tokenMetadata(_nftId);
                            require(_isSupportedToken(token), "ERR__TOKEN_NOT_SUPPORTED");
                            require(token != NATIVE, "ERR__WRONG_FUNCTION");
                            require(
                                IERC20Upgradeable(token).allowance(_msgSender(), address(this)) >= _amount,
                                "ERR__INSUFFICIENT_ALLOWANCE"
                            );
                            SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(token), _msgSender(), address(liquidityPool), _amount);
                            _increaseLiquidity(_nftId, _amount);
                        }
                        /**
                         * @dev Function to allow LPs to add native token liquidity to existing NFT
                         */
                        function increaseNativeLiquidity(uint256 _nftId) external payable nonReentrant whenNotPaused {
                            (address token, , ) = lpToken.tokenMetadata(_nftId);
                            require(_isSupportedToken(NATIVE), "ERR__TOKEN_NOT_SUPPORTED");
                            require(token == NATIVE, "ERR__WRONG_FUNCTION");
                            require(address(liquidityPool) != address(0), "ERR__LIQUIDITY_POOL_NOT_SET");
                            (bool success, ) = address(liquidityPool).call{value: msg.value}("");
                            require(success, "ERR__NATIVE_TRANSFER_FAILED");
                            _increaseLiquidity(_nftId, msg.value);
                        }
                        /**
                         * @dev Function to allow LPs to remove their liquidity from an existing NFT
                         *      Also automatically redeems any earned fee
                         */
                        function removeLiquidity(uint256 _nftId, uint256 _amount)
                            external
                            nonReentrant
                            onlyValidLpToken(_nftId, _msgSender())
                            whenNotPaused
                        {
                            (address _tokenAddress, uint256 nftSuppliedLiquidity, uint256 totalNFTShares) = lpToken.tokenMetadata(_nftId);
                            require(_isSupportedToken(_tokenAddress), "ERR__TOKEN_NOT_SUPPORTED");
                            require(_amount != 0, "ERR__INVALID_AMOUNT");
                            require(nftSuppliedLiquidity >= _amount, "ERR__INSUFFICIENT_LIQUIDITY");
                            whiteListPeriodManager.beforeLiquidityRemoval(_msgSender(), _tokenAddress, _amount);
                            // Calculate how much shares represent input amount
                            uint256 lpSharesForInputAmount = _amount * getTokenPriceInLPShares(_tokenAddress);
                            // Calculate rewards accumulated
                            uint256 eligibleLiquidity = sharesToTokenAmount(totalNFTShares, _tokenAddress);
                            uint256 lpFeeAccumulated;
                            // Handle edge cases where eligibleLiquidity is less than what was supplied by very small amount
                            if (nftSuppliedLiquidity > eligibleLiquidity) {
                                lpFeeAccumulated = 0;
                            } else {
                                unchecked {
                                    lpFeeAccumulated = eligibleLiquidity - nftSuppliedLiquidity;
                                }
                            }
                            // Calculate amount of lp shares that represent accumulated Fee
                            uint256 lpSharesRepresentingFee = lpFeeAccumulated * getTokenPriceInLPShares(_tokenAddress);
                            totalLPFees[_tokenAddress] -= lpFeeAccumulated;
                            uint256 amountToWithdraw = _amount + lpFeeAccumulated;
                            uint256 lpSharesToBurn = lpSharesForInputAmount + lpSharesRepresentingFee;
                            // Handle round off errors to avoid dust lp token in contract
                            if (totalNFTShares - lpSharesToBurn < BASE_DIVISOR) {
                                lpSharesToBurn = totalNFTShares;
                            }
                            totalReserve[_tokenAddress] -= amountToWithdraw;
                            totalLiquidity[_tokenAddress] -= _amount;
                            totalSharesMinted[_tokenAddress] -= lpSharesToBurn;
                            _decreaseCurrentLiquidity(_tokenAddress, _amount);
                            _burnSharesFromNft(_nftId, lpSharesToBurn, _amount, _tokenAddress);
                            _transferFromLiquidityPool(_tokenAddress, _msgSender(), amountToWithdraw);
                            emit LiquidityRemoved(_tokenAddress, amountToWithdraw, _msgSender());
                        }
                        /**
                         * @dev Function to allow LPs to claim the fee earned on their NFT
                         * @param _nftId ID of NFT where liquidity is recorded
                         */
                        function claimFee(uint256 _nftId) external onlyValidLpToken(_nftId, _msgSender()) whenNotPaused nonReentrant {
                            (address _tokenAddress, uint256 nftSuppliedLiquidity, uint256 totalNFTShares) = lpToken.tokenMetadata(_nftId);
                            require(_isSupportedToken(_tokenAddress), "ERR__TOKEN_NOT_SUPPORTED");
                            uint256 lpSharesForSuppliedLiquidity = nftSuppliedLiquidity * getTokenPriceInLPShares(_tokenAddress);
                            // Calculate rewards accumulated
                            uint256 eligibleLiquidity = sharesToTokenAmount(totalNFTShares, _tokenAddress);
                            uint256 lpFeeAccumulated = eligibleLiquidity - nftSuppliedLiquidity;
                            require(lpFeeAccumulated > 0, "ERR__NO_REWARDS_TO_CLAIM");
                            // Calculate amount of lp shares that represent accumulated Fee
                            uint256 lpSharesRepresentingFee = totalNFTShares - lpSharesForSuppliedLiquidity;
                            totalReserve[_tokenAddress] -= lpFeeAccumulated;
                            totalSharesMinted[_tokenAddress] -= lpSharesRepresentingFee;
                            totalLPFees[_tokenAddress] -= lpFeeAccumulated;
                            _burnSharesFromNft(_nftId, lpSharesRepresentingFee, 0, _tokenAddress);
                            _transferFromLiquidityPool(_tokenAddress, _msgSender(), lpFeeAccumulated);
                            emit FeeClaimed(_tokenAddress, lpFeeAccumulated, _msgSender(), lpSharesRepresentingFee);
                        }
                        /**
                         * @dev Internal Function to burn LP shares and remove liquidity from existing NFT
                         */
                        function _burnSharesFromNft(
                            uint256 _nftId,
                            uint256 _shares,
                            uint256 _tokenAmount,
                            address _tokenAddress
                        ) internal {
                            (, uint256 nftSuppliedLiquidity, uint256 nftShares) = lpToken.tokenMetadata(_nftId);
                            nftShares -= _shares;
                            nftSuppliedLiquidity -= _tokenAmount;
                            lpToken.updateTokenMetadata(_nftId, LpTokenMetadata(_tokenAddress, nftSuppliedLiquidity, nftShares));
                        }
                        function _transferFromLiquidityPool(
                            address _tokenAddress,
                            address _receiver,
                            uint256 _tokenAmount
                        ) internal {
                            liquidityPool.transfer(_tokenAddress, _receiver, _tokenAmount);
                        }
                        function getSuppliedLiquidity(uint256 _nftId) external view returns (uint256) {
                            (, uint256 totalSuppliedLiquidity, ) = lpToken.tokenMetadata(_nftId);
                            return totalSuppliedLiquidity;
                        }
                        function _msgSender()
                            internal
                            view
                            virtual
                            override(ContextUpgradeable, ERC2771ContextUpgradeable)
                            returns (address sender)
                        {
                            return ERC2771ContextUpgradeable._msgSender();
                        }
                        function _msgData()
                            internal
                            view
                            virtual
                            override(ContextUpgradeable, ERC2771ContextUpgradeable)
                            returns (bytes calldata)
                        {
                            return ERC2771ContextUpgradeable._msgData();
                        }
                        receive() external payable {
                            emit EthReceived(_msgSender(), msg.value);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @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 a proxied contract can't have 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.
                     *
                     * 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.
                     */
                    abstract contract Initializable {
                        /**
                         * @dev Indicates that the contract has been initialized.
                         */
                        bool private _initialized;
                        /**
                         * @dev Indicates that the contract is in the process of being initialized.
                         */
                        bool private _initializing;
                        /**
                         * @dev Modifier to protect an initializer function from being invoked twice.
                         */
                        modifier initializer() {
                            require(_initializing || !_initialized, "Initializable: contract is already initialized");
                            bool isTopLevelCall = !_initializing;
                            if (isTopLevelCall) {
                                _initializing = true;
                                _initialized = true;
                            }
                            _;
                            if (isTopLevelCall) {
                                _initializing = false;
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module that helps prevent reentrant calls to a function.
                     *
                     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
                     * available, which can be applied to functions to make sure there are no nested
                     * (reentrant) calls to them.
                     *
                     * Note that because there is a single `nonReentrant` guard, functions marked as
                     * `nonReentrant` may not call one another. This can be worked around by making
                     * those functions `private`, and then adding `external` `nonReentrant` entry
                     * points to them.
                     *
                     * TIP: If you would like to learn more about reentrancy and alternative ways
                     * to protect against it, check out our blog post
                     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
                     */
                    abstract contract ReentrancyGuardUpgradeable is Initializable {
                        // Booleans are more expensive than uint256 or any type that takes up a full
                        // word because each write operation emits an extra SLOAD to first read the
                        // slot's contents, replace the bits taken up by the boolean, and then write
                        // back. This is the compiler's defense against contract upgrades and
                        // pointer aliasing, and it cannot be disabled.
                        // The values being non-zero value makes deployment a bit more expensive,
                        // but in exchange the refund on every call to nonReentrant will be lower in
                        // amount. Since refunds are capped to a percentage of the total
                        // transaction's gas, it is best to keep them low in cases like this one, to
                        // increase the likelihood of the full refund coming into effect.
                        uint256 private constant _NOT_ENTERED = 1;
                        uint256 private constant _ENTERED = 2;
                        uint256 private _status;
                        function __ReentrancyGuard_init() internal initializer {
                            __ReentrancyGuard_init_unchained();
                        }
                        function __ReentrancyGuard_init_unchained() internal initializer {
                            _status = _NOT_ENTERED;
                        }
                        /**
                         * @dev Prevents a contract from calling itself, directly or indirectly.
                         * Calling a `nonReentrant` function from another `nonReentrant`
                         * function is not supported. It is possible to prevent this from happening
                         * by making the `nonReentrant` function external, and make it call a
                         * `private` function that does the actual work.
                         */
                        modifier nonReentrant() {
                            // On the first call to nonReentrant, _notEntered will be true
                            require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                            // Any calls to nonReentrant after this point will fail
                            _status = _ENTERED;
                            _;
                            // By storing the original value once again, a refund is triggered (see
                            // https://eips.ethereum.org/EIPS/eip-2200)
                            _status = _NOT_ENTERED;
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/ContextUpgradeable.sol";
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * By default, the owner account will be the one that deploys the contract. This
                     * can later be changed with {transferOwnership}.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
                        address private _owner;
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        function __Ownable_init() internal initializer {
                            __Context_init_unchained();
                            __Ownable_init_unchained();
                        }
                        function __Ownable_init_unchained() internal initializer {
                            _setOwner(_msgSender());
                        }
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view virtual returns (address) {
                            return _owner;
                        }
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(owner() == _msgSender(), "Ownable: caller is not the owner");
                            _;
                        }
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public virtual onlyOwner {
                            _setOwner(address(0));
                        }
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public virtual onlyOwner {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            _setOwner(newOwner);
                        }
                        function _setOwner(address newOwner) private {
                            address oldOwner = _owner;
                            _owner = newOwner;
                            emit OwnershipTransferred(oldOwner, newOwner);
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP.
                     */
                    interface IERC20Upgradeable {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(
                            address sender,
                            address recipient,
                            uint256 amount
                        ) external returns (bool);
                        /**
                         * @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);
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../IERC20Upgradeable.sol";
                    import "../../../utils/AddressUpgradeable.sol";
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 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 SafeERC20Upgradeable {
                        using AddressUpgradeable for address;
                        function safeTransfer(
                            IERC20Upgradeable token,
                            address to,
                            uint256 value
                        ) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                        function safeTransferFrom(
                            IERC20Upgradeable token,
                            address from,
                            address to,
                            uint256 value
                        ) internal {
                            _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                        /**
                         * @dev Deprecated. This function has issues similar to the ones found in
                         * {IERC20-approve}, and its usage is discouraged.
                         *
                         * Whenever possible, use {safeIncreaseAllowance} and
                         * {safeDecreaseAllowance} instead.
                         */
                        function safeApprove(
                            IERC20Upgradeable token,
                            address spender,
                            uint256 value
                        ) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            require(
                                (value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                        function safeIncreaseAllowance(
                            IERC20Upgradeable token,
                            address spender,
                            uint256 value
                        ) internal {
                            uint256 newAllowance = token.allowance(address(this), spender) + value;
                            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        function safeDecreaseAllowance(
                            IERC20Upgradeable token,
                            address spender,
                            uint256 value
                        ) internal {
                            unchecked {
                                uint256 oldAllowance = token.allowance(address(this), spender);
                                require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                                uint256 newAllowance = oldAllowance - value;
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        }
                        /**
                         * @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).
                         */
                        function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                            // the target address contains contract code and also asserts for success in the low-level call.
                            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                            if (returndata.length > 0) {
                                // Return data is optional
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    /**
                     * @dev Context variant with ERC2771 support.
                     * Here _trustedForwarder is made internal instead of private
                     * so it can be changed via Child contracts with a setter method.
                     */
                    abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable {
                        event TrustedForwarderChanged(address indexed _tf);
                        address internal _trustedForwarder;
                        function __ERC2771Context_init(address trustedForwarder) internal initializer {
                            __Context_init_unchained();
                            __ERC2771Context_init_unchained(trustedForwarder);
                        }
                        function __ERC2771Context_init_unchained(address trustedForwarder) internal initializer {
                            _trustedForwarder = trustedForwarder;
                        }
                        function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
                            return forwarder == _trustedForwarder;
                        }
                        function _msgSender() internal view virtual override returns (address sender) {
                            if (isTrustedForwarder(msg.sender)) {
                                // The assembly code is more direct than the Solidity version using `abi.decode`.
                                assembly {
                                    sender := shr(96, calldataload(sub(calldatasize(), 20)))
                                }
                            } else {
                                return super._msgSender();
                            }
                        }
                        function _msgData() internal view virtual override returns (bytes calldata) {
                            if (isTrustedForwarder(msg.sender)) {
                                return msg.data[:msg.data.length - 20];
                            } else {
                                return super._msgData();
                            }
                        }
                        function _setTrustedForwarder(address _tf) internal virtual {
                            require(_tf != address(0), "TrustedForwarder can't be 0");
                            _trustedForwarder = _tf;
                            emit TrustedForwarderChanged(_tf);
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                    import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
                    /**
                     * @dev Contract module which allows children to implement an emergency stop
                     * mechanism that can be triggered by an authorized account.
                     *
                     * This module is used through inheritance. It will make available the
                     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
                     * the functions of your contract. Note that they will not be pausable by
                     * simply including this module, only once the modifiers are put in place.
                     */
                    abstract contract Pausable is Initializable, PausableUpgradeable {
                        address private _pauser;
                        event PauserChanged(address indexed previousPauser, address indexed newPauser);
                        /**
                         * @dev The pausable constructor sets the original `pauser` of the contract to the sender
                         * account & Initializes the contract in unpaused state..
                         */
                        function __Pausable_init(address pauser) internal initializer {
                            require(pauser != address(0), "Pauser Address cannot be 0");
                            __Pausable_init();
                            _pauser = pauser;
                        }
                        /**
                         * @return true if `msg.sender` is the owner of the contract.
                         */
                        function isPauser(address pauser) public view returns (bool) {
                            return pauser == _pauser;
                        }
                        /**
                         * @dev Throws if called by any account other than the pauser.
                         */
                        modifier onlyPauser() {
                            require(isPauser(msg.sender), "Only pauser is allowed to perform this operation");
                            _;
                        }
                        /**
                         * @dev Allows the current pauser to transfer control of the contract to a newPauser.
                         * @param newPauser The address to transfer pauserShip to.
                         */
                        function changePauser(address newPauser) public onlyPauser whenNotPaused {
                            _changePauser(newPauser);
                        }
                        /**
                         * @dev Transfers control of the contract to a newPauser.
                         * @param newPauser The address to transfer ownership to.
                         */
                        function _changePauser(address newPauser) internal {
                            require(newPauser != address(0));
                            emit PauserChanged(_pauser, newPauser);
                            _pauser = newPauser;
                        }
                        function renouncePauser() external virtual onlyPauser whenNotPaused {
                            emit PauserChanged(_pauser, address(0));
                            _pauser = address(0);
                        }
                        function pause() public onlyPauser {
                            _pause();
                        }
                        function unpause() public onlyPauser {
                            _unpause();
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "../structures/LpTokenMetadata.sol";
                    interface ILPToken {
                        function approve(address to, uint256 tokenId) external;
                        function balanceOf(address _owner) external view returns (uint256);
                        function exists(uint256 _tokenId) external view returns (bool);
                        function getAllNftIdsByUser(address _owner) external view returns (uint256[] memory);
                        function getApproved(uint256 tokenId) external view returns (address);
                        function initialize(
                            string memory _name,
                            string memory _symbol,
                            address _trustedForwarder
                        ) external;
                        function isApprovedForAll(address _owner, address operator) external view returns (bool);
                        function isTrustedForwarder(address forwarder) external view returns (bool);
                        function liquidityPoolAddress() external view returns (address);
                        function mint(address _to) external returns (uint256);
                        function name() external view returns (string memory);
                        function owner() external view returns (address);
                        function ownerOf(uint256 tokenId) external view returns (address);
                        function paused() external view returns (bool);
                        function renounceOwnership() external;
                        function safeTransferFrom(
                            address from,
                            address to,
                            uint256 tokenId
                        ) external;
                        function safeTransferFrom(
                            address from,
                            address to,
                            uint256 tokenId,
                            bytes memory _data
                        ) external;
                        function setApprovalForAll(address operator, bool approved) external;
                        function setLiquidityPool(address _lpm) external;
                        function setWhiteListPeriodManager(address _whiteListPeriodManager) external;
                        function supportsInterface(bytes4 interfaceId) external view returns (bool);
                        function symbol() external view returns (string memory);
                        function tokenByIndex(uint256 index) external view returns (uint256);
                        function tokenMetadata(uint256)
                            external
                            view
                            returns (
                                address token,
                                uint256 totalSuppliedLiquidity,
                                uint256 totalShares
                            );
                        function tokenOfOwnerByIndex(address _owner, uint256 index) external view returns (uint256);
                        function tokenURI(uint256 tokenId) external view returns (string memory);
                        function totalSupply() external view returns (uint256);
                        function transferFrom(
                            address from,
                            address to,
                            uint256 tokenId
                        ) external;
                        function transferOwnership(address newOwner) external;
                        function updateTokenMetadata(uint256 _tokenId, LpTokenMetadata memory _lpTokenMetadata) external;
                        function whiteListPeriodManager() external view returns (address);
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    import "../structures/TokenConfig.sol";
                    interface ITokenManager {
                        function getEquilibriumFee(address tokenAddress) external view returns (uint256);
                        function getMaxFee(address tokenAddress) external view returns (uint256);
                        function changeFee(
                            address tokenAddress,
                            uint256 _equilibriumFee,
                            uint256 _maxFee
                        ) external;
                        function tokensInfo(address tokenAddress)
                            external
                            view
                            returns (
                                uint256 transferOverhead,
                                bool supportedToken,
                                uint256 equilibriumFee,
                                uint256 maxFee,
                                TokenConfig memory config
                            );
                        function excessStateTransferFeePerc(address tokenAddress) external view returns (uint256);
                        function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory);
                        function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory);
                        function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory);
                        function changeExcessStateFee(address _tokenAddress, uint256 _excessStateFeePer) external;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    interface IWhiteListPeriodManager {
                        function areWhiteListRestrictionsEnabled() external view returns (bool);
                        function beforeLiquidityAddition(
                            address _lp,
                            address _token,
                            uint256 _amount
                        ) external;
                        function beforeLiquidityRemoval(
                            address _lp,
                            address _token,
                            uint256 _amount
                        ) external;
                        function beforeLiquidityTransfer(
                            address _from,
                            address _to,
                            address _token,
                            uint256 _amount
                        ) external;
                        function getMaxCommunityLpPositon(address _token) external view returns (uint256);
                        function initialize(
                            address _trustedForwarder,
                            address _liquidityProviders,
                            address _tokenManager
                        ) external;
                        function isExcludedAddress(address) external view returns (bool);
                        function isTrustedForwarder(address forwarder) external view returns (bool);
                        function owner() external view returns (address);
                        function paused() external view returns (bool);
                        function perTokenTotalCap(address) external view returns (uint256);
                        function perTokenWalletCap(address) external view returns (uint256);
                        function renounceOwnership() external;
                        function setAreWhiteListRestrictionsEnabled(bool _status) external;
                        function setCap(
                            address _token,
                            uint256 _totalCap,
                            uint256 _perTokenWalletCap
                        ) external;
                        function setCaps(
                            address[] memory _tokens,
                            uint256[] memory _totalCaps,
                            uint256[] memory _perTokenWalletCaps
                        ) external;
                        function setIsExcludedAddressStatus(address[] memory _addresses, bool[] memory _status) external;
                        function setLiquidityProviders(address _liquidityProviders) external;
                        function setPerTokenWalletCap(address _token, uint256 _perTokenWalletCap) external;
                        function setTokenManager(address _tokenManager) external;
                        function setTotalCap(address _token, uint256 _totalCap) external;
                        function transferOwnership(address newOwner) external;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    interface ILiquidityPool {
                        function baseGas() external view returns (uint256);
                        function changePauser(address newPauser) external;
                        function checkHashStatus(
                            address tokenAddress,
                            uint256 amount,
                            address receiver,
                            bytes memory depositHash
                        ) external view returns (bytes32 hashSendTransaction, bool status);
                        function depositConfig(uint256, address) external view returns (uint256 min, uint256 max);
                        function depositErc20(
                            uint256 toChainId,
                            address tokenAddress,
                            address receiver,
                            uint256 amount,
                            string memory tag
                        ) external;
                        function depositNative(
                            address receiver,
                            uint256 toChainId,
                            string memory tag
                        ) external;
                        function gasFeeAccumulated(address, address) external view returns (uint256);
                        function gasFeeAccumulatedByToken(address) external view returns (uint256);
                        function getCurrentLiquidity(address tokenAddress) external view returns (uint256 currentLiquidity);
                        function getExecutorManager() external view returns (address);
                        function getRewardAmount(uint256 amount, address tokenAddress) external view returns (uint256 rewardAmount);
                        function getTransferFee(address tokenAddress, uint256 amount) external view returns (uint256 fee);
                        function incentivePool(address) external view returns (uint256);
                        function initialize(
                            address _executorManagerAddress,
                            address pauser,
                            address _trustedForwarder,
                            address _tokenManager,
                            address _liquidityProviders
                        ) external;
                        function isPauser(address pauser) external view returns (bool);
                        function isTrustedForwarder(address forwarder) external view returns (bool);
                        function owner() external view returns (address);
                        function paused() external view returns (bool);
                        function processedHash(bytes32) external view returns (bool);
                        function renounceOwnership() external;
                        function renouncePauser() external;
                        function transfer(address _tokenAddress, address receiver, uint256 _tokenAmount) external;
                        function sendFundsToUser(
                            address tokenAddress,
                            uint256 amount,
                            address receiver,
                            bytes memory depositHash,
                            uint256 tokenGasPrice,
                            uint256 fromChainId
                        ) external;
                        function setBaseGas(uint128 gas) external;
                        function setExecutorManager(address _executorManagerAddress) external;
                        function setLiquidityProviders(address _liquidityProviders) external;
                        function setTrustedForwarder(address trustedForwarder) external;
                        function transferConfig(address) external view returns (uint256 min, uint256 max);
                        function transferOwnership(address newOwner) external;
                        function withdrawErc20GasFee(address tokenAddress) external;
                        function withdrawNativeGasFee() external;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    abstract contract ContextUpgradeable is Initializable {
                        function __Context_init() internal initializer {
                            __Context_init_unchained();
                        }
                        function __Context_init_unchained() internal initializer {
                        }
                        function _msgSender() internal view virtual returns (address) {
                            return msg.sender;
                        }
                        function _msgData() internal view virtual returns (bytes calldata) {
                            return msg.data;
                        }
                        uint256[50] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library AddressUpgradeable {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies on extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                            uint256 size;
                            assembly {
                                size := extcodesize(account)
                            }
                            return size > 0;
                        }
                        /**
                         * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            (bool success, ) = recipient.call{value: amount}("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                        /**
                         * @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, it is bubbled up by this
                         * function (like regular Solidity function calls).
                         *
                         * 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.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                            return functionCall(target, data, "Address: low-level call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                         * `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCall(
                            address target,
                            bytes memory data,
                            string memory errorMessage
                        ) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, 0, errorMessage);
                        }
                        /**
                         * @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`.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(
                            address target,
                            bytes memory data,
                            uint256 value
                        ) internal returns (bytes memory) {
                            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                         * with `errorMessage` as a fallback revert reason when `target` reverts.
                         *
                         * _Available since v3.1._
                         */
                        function functionCallWithValue(
                            address target,
                            bytes memory data,
                            uint256 value,
                            string memory errorMessage
                        ) internal returns (bytes memory) {
                            require(address(this).balance >= value, "Address: insufficient balance for call");
                            require(isContract(target), "Address: call to non-contract");
                            (bool success, bytes memory returndata) = target.call{value: value}(data);
                            return verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                            return functionStaticCall(target, data, "Address: low-level static call failed");
                        }
                        /**
                         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                         * but performing a static call.
                         *
                         * _Available since v3.3._
                         */
                        function functionStaticCall(
                            address target,
                            bytes memory data,
                            string memory errorMessage
                        ) internal view returns (bytes memory) {
                            require(isContract(target), "Address: static call to non-contract");
                            (bool success, bytes memory returndata) = target.staticcall(data);
                            return verifyCallResult(success, returndata, errorMessage);
                        }
                        /**
                         * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
                         * revert reason using the provided one.
                         *
                         * _Available since v4.3._
                         */
                        function verifyCallResult(
                            bool success,
                            bytes memory returndata,
                            string memory errorMessage
                        ) internal pure returns (bytes memory) {
                            if (success) {
                                return returndata;
                            } else {
                                // 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 {
                                        let returndata_size := mload(returndata)
                                        revert(add(32, returndata), returndata_size)
                                    }
                                } else {
                                    revert(errorMessage);
                                }
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity ^0.8.0;
                    import "../utils/ContextUpgradeable.sol";
                    import "../proxy/utils/Initializable.sol";
                    /**
                     * @dev Contract module which allows children to implement an emergency stop
                     * mechanism that can be triggered by an authorized account.
                     *
                     * This module is used through inheritance. It will make available the
                     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
                     * the functions of your contract. Note that they will not be pausable by
                     * simply including this module, only once the modifiers are put in place.
                     */
                    abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
                        /**
                         * @dev Emitted when the pause is triggered by `account`.
                         */
                        event Paused(address account);
                        /**
                         * @dev Emitted when the pause is lifted by `account`.
                         */
                        event Unpaused(address account);
                        bool private _paused;
                        /**
                         * @dev Initializes the contract in unpaused state.
                         */
                        function __Pausable_init() internal initializer {
                            __Context_init_unchained();
                            __Pausable_init_unchained();
                        }
                        function __Pausable_init_unchained() internal initializer {
                            _paused = false;
                        }
                        /**
                         * @dev Returns true if the contract is paused, and false otherwise.
                         */
                        function paused() public view virtual returns (bool) {
                            return _paused;
                        }
                        /**
                         * @dev Modifier to make a function callable only when the contract is not paused.
                         *
                         * Requirements:
                         *
                         * - The contract must not be paused.
                         */
                        modifier whenNotPaused() {
                            require(!paused(), "Pausable: paused");
                            _;
                        }
                        /**
                         * @dev Modifier to make a function callable only when the contract is paused.
                         *
                         * Requirements:
                         *
                         * - The contract must be paused.
                         */
                        modifier whenPaused() {
                            require(paused(), "Pausable: not paused");
                            _;
                        }
                        /**
                         * @dev Triggers stopped state.
                         *
                         * Requirements:
                         *
                         * - The contract must not be paused.
                         */
                        function _pause() internal virtual whenNotPaused {
                            _paused = true;
                            emit Paused(_msgSender());
                        }
                        /**
                         * @dev Returns to normal state.
                         *
                         * Requirements:
                         *
                         * - The contract must be paused.
                         */
                        function _unpause() internal virtual whenPaused {
                            _paused = false;
                            emit Unpaused(_msgSender());
                        }
                        uint256[49] private __gap;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    struct LpTokenMetadata {
                        address token;
                        uint256 suppliedLiquidity;
                        uint256 shares;
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.0;
                    struct TokenInfo {
                        uint256 transferOverhead;
                        bool supportedToken;
                        uint256 equilibriumFee; // Percentage fee Represented in basis points
                        uint256 maxFee; // Percentage fee Represented in basis points
                        TokenConfig tokenConfig;
                    }
                    struct TokenConfig {
                        uint256 min;
                        uint256 max;
                    }