// File: final_etl_staking.sol




/*
"SPDX-License-Identifier: UNLICENSED"
*/
pragma solidity ^0.6.6;


import "./uniswap_interface.sol";
import "./safemath_contract.sol";

contract EtherealStaking 
{
    IERC20 usdt_token;
    IERC20 etl_token;

    using SafeMathX for uint256;
    address private marketingAddress;
    address private Default_Referral;
    address private liquidityAddress;
    address private pairedContract;
    address owner ;

    uint256 constant private PERCENTS_DIVIDER = 10000;
    uint256 constant private USDTMULT = 1000000;
    uint256 constant private ETLTMULT = 100000000;
    uint16[] private REFERRAL_PERCENTS = [0,300,200,100,50,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25];
    uint16[] private STAKE_TREAM = [200,400,600];
    uint16[] private STAKE_PER = [2600,3000,3200];
    uint16[] private DIRECT_PER = [800,1100,1500];
    uint256 private DaysSend=86400;
    uint16[5] private self_stake=[200,400,800,1200,1600];
    uint16[5] private direct_stake=[1000,2000,4000,6000,8000];
    uint24[5] private team_stake=[10000,20000,40000,60000,80000];
    uint256[5] private RankCounts=[0,0,0,0,0];
    uint256[5] private StakBonusx=[0,0,0,0,0];
    uint256 private ETLCurRate;
    uint256 private Total_ETL_Staking;
    uint256 private Total_USDT_Staking;
    uint256 private Total_Users;
    uint256 private Total_ETL_Supply;
    uint256 private Total_ETL_Balance;
    uint256 private USDT35RECIVED;
    uint256 private TUSDT_Deposit;

    uint256 private Total_ETL_Withdrawal;
    uint256 private Total_USDT_Withdrawal;
    uint256 private ReservedFund =0;
    uint256 private ReservedCycle =0;
    address [] private Global_Leaders;
    address [] private Team_Leaders;

    uint256 private TETLPS=0;
    uint256 private TUSDTB=0;
    uint256 private EliteCount=0;
    uint256 private globalStakCount=0;
    uint8 TeamLeaderPaid=0;
    event Registration (address indexed _regadd,address indexed _refadd,uint256 _usdt,uint256 _stakeDays, uint256 _regDate,uint256 _endDate);
    event Deposit (address indexed regadd,address indexed refadd,uint256 usdt,uint256 stakeDays,uint256 _startDate,uint256 _endDate);
    event WithdrawalUSDTEvent (address indexed regadd,uint256 usdt,uint256 _WithDate);
    event WithdrawalETLEvent (address indexed regadd,uint256 etl,uint256 _WithDate);
        
   
    struct User 
	{
        address myaddress;
		address referrer;
        uint256 regdt;
        uint256 User_TotalETL_Stak;
        uint256 User_TotalUSDT_Stak;
        uint256 Total_Directs;
        uint256 Directs_StakUSDT;
        uint256 Directs_StakETL;
        uint256 Total_Teams;
        uint256 Team_StakUSDT;
        uint256 Team_StakETL;
        uint8 isTeamLeader;
        uint256 Directs_Bonus;
        uint256 Level_Bonus;
        uint256 Global_Stak_Bonus;
        uint256 Elite_Pool_Bonus;
        uint256 Team_Leader_Bonus;
        uint256 Global_Leader_Bonus;
        uint256 USDT_Bonus_Withdrawal;
        uint256 ETL_Bonus_Withdrawal;
        uint8 GlobalStakRank;
        uint256 count;
        uint8 MaxLevel;
        mapping(uint256 => BuyStak) Package;
        mapping(uint256 => LevelBonus) levelTeam;

        
    }


    struct BuyStak 
	{
		uint256 buyETL;
        uint256 usdtAmt;
        uint256 etlUsdtRate;
        uint256 StakDays;
        uint256 CreditPer;
		uint256 start;
		uint256 enddt;
	}

    struct LevelBonus 
	{
        uint256 USDTStak;
        uint256 ETLStak;
        uint256 CommPer;
        uint256 LevelBonus;
        uint256 LevelSize;
	}

    struct GlobalStakPool 
	{
        address add;
	}

    struct ElitePool 
	{
        address addr;
        uint256 enddt;
   	}

   
    struct TeamLeaderPool 
	{
        address addr;
	}

    mapping (address => User)  users;
    mapping (uint256 => GlobalStakPool)  GlobalUsers;
    mapping (uint256 => TeamLeaderPool)  TeamLeaderPools;
   
    mapping (uint256 => ElitePool)  ElitePools;
  
    

    constructor(address _usdt_tokenAddr,address _etl_tokenAddr,uint256 _ETLCurRate, address payable _marketingAddr, address payable _LiquidityAddr,address _pairedContract) public 
	{
		require(!isContract(_marketingAddr) );
        usdt_token = IERC20(_usdt_tokenAddr);
        etl_token = IERC20(_etl_tokenAddr);
        ETLCurRate=_ETLCurRate;
		marketingAddress = _marketingAddr;
		liquidityAddress=_LiquidityAddr;
		owner=msg.sender;
        Default_Referral=0x0000000000000000000000000000000000000000;
        User storage user = users[msg.sender];
        user.myaddress=msg.sender;
        user.referrer=0x0000000000000000000000000000000000000000;
        user.regdt = block.timestamp;
        pairedContract=_pairedContract;
	}


   function getTokenPrice(address pairAddress, uint amount) public view returns(uint256)
   {
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
        //IERC20 token1 = IERC20(pair.token1());
        (uint Res0, uint Res1,) = pair.getReserves();
        uint256 res0 = Res0;  //*(10**token1.decimals());
        return((amount*Res1)/res0); // return amount of token0 needed to buy token1
   }
	
    function Staking (uint256 _usdt,uint256 _staking_days ) public payable
    {
        require(isUserIDExists(msg.sender),"This Address is not Registred" );
        address referAdd=users[msg.sender].referrer;
        Register(referAdd,_usdt,_staking_days );
    }
    
    
    function Register(address _referrer,uint256 _usdt,uint256 _staking_days ) public payable 
	{
        uint256 etltoken=0;
        require(_usdt<=usdt_token.balanceOf(msg.sender),"Please Check Balance in Your Wallet Address");
        ETLCurRate=getTokenPrice(pairedContract,100000000);
        
        etltoken = _usdt.div(ETLCurRate).mul(ETLTMULT);
        require(etltoken <(Total_ETL_Supply-TETLPS) ,"ETL Supply is low");
        require(_usdt >=25*USDTMULT ,"USDT Stak Value Should more then 25 USDT");
        require(isUserIDExists(_referrer),"Referral Address not found" );
        require(msg.sender!=_referrer,"Referral Address and Register Address will not be same" );
        require(_staking_days ==200 || _staking_days ==400 || _staking_days ==600, "Staking Terms Should be 200,400,or 600 days" );
        
      
       User storage user = users[msg.sender];
       
       uint8 isnew=0;
       
       usdt_token.transferFrom(msg.sender, address(this), _usdt.mul(3500).div(PERCENTS_DIVIDER));
       usdt_token.transferFrom(msg.sender, liquidityAddress, _usdt.mul(3500).div(PERCENTS_DIVIDER));
       usdt_token.transferFrom(msg.sender, marketingAddress, _usdt.mul(3000).div(PERCENTS_DIVIDER));
       
       USDT35RECIVED += _usdt.mul(3500).div(PERCENTS_DIVIDER);
       ReservedFund += _usdt.mul(500).div(PERCENTS_DIVIDER);
        uint8 SeqNo=0;
        if (_staking_days==200){
                    SeqNo=0;
            }
        if (_staking_days==400){
                    SeqNo=1;
            }
        if (_staking_days==600){
                    SeqNo=2;
            }  
        
        TETLPS += etltoken + etltoken.mul(STAKE_PER[SeqNo]).div(PERCENTS_DIVIDER);

        uint256 j=0;  
        if(!isUserIDExists(msg.sender))
        {
            
            user.myaddress=msg.sender;
            user.referrer=_referrer;
            user.regdt = block.timestamp;
            user.isTeamLeader=0;
            j=user.count+1;
            user.Package[j].buyETL = etltoken;
            user.Package[j].usdtAmt = _usdt;
            user.Package[j].etlUsdtRate = ETLCurRate;
            users[_referrer].Total_Directs += 1;
            user.GlobalStakRank=0;
            
            user.Package[j].start = block.timestamp;

            user.Package[j].enddt = block.timestamp.add(STAKE_TREAM[SeqNo]*(DaysSend));
            user.Package[j].StakDays = STAKE_TREAM[SeqNo]*(DaysSend);
            user.Package[j].CreditPer = STAKE_PER[SeqNo];
          

           // users[_referrer].directTeams[users[_referrer].Total_Directs].addr=msg.sender;
            user.count +=1;
            Total_Users += 1;
            isnew=1;
            emit  Registration (msg.sender,_referrer,_usdt,_staking_days,block.timestamp,block.timestamp.add(STAKE_TREAM[SeqNo]*(DaysSend)));

        }
        else 
        {
           
            j = users[msg.sender].count+1;

            users[msg.sender].Package[j].buyETL = etltoken;
            users[msg.sender].Package[j].usdtAmt = _usdt;
            users[msg.sender].Package[j].etlUsdtRate = ETLCurRate;
            users[msg.sender].Package[j].start = block.timestamp;
            users[msg.sender].Package[j].enddt = block.timestamp.add(STAKE_TREAM[SeqNo]*(DaysSend));
            users[msg.sender].Package[j].StakDays = STAKE_TREAM[SeqNo]*(DaysSend);
            users[msg.sender].Package[j].CreditPer = STAKE_PER[SeqNo];
      
            users[msg.sender].count +=1;
            isnew=0;


        }
        
        emit Deposit (msg.sender,_referrer,_usdt,_staking_days,block.timestamp,block.timestamp.add(STAKE_TREAM[SeqNo]*(DaysSend)));
        users[_referrer].Directs_Bonus +=_usdt.mul(DIRECT_PER[SeqNo]).div(PERCENTS_DIVIDER);
        
        TUSDTB+=_usdt.mul(DIRECT_PER[SeqNo]).div(PERCENTS_DIVIDER);
        Total_ETL_Staking += etltoken;
        Total_USDT_Staking +=_usdt;

        users[_referrer].Directs_StakUSDT += _usdt;
        users[_referrer].Directs_StakETL += etltoken;
        users[msg.sender].User_TotalUSDT_Stak += _usdt;
        users[msg.sender].User_TotalETL_Stak += etltoken;     // Total Staking ETL  
        
        //----------------------------------------------------------
        updateGlobalShareBonus();

        updateGlobalStakShare(msg.sender);
        //-----------------------------------------------------------
        
		uint256 TeamLeaderfund=0;
        TeamLeaderfund=(_usdt.mul(500).div(PERCENTS_DIVIDER));

       
        
        uint8 i=0;
        address upline;
        upline = _referrer;
        TeamLeaderPaid=0;
		while (upline!=Default_Referral) 
		{
            
            if(i<25)
            {
                if(isnew==1 && i>=users[upline].MaxLevel ) 
                { users[upline].MaxLevel=i;}                
                if(isnew==1 )
                {
                    users[upline].levelTeam[i].LevelSize +=1;
                    users[upline].Total_Teams +=1;
                 }
                
                users[upline].levelTeam[i].USDTStak += _usdt;
                users[upline].levelTeam[i].ETLStak += etltoken;
                users[upline].levelTeam[i].CommPer = REFERRAL_PERCENTS[i];
                users[upline].levelTeam[i].LevelBonus += _usdt.mul(REFERRAL_PERCENTS[i]).div(PERCENTS_DIVIDER);
                TUSDTB+=_usdt.mul(REFERRAL_PERCENTS[i]).div(PERCENTS_DIVIDER);
                users[upline].Level_Bonus +=_usdt.mul(REFERRAL_PERCENTS[i]).div(PERCENTS_DIVIDER);
                
               
                users[upline].Team_StakUSDT +=_usdt;
                users[upline].Team_StakETL += etltoken;

            }
            
            if(users[upline].isTeamLeader==1 && TeamLeaderPaid==0)
            {
                 users[upline].Team_Leader_Bonus += TeamLeaderfund;
                 TUSDTB+=TeamLeaderfund;
                 TeamLeaderPaid=1;
                 
            }

            updateGlobalStakShare(upline);
            upline = users[upline].referrer;

            i=i+1;
        }
       
        updateGlobalLeaderBonus(_usdt);
        updateEliteBonus(_usdt);
         
        if(_usdt>=5000*USDTMULT && _staking_days>=400)
        {
             EliteCount=EliteCount+1;
             ElitePool storage elitepoolx = ElitePools[EliteCount];
             elitepoolx.addr = msg.sender;
             elitepoolx.enddt= STAKE_TREAM[SeqNo]*(DaysSend);
            
        }
      

    }

    
    
    function updateGlobalLeaderBonus(uint256 _usdt) internal
    {

        uint256 GlobalLeaderfund=0;

        if(Global_Leaders.length>0)
        { 
            GlobalLeaderfund = (_usdt.mul(500).div(PERCENTS_DIVIDER)).div(Global_Leaders.length);

        }

       

        for(uint256 m=0;m<Global_Leaders.length;m++)
        {
            users[Global_Leaders[m]].Global_Leader_Bonus += GlobalLeaderfund;
            TUSDTB+=GlobalLeaderfund;
        }

        
    }


    function updateEliteBonus(uint256 _usdt) internal
    {
        uint256 EliteFund=0;
        if(EliteCount>0)
        {
            EliteFund= _usdt.mul(300).div(PERCENTS_DIVIDER).div(EliteCount);
        }
        uint256 k=0;
        while (k<= EliteCount) 
        {
            if (ElitePools[k].enddt <= block.timestamp)
            {
                users[ElitePools[k].addr].Elite_Pool_Bonus += EliteFund;
                TUSDTB+=EliteFund;
            }
            k=k+1;
        }
    }


    function updateGlobalShareBonus() internal
    {   
       StakBonusx[0]=0;
       StakBonusx[1]=0;
       StakBonusx[2]=0;
       StakBonusx[3]=0;
       StakBonusx[4]=0;

        if(ReservedFund>=50*USDTMULT)
        {
            for (uint8 i=0;i<=4;i++)
            {
                if(RankCounts[i]>0) 
                {  
                    StakBonusx[i]=ReservedFund.div(5).div(RankCounts[i]);    
                }
            }
      
            for (uint256 n=1;n<=globalStakCount;n++)
            {
                for (uint8 j=0;j<=4;j++ )
                {
                    if(users[GlobalUsers[n].add].GlobalStakRank==j+1)
                    {
                        users[GlobalUsers[n].add].Global_Stak_Bonus += StakBonusx[j];
                        TUSDTB+=StakBonusx[j];
                    }
                }
        
            }
            ReservedCycle+=1;
            ReservedFund=0;

         }
    }

    function updateGlobalStakShare(address addr) internal
    {
        
         for (uint8 j=0;j<5;j++)
         {      
             if(users[addr].GlobalStakRank<j+1)
             {
                if(users[addr].User_TotalUSDT_Stak>=self_stake[j]*USDTMULT &&  users[addr].Directs_StakUSDT>=direct_stake[j]*USDTMULT && users[addr].Team_StakUSDT>=team_stake[j]*USDTMULT)
                {
                            RankCounts[j]+=1;
                           
                            
                            if(j>0)
                            {
                                RankCounts[j-1]=RankCounts[j-1]-1;
                            }
                            
                            if (users[addr].GlobalStakRank==0)
                            {
                               globalStakCount = globalStakCount+1;
                               GlobalStakPool storage globalusersx=GlobalUsers[globalStakCount];
                               globalusersx.add=addr;
                               
                            }

                             users[addr].GlobalStakRank=j+1;
                }
            }
         }


    }
    //--------------------------ROI Function
    function getUserETLBonus(address userAddress) public view returns (uint256) {
				      
		uint256 totalDividends=0;
		uint256 dividends;
		uint256 curdate;
	  
         User storage userx = users[userAddress];

        for (uint16 i=1; i<=userx.count;i++)
        {
            dividends=0;
            if (userx.Package[i].usdtAmt>0)
            {
      
                if(userx.Package[i].enddt>= block.timestamp)
                {
                    curdate=block.timestamp;
                }
                else
                {
                    curdate=userx.Package[i].enddt;
                }
                
				
				dividends = (userx.Package[i].buyETL.mul(userx.Package[i].CreditPer).div(PERCENTS_DIVIDER))
				.mul(curdate.sub(userx.Package[i].start)).div(userx.Package[i].StakDays);

                if(userx.Package[i].enddt<=curdate)
                {
                    dividends = dividends.add(userx.Package[i].buyETL);
                }
          
				totalDividends = totalDividends.add(dividends);

            }
            }

		return totalDividends;
	}

  


   

   function getUsersPersonalDetails(address myadd) public view returns (address _selfadd,address _refAdd,uint256 _selfStakETL,uint256 _selfStakUSDT,uint256 _usdtwith,uint256 _ethwith,uint8 _GlobalStakRank)
   {
       User storage u = users[myadd];
       return (u.myaddress,u.referrer,u.User_TotalETL_Stak,u.User_TotalUSDT_Stak,u.USDT_Bonus_Withdrawal,u.ETL_Bonus_Withdrawal,u.GlobalStakRank);
   }

   function getGlobalSharingTeamDtl() public view returns (uint256 _ReservedCycle,uint256 _ReservedFund,uint256 _SilverCount,uint256 _GoldCount,uint256 _PlatinumCount,uint256 _DiamondCount,uint256 _CrownCount)
   {
      return (ReservedCycle,ReservedFund,RankCounts[0],RankCounts[1],RankCounts[2],RankCounts[3],RankCounts[4]);
   }
  
  
   function getUsersTeamLevelDtl(address myadd) public view returns (uint256[] memory _USDTStak1, uint256[] memory _ETLStak1,uint256[] memory _CommPer1,uint256[] memory _LevelBonus1,uint256[] memory _LevelSize1)
   {
       require(isUserIDExists(myadd),"Address not Register" );
        address addr=myadd;
        uint8 k=users[addr].MaxLevel;
        uint256 [] memory USDTStak1= new uint256[](k);
        uint256 [] memory  ETLStak1= new uint256[](k);
        uint256 [] memory CommPer1= new uint256[](k);
        uint256 [] memory LevelBonus1= new uint256[](k);
        uint256 [] memory LevelSize1= new uint256[](k);
        
        
         
       for (uint8 i=1;i<=k;i++)
       {
           USDTStak1[i-1]=users[addr].levelTeam[i].USDTStak ;
           ETLStak1[i-1]=users[addr].levelTeam[i].ETLStak ;
           CommPer1[i-1]=users[addr].levelTeam[i].CommPer ;
           LevelBonus1[i-1]=users[addr].levelTeam[i].LevelBonus ;
           LevelSize1[i-1]=users[addr].levelTeam[i].LevelSize ;
           
       }
        
       return (USDTStak1,ETLStak1,CommPer1,LevelBonus1,LevelSize1 );
   }


   function getUsersStakDtl(address myadd) public view returns (uint256[] memory _buyETL, uint256[] memory _usdtAmt,uint256[] memory _etlUsdtRate,uint256[] memory _StakDays,uint256[] memory _CreditPer,uint256[] memory _start,uint256[] memory _enddt)
   {
       require(isUserIDExists(myadd),"Address not Register" );
       address addr=myadd;
       uint256 k=users[addr].count;
        uint256 [] memory buyETL = new uint256[](k);
        uint256 [] memory  usdtAmt= new uint256[](k);
        uint256 [] memory etlUsdtRate= new uint256[](k);
        uint256 [] memory StakDays= new uint256[](k);
        uint256 [] memory CreditPer= new uint256[](k);
		uint256 [] memory start= new uint256[](k);
		uint256 [] memory enddt= new uint256[](k);
        
       for (uint256 i=1;i<=users[addr].count;i++)
       {
           buyETL[i-1]=users[addr].Package[i].buyETL ;
           usdtAmt[i-1]=users[addr].Package[i].usdtAmt ;
           etlUsdtRate[i-1]=users[addr].Package[i].etlUsdtRate ;
           StakDays[i-1]=users[addr].Package[i].StakDays ;
           CreditPer[i-1]=users[addr].Package[i].CreditPer ;
           start[i-1]=users[addr].Package[i].start ;
           enddt[i-1]=users[addr].Package[i].enddt ;
       }
        
       return (buyETL,usdtAmt,etlUsdtRate,StakDays,CreditPer,start,enddt);
   }

   
   
//---------------------admin function--------------------------
    function AddTeamLeaderPool(address Addr) external 
	{
        require(owner==msg.sender,"Only Owner can do" );
        require(isUserIDExists(Addr),"Address not Register" );
        Team_Leaders.push(Addr);
        users[Addr].isTeamLeader=1;
    }

    function DeleteTeamLeaderPool(uint256 k) external 
	{
        require(owner==msg.sender,"Only Owner can do" );
        require(isUserIDExists(Team_Leaders[k]),"Address not Register" );
        users[Team_Leaders[k]].isTeamLeader=0;
       
        Team_Leaders[k] = Team_Leaders[Team_Leaders.length - 1];
        Team_Leaders.pop();

   
    }

     function AddGlobalLeaderPool(address Addr) external 
	{
        require(owner==msg.sender,"Only Owner can do" );
        require(isUserIDExists(Addr),"Address not Register" );
        Global_Leaders.push(Addr);
        

    }

      function DeleteGlobalLeaderPool(uint256 k) external 
	{
        require(owner==msg.sender,"Only Owner can do" );
        require(isUserIDExists(Global_Leaders[k]),"Address not Register" );
        Global_Leaders[k] = Global_Leaders[Global_Leaders.length - 1];
        Global_Leaders.pop();
        
    }

    function DepositEthereal(uint256 _etlToken) external  
	{
        require(owner==msg.sender,"Only Owner can do" );
         require(_etlToken<etl_token.balanceOf(msg.sender),"Please Check Balance in Your Wallet Address");
        Total_ETL_Supply +=_etlToken;
        Total_ETL_Balance += _etlToken;
        etl_token.transferFrom(msg.sender, address(this), _etlToken);
        
    }
    function DepositUSDT(uint256 _USDTToken) external 
	{
        require(owner==msg.sender,"Only Owner can do" );
        require(_USDTToken<usdt_token.balanceOf(msg.sender),"Please Check Balance in Your Wallet Address");
        TUSDT_Deposit+=_USDTToken;
        usdt_token.transferFrom(msg.sender, address(this), _USDTToken);
        
    }

    function getEliteList() public view returns (address[] memory _eliteAddrs)
     {

       //require(owner==msg.sender,"Only Owner can view" );
       address [] memory eliteAddrs= new address[](EliteCount);
       for (uint256 i=1;i<=EliteCount;i++)
       {
           eliteAddrs[i]= ElitePools[i].addr ;
       }
        
       return (eliteAddrs );

   }

   
    function getTeamLeaderList() public view returns (address[] memory _eliteAddrs)
     {
      
       return (Team_Leaders );

   }

   function getGlobalTeamLeaderList() public view returns (address[] memory _eliteAddrs)
   {
      
      return (Global_Leaders);

   }



    //-------------------------get user function------------------------------------------------------
    function WithdrawalEthereal() external 
	{
         require(isUserIDExists(msg.sender),"Address not Register" );
         uint256 bal=getUsersWithBalETL(msg.sender);
         etl_token.transfer(msg.sender,bal);
         Total_ETL_Balance = Total_ETL_Balance-bal;
         Total_ETL_Withdrawal+=bal;
         users[msg.sender].ETL_Bonus_Withdrawal +=bal;
                
        emit WithdrawalETLEvent (msg.sender,bal,block.timestamp);


       
    }


    function WithdrawalUSDT() external 
	{
         require(isUserIDExists(msg.sender),"Address not Register" );
         uint256 balusdt=getUsersWithBalUSDT(msg.sender);
         usdt_token.transfer(msg.sender,balusdt);
         users[msg.sender].USDT_Bonus_Withdrawal+=balusdt;
         Total_USDT_Withdrawal+=balusdt;
        
       emit WithdrawalUSDTEvent (msg.sender,balusdt,block.timestamp);
    }


    function getUsersWithBalUSDT(address myadd) public view returns (uint256)
   {
       require(isUserIDExists(myadd),"Address not Register" );
       uint256 WithdrawaAble;
       WithdrawaAble= users[myadd].Directs_Bonus+users[myadd].Level_Bonus+users[myadd].Global_Stak_Bonus+users[myadd].Elite_Pool_Bonus+users[myadd].Team_Leader_Bonus+users[myadd].Global_Leader_Bonus- users[myadd].USDT_Bonus_Withdrawal;
       return WithdrawaAble;
   }

   function getUsersWithBalETL(address myadd) public view returns (uint256)
   {
       require(isUserIDExists(myadd),"Address not Register" );
       uint256 WithdrawaAble;
       WithdrawaAble=getUserETLBonus(myadd)-users[myadd].ETL_Bonus_Withdrawal;
       
       return WithdrawaAble;
   }

    
    function getUsersTeamDetails(address myadd) public view returns (uint256 _totalDirects,uint256 _totalDirectBV,uint256 _totalDirectETL,uint256 _totalTeams,uint256 _totalTeamBV,uint256 _totalTeamETL) {
          
		return (users[myadd].Total_Directs,users[myadd].Directs_StakUSDT,users[myadd].Directs_StakETL,users[myadd].Total_Teams,users[myadd].Team_StakUSDT,users[myadd].Team_StakETL);
	}

   

    function getUserUSDTBonusDetails(address myadd) public view returns (uint256 _Directs_Bonus,uint256 _Level_Bonus,uint256 _Global_Stak_Bonus,uint256 _Elite_Pool_Bonus,uint256 _Team_Leader_Bonus,uint256 _Global_Leader_Bonus,uint256 _TotalUSDTBonus) {
        
        uint256 totalUSDTBonus = users[myadd].Directs_Bonus+users[myadd].Level_Bonus+users[myadd].Global_Stak_Bonus+users[myadd].Elite_Pool_Bonus+users[myadd].Team_Leader_Bonus+users[myadd].Global_Leader_Bonus;
		return (users[myadd].Directs_Bonus,users[myadd].Level_Bonus,users[myadd].Global_Stak_Bonus,users[myadd].Elite_Pool_Bonus,users[myadd].Team_Leader_Bonus,users[myadd].Global_Leader_Bonus,totalUSDTBonus);
	}



    //----------------Contract Global Function-----------------------------------------
     function getETLUsdtRate() public view returns (uint256) {
		uint256 r=getTokenPrice(pairedContract,100000000);
        if (r<50000)
        {
            r=50000;
        }
        return r;
	}

    function getContractDetails() public view returns (uint256 _Bal_ETL_Supply,uint256 _Total_ETL_Balance,uint256 _ETLCurRate,uint256 _Total_ETL_Staking,uint256 _Total_USDT_Staking,uint256 _Total_ETL_Withdrawal,uint256 _Total_USDT_Withdrawal,uint256 _Total_Users) {
		return ((Total_ETL_Supply-Total_ETL_Staking),Total_ETL_Balance,ETLCurRate,Total_ETL_Staking,Total_USDT_Staking,Total_ETL_Withdrawal,Total_USDT_Withdrawal,Total_Users);
	}
    function getContractBalance() public view returns (uint256 b1,uint256 b2,uint256 b3,uint256 b4,uint256 b5,uint256 b6,uint256 b7)
    {
        
        return (Total_ETL_Supply,TETLPS,usdt_token.balanceOf(address(this)),TUSDTB,etl_token.balanceOf(address(this)),TUSDT_Deposit,USDT35RECIVED); 
    }

    function setLiquidityContract(address liqAdd) external  
    {
        require(owner==msg.sender,"Only Owner can do" ); 
            pairedContract=liqAdd;
    }
    function updateOwner(address ownAdd) external 
    {
        require(owner==msg.sender,"Only Owner can do" ); 
        owner=ownAdd;
    }
   //----------------------validate function---------------------------------------------------------------
    function isContract(address addr) internal view returns (bool) {
        uint size;
        assembly { size := extcodesize(addr) }
        return size > 0;
    }

    
    function isUserIDExists(address _useraddress) public view returns (bool) 
    {
        if(users[_useraddress].myaddress==address(0))
        {
            return false;    
        }
        else
        {
            return true; 
        }
        
    }

}


// File: safemath_contract.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;
library SafeMathX {
   
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// File: uniswap_interface.sol



/**
"SPDX-License-Identifier: UNLICENSED"
 *Submitted for verification at Etherscan.io on 2020-06-05
*/

pragma solidity ^0.6.6;

interface IUniswapV2Factory {
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    function feeTo() external view returns (address);
    function feeToSetter() external view returns (address);

    function getPair(address tokenA, address tokenB) external view returns (address pair);
    function allPairs(uint) external view returns (address pair);
    function allPairsLength() external view returns (uint);

    function createPair(address tokenA, address tokenB) external returns (address pair);

    function setFeeTo(address) external;
    function setFeeToSetter(address) external;
}

interface IUniswapV2Pair {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);
    function PERMIT_TYPEHASH() external pure returns (bytes32);
    function nonces(address owner) external view returns (uint);

    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
    
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint);
    function factory() external view returns (address);
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function price0CumulativeLast() external view returns (uint);
    function price1CumulativeLast() external view returns (uint);
    function kLast() external view returns (uint);

    function mint(address to) external returns (uint liquidity);
    function burn(address to) external returns (uint amount0, uint amount1);
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
    function skim(address to) external;
    function sync() external;

    function initialize(address, address) external;
}

interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
}

interface IWETH {
    function deposit() external payable;
    function transfer(address to, uint value) external returns (bool);
    function withdraw(uint) external;
}

contract UniswapV2Router02 is IUniswapV2Router02 {
    using SafeMath for uint;

    address public immutable override factory;
    address public immutable override WETH;

    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
        _;
    }

    constructor(address _factory, address _WETH) public {
        factory = _factory;
        WETH = _WETH;
    }

    receive() external payable {
        assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
    }

    // **** ADD LIQUIDITY ****
    function _addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin
    ) internal virtual returns (uint amountA, uint amountB) {
        // create the pair if it doesn't exist yet
        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }
        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
        if (reserveA == 0 && reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
            if (amountBOptimal <= amountBDesired) {
                require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
                (amountA, amountB) = (amountADesired, amountBOptimal);
            } else {
                uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
                assert(amountAOptimal <= amountADesired);
                require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            }
        }
    }
    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
        TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
        liquidity = IUniswapV2Pair(pair).mint(to);
    }
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
        (amountToken, amountETH) = _addLiquidity(
            token,
            WETH,
            amountTokenDesired,
            msg.value,
            amountTokenMin,
            amountETHMin
        );
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
        IWETH(WETH).deposit{value: amountETH}();
        assert(IWETH(WETH).transfer(pair, amountETH));
        liquidity = IUniswapV2Pair(pair).mint(to);
        // refund dust eth, if any
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
    }

    // **** REMOVE LIQUIDITY ****
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
        (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
        (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
        (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
        require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
        require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
    }
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
        (amountToken, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        TransferHelper.safeTransfer(token, to, amountToken);
        IWETH(WETH).withdraw(amountETH);
        TransferHelper.safeTransferETH(to, amountETH);
    }
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountA, uint amountB) {
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
    }
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountToken, uint amountETH) {
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
    }

    // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) public virtual override ensure(deadline) returns (uint amountETH) {
        (, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
        IWETH(WETH).withdraw(amountETH);
        TransferHelper.safeTransferETH(to, amountETH);
    }
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountETH) {
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
            token, liquidity, amountTokenMin, amountETHMin, to, deadline
        );
    }

    // **** SWAP ****
    // requires the initial amount to have already been sent to the first pair
    function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
        for (uint i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0,) = UniswapV2Library.sortTokens(input, output);
            uint amountOut = amounts[i + 1];
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
            IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                amount0Out, amount1Out, to, new bytes(0)
            );
        }
    }
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
        amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, to);
    }
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, to);
    }
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        IWETH(WETH).deposit{value: amounts[0]}();
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
        _swap(amounts, path, to);
    }
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, address(this));
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, address(this));
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        IWETH(WETH).deposit{value: amounts[0]}();
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
        _swap(amounts, path, to);
        // refund dust eth, if any
        if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
    }

    // **** SWAP (supporting fee-on-transfer tokens) ****
    // requires the initial amount to have already been sent to the first pair
    function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
        for (uint i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0,) = UniswapV2Library.sortTokens(input, output);
            IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
            uint amountInput;
            uint amountOutput;
            { // scope to avoid stack too deep errors
            (uint reserve0, uint reserve1,) = pair.getReserves();
            (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
            amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
            amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
            }
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
            pair.swap(amount0Out, amount1Out, to, new bytes(0));
        }
    }
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) {
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
        );
        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to);
        require(
            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
        );
    }
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    )
        external
        virtual
        override
        payable
        ensure(deadline)
    {
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        uint amountIn = msg.value;
        IWETH(WETH).deposit{value: amountIn}();
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to);
        require(
            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
        );
    }
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    )
        external
        virtual
        override
        ensure(deadline)
    {
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
        );
        _swapSupportingFeeOnTransferTokens(path, address(this));
        uint amountOut = IERC20(WETH).balanceOf(address(this));
        require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        IWETH(WETH).withdraw(amountOut);
        TransferHelper.safeTransferETH(to, amountOut);
    }

    // **** LIBRARY FUNCTIONS ****
    function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
        return UniswapV2Library.quote(amountA, reserveA, reserveB);
    }

    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
        public
        pure
        virtual
        override
        returns (uint amountOut)
    {
        return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
    }

    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
        public
        pure
        virtual
        override
        returns (uint amountIn)
    {
        return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
    }

    function getAmountsOut(uint amountIn, address[] memory path)
        public
        view
        virtual
        override
        returns (uint[] memory amounts)
    {
        return UniswapV2Library.getAmountsOut(factory, amountIn, path);
    }

    function getAmountsIn(uint amountOut, address[] memory path)
        public
        view
        virtual
        override
        returns (uint[] memory amounts)
    {
        return UniswapV2Library.getAmountsIn(factory, amountOut, path);
    }
}

// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)

library SafeMath {
    function add(uint x, uint y) internal pure returns (uint z) {
        require((z = x + y) >= x, 'ds-math-add-overflow');
    }

    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x, 'ds-math-sub-underflow');
    }

    function mul(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
    }
   
}

library UniswapV2Library {
    using SafeMath for uint;

    // returns sorted token addresses, used to handle return values from pairs sorted in this order
    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
        require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
    }

    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(uint(keccak256(abi.encodePacked(
                hex'ff',
                factory,
                keccak256(abi.encodePacked(token0, token1)),
                hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
            ))));
    }

    // fetches and sorts the reserves for a pair
    function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
        (address token0,) = sortTokens(tokenA, tokenB);
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

    // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
    function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
        require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
        require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        amountB = amountA.mul(reserveB) / reserveA;
    }

    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }

    // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
        require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint numerator = reserveIn.mul(amountOut).mul(1000);
        uint denominator = reserveOut.sub(amountOut).mul(997);
        amountIn = (numerator / denominator).add(1);
    }

    // performs chained getAmountOut calculations on any number of pairs
    function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
        amounts = new uint[](path.length);
        amounts[0] = amountIn;
        for (uint i; i < path.length - 1; i++) {
            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
            amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
        }
    }

    // performs chained getAmountIn calculations on any number of pairs
    function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
        amounts = new uint[](path.length);
        amounts[amounts.length - 1] = amountOut;
        for (uint i = path.length - 1; i > 0; i--) {
            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
            amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
        }
    }
}

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
    }

    function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
    }

    function safeTransferFrom(address token, address from, address to, uint value) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
    }

    function safeTransferETH(address to, uint value) internal {
        (bool success,) = to.call{value:value}(new bytes(0));
        require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
    }
}