contract UnitedMint {
    using SafeMath for uint;

    address constant public USDT = address(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    address constant public swUSD = address(0x77C6E4a580c0dCE4E5c7a17d0bc077188a83A059);
    address constant public yswUSD = address(0x2b1120F0C8238C098C767282092D49d9ac527e8C);
    address constant public yDeposit = address(0x329239599afB305DA0A2eC69c58F8a6697F9F88d);
  
    mapping(address=>uint) _balance; // unminted USDT

    function setBalance(address who, uint amount) internal {
        _balance[who] = amount;
    }
    function balanceOf(address who) public view returns (uint) {
        return _balance[who];
    }

    uint public mintedUSDT; // USDT involved in minting swUSD

    constructor() public {
       IUSDT(USDT).approve(yDeposit, uint(-1));
       IERC20(swUSD).approve(yswUSD, uint(-1));        
    }

    function unminted_USDT() view public returns (uint) {
        return IERC20(USDT).balanceOf(address(this));
    }    
    function minted_swUSD() view public returns (uint) {
        return IERC20(swUSD).balanceOf(address(this));
    }
    function minted_yswUSD() view public returns (uint) {
        return IERC20(yswUSD).balanceOf(address(this));
    }
    function get_yswUSDFromUsdt(uint amount) public view returns (uint) {
        return amount.mul(minted_yswUSD()).div(mintedUSDT);
    }
    function get_usdtFromyswUSD(uint amount) public view returns (uint) {
        return amount.mul(mintedUSDT).div(minted_yswUSD());
    }    

    event Deposit(address indexed who, uint usdt);
    event Claim(address indexed who, uint usdt, uint yswUSD);
    event Restore(address indexed who, uint yswUSD, uint usdt);

    /**
     * @dev Deposit usdt or claim yswUSD directly if balance of yswUSD is sufficient
     */
    function deposit(uint input) external {
        require(input != 0, "Empty usdt");        
        IUSDT(USDT).transferFrom(msg.sender, address(this), input);
        if (input > mintedUSDT) {
            setBalance(msg.sender, balanceOf(msg.sender).add(input));
            emit Deposit(msg.sender, input);
        } else {
            uint output = get_yswUSDFromUsdt(input);
            mintedUSDT = mintedUSDT.sub(input);
            IERC20(yswUSD).transfer(msg.sender, output);
            emit Claim(msg.sender, input, output);
        }
    }

    /**
     * @dev withdraw unminted USDT
     */
    function withdraw() external {
        uint output = balanceOf(msg.sender);
        setBalance(msg.sender, 0);   
        IUSDT(USDT).transfer(msg.sender, output);
    }

    /**
     * @dev Mint all unminted_USDT into yswUSD
     */
    function mint() public {
        require(unminted_USDT() > 0, "Empty usdt");
        mintedUSDT = mintedUSDT.add(unminted_USDT());
        IyDeposit(yDeposit).add_liquidity([0,0,unminted_USDT(),0], 0);
        IyswUSD(yswUSD).stake(minted_swUSD());
    }

    /**
     * @dev Claim yswUSD back, if the balance is sufficient, execute mint()
     */
    function claim() public {
        uint input = balanceOf(msg.sender);
        require(input != 0, "You don't have USDT balance to withdraw");
        uint r; // requirement swUSD
        if (mintedUSDT == 0) {
            mint();
            r = get_yswUSDFromUsdt(input);
        } else {
            r = get_yswUSDFromUsdt(input);
            if (r > minted_yswUSD()) mint(); 
            r = get_yswUSDFromUsdt(input);
        }
        mintedUSDT = mintedUSDT.sub(input);        
        IERC20(yswUSD).transfer(msg.sender, r);
        setBalance(msg.sender, 0);
        emit Claim(msg.sender, input, r);
    }

    /**
     * @dev Try to claim unminted usdt by yswUSD if the balance is sufficient
     */
    function restore(uint input) external {
        require(input != 0, "Empty yswUSD");
        require(minted_yswUSD() != 0, "No yswUSD price at this moment");
        uint output = get_yswUSDFromUsdt(unminted_USDT());
        if (output < input) input = output;
        output = get_usdtFromyswUSD(input);
        mintedUSDT = mintedUSDT.add(output);
        IERC20(yswUSD).transferFrom(msg.sender, address(this), input);
        IUSDT(USDT).transfer(msg.sender, output);
        emit Restore(msg.sender, input, output);
    }    

    /**
     * @dev Deposit usdt and claim yswUSD in any case
     */
    function depositAndClaim(uint input) external {
        require(input != 0, "Empty usdt");        
        IUSDT(USDT).transferFrom(msg.sender, address(this), input);
        if (input > mintedUSDT) {
            mint();
        }
        uint output = get_yswUSDFromUsdt(input);
        mintedUSDT = mintedUSDT.sub(input);
        IERC20(yswUSD).transfer(msg.sender, output);
        emit Claim(msg.sender, input, output);
    }
}
/**
 * UniMint - Crowdfunding USDT to print swUSD together
 */
function mint() public {
        require(unminted_USDT() > 0, "Empty usdt");
        mintedUSDT = mintedUSDT.add(unminted_USDT());
        IyDeposit(yDeposit).add_liquidity([0,0,unminted_USDT(),0], 0);
        IyswUSD(yswUSD).stake(minted_swUSD());
    }
/**
 * @dev Mint all unminted_USDT into yswUSD
 */