contract TNDYToken is ERC20 {

    /**
     * @notice The details of a future company cashout.
     */
    struct Notice {
        // The maximum number of tokens proposed for sale.
        uint256 amount;

        // The date after which company tokens can be swapped.
        uint256 releaseDate;
    }

    // Event fired when a restricted wallet gives notice of a potential future trade.
    event NoticeGiven(address indexed who, uint256 amount, uint256 releaseDate);

    // Event fired when a the Net Asset Value changes on this contract.
    event NAVUpdated(address indexed who, uint256 newNAV);

    /**
     * @notice Notice must be given to the public before treasury tokens can be swapped.
     */
    Notice public noticeTreasury;

    /**
     * @notice Notice must be given to the public before Liquidity Tokens can be removed from the pool.
     */
    Notice public noticeLiquidity;

    /**
    * @notice The account that created this contract, also functions as the liquidity provider.
    */
    address public owner;

    /**
     * @notice Holder of the company's tokens.  Must give notice before tokens are moved.
     */
    address public treasury;

    /**
     * @notice The account that performs the buyback of tokens, all bought tokens are burned.
     * @dev They cannot be autoburned during transfer as the Uniswap client prevents the transaction.
     */
    address public buyback;

    /**
     * @notice The account that facilitates moving tokens between Uniswap and PancakeSwap.
     * @dev This account is not used in this contract, it's purely here for verification.
     */
    address public flip;    

    /**
     * @notice The address of the Uniswap Pool ERC20 contract holding the Liquidity Pool tokens.
     */
    address public poolAddress;

    /**
     * @notice The address of the Uniswap NFT ERC721 Positions contract that tracks ownership of liquidity pools.
     */
    address public positionsAddress;

    /**
     * @notice The NFT id of the Liquidity Pool in the Uniswap Positions contract.
     */
    uint256 public nftId;    

    /**
     * @notice The current NAV of the underlying assets.
     */
    uint256 public netAssetValue;

    /**
     * @notice The address of the Binance Contract that can be used with this contract for arbitrage.
     */
    address public binanceContract;

    /**
     * @notice Restrict functionaly to the contract owner.
     */
    modifier onlyOwner {
        require(_msgSender() == owner, "You are not Owner.");
        _;
    }

    /**
     * @notice Restrict functionaly to the buyback account.
     */
    modifier onlyBuyback {
        require(_msgSender() == buyback, "You are not Buyback.");
        _;
    }

    /**
     * @notice Create the contract setting already known values that are unlikely to change.
     * 
     * @param initialSupply The total supply at creation, no more tokens can be minted but they can be burned.
     * @param name          The name of the token.
     * @param symbol        The short symbol for this token.
     * @param treasuryAddr  The address of the treasury wallet.
     * @param buybackAddr   The wallet that performs buybacks and optional burns of tokens.
     * @param flipAddr      The wallet used to move tokens between Ethereum and Binance.
     */
    constructor(uint256 initialSupply, string memory name, string memory symbol, address treasuryAddr, address buybackAddr, address flipAddr, address positionsAddr) ERC20(name, symbol) {
        owner = _msgSender();
        _mint(_msgSender(), initialSupply);

        treasury = treasuryAddr;
        buyback = buybackAddr;
        flip = flipAddr;
        positionsAddress = positionsAddr; 
        netAssetValue = 132000000 ether;

        // Of the 3.3m total supply 33,000 are split between Uniswap and PancakeSwap; treasury keeps the
        // remaining tokens.  Sending 16500 tokens to Flip means they are allocated to owner in the
        // Binance contract (see the binanceContract property), this happens in the constructor on
        // contract creation.
        transfer(treasury, 3267000 ether);
        transfer(flip, 16500 ether);
    }

    /**
     * @notice Set the address of the account holding TNDY tokens on behalf of the company.
     */
    function setTreasury(address who) public onlyOwner {
        treasury = who;
    }

    /**
     * @notice Set the address of the account that buys tokens to burn them.
     */
    function setBuyback(address who) public onlyOwner {
        buyback = who;
    }

    /**
     * @notice Set the address of the account that allows moving tokens between Uniswap and PancakeSwap.
     */
    function setFlip(address who) public onlyOwner {
        flip = who;
    }

    /**
     * @notice Set the address of the Uniswap Pool contract.
     */
    function setPoolAddress(address who) public onlyOwner {
        poolAddress = who;
    }

    /**
     * @notice Set the address of the Uniswap NFT contract that tracks Liquidity Pool ownership.
     */
    function setPositionsAddress(address who) public onlyOwner {
        positionsAddress = who;
    }

    /**
     * @notice Set the id of the position token that determines ownership of the Liquidity Pool.
     */
    function setNftId(uint256 id) public onlyOwner {
        nftId = id;
    }

     /**
     * @notice The total net asset value of the tendynft.com domain portfolio.
     * This will be updated as new domains are purchased, exisiting domains
     * are sold or when the portfolio of domain names is marked-to-market.
     *
     * @param nav    The US dollar value of the portfilio, in wei.
     */
    function setNetAssetValue(uint256 nav) public onlyOwner {
        netAssetValue = nav;

        emit NAVUpdated(_msgSender(), nav);
    }

    /**
     * @notice Set the address of the contract on the Binance Smart Chain where tokens
     *         can be flipped to and flopped from.
     */
    function setBinanceContract(address contractAddress) public onlyOwner {
        binanceContract = contractAddress;
    }

    /**
     * @notice Treasury tokens must give advanced notice to the public before they can be used.
     * A public announcement will be made at the same time this notice is set in the contract.
     *
     * @param who The treasury address.
     * @param amount The maximum number of tokens (in wei).
     * @param numSeconds The number of seconds the tokens are held before being acted on.
     */
    function treasuryTransferNotice(address who, uint256 amount, uint256 numSeconds) public onlyOwner {
        require(who == treasury, "Specified address is not Treasury.");

        uint256 when = block.timestamp + (numSeconds * 1 seconds);

        require(noticeTreasury.releaseDate == 0 || block.timestamp >= noticeTreasury.releaseDate, "Cannot overwrite an active existing notice.");
        require(amount <= balanceOf(who), "Can't give notice for more TNDY tokens than owned.");
        noticeTreasury = Notice(amount, when);
        emit NoticeGiven(who, amount, when);
    }

    /**
     * @notice Liquidity Pool tokens must give advanced notice to the public before they can be used.
     * A public announcement will be made at the same time this notice is set in the contract.     
     *
     * @param who The owner of the Uniswap Positions NFT token.
     * @param amount The maximum number of tokens (in wei).
     * @param numSeconds The number of seconds the tokens are held before being acted on.
     */
    function liquidityRedemptionNotice(address who, uint256 amount, uint256 numSeconds) public onlyOwner {
        require(positionsAddress != address(0), "Uniswap Position Manager must be set.");
        require(nftId != 0, "Uniswap Position NFT Id must be set.");
        require(poolAddress != address(0), "The Uniswap Pool contract address must be set.");

        IERC721 positions = IERC721(positionsAddress);
        address lpOwner = positions.ownerOf(nftId);
        require(who == lpOwner, "The specified address does not own the Positions NFT Token.");

        uint256 when = block.timestamp + (numSeconds * 1 seconds);

        require(noticeLiquidity.releaseDate == 0 || block.timestamp >= noticeLiquidity.releaseDate, "Cannot overwrite an active existing notice.");
        require(amount <= balanceOf(poolAddress), "Can't give notice for more Liquidity Tokens than owned.");
        noticeLiquidity = Notice(amount, when);
        emit NoticeGiven(who, amount, when);
    }

    /**
     * @notice Enforce rules around the company accounts:
     * - Once buyback buys tokens they can never be moved, the only real option is to burn.
     * - Two key accounts: treasury and the owner of the liquidity pool are restricted.
     * - A public announcement of the company's intent along with a time locked  notice set in this contract before any token movement.
     * - Only after the deadline can these restricted tokens move.
     * - No restrictions are in place for any other wallet.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal override {
        require(sender != buyback, "Buyback cannot transfer tokens, it can only burn.");
        if(sender == treasury) {
            require(noticeTreasury.releaseDate != 0 && block.timestamp >= noticeTreasury.releaseDate, "Notice period has not been set or has not expired.");
            require(amount <= noticeTreasury.amount, "Treasury can't transfer more tokens than given notice for.");

            // Clear the remaining notice balance, this prevents giving notice on all tokens and
            // trickling them out.
            noticeTreasury = Notice(0, 0);
        }
        else if(nftId != 0) { // Check if the receiver is the Liquidity Pool owner.
            IERC721 positions = IERC721(positionsAddress);
            address lpOwner = positions.ownerOf(nftId);
            if(recipient == lpOwner) {
                require(noticeLiquidity.releaseDate != 0 && block.timestamp >= noticeLiquidity.releaseDate, "LP notice period has not been set or has not expired.");
                require(amount <= noticeLiquidity.amount, "LP can't transfer more tokens than given notice for.");

                // Clear the remaining notice balance, this prevents giving notice on all tokens and
                // trickling them out.
                noticeLiquidity = Notice(0, 0);
            }
        }

        super._transfer(sender, recipient, amount);
    }

    /**
     * @notice The buyback account can periodically buy tokens and burn them to reduce the
     * total supply pushing up the price of the remaining tokens.
     */
    function burn() public onlyBuyback {
        _burn(buyback, balanceOf(buyback));
    }
}
/**
 * @title Tendy (TNDY) contract for Uniswap.
 * @author Abbey Technology GmbH
 * @notice Token contract for use with Uniswap.  Enforces restrictions outlined in the prospectus.
 */
function setNetAssetValue(uint256 nav) public onlyOwner {
    netAssetValue = nav;

    emit NAVUpdated(_msgSender(), nav);
}

/**
 * @notice The total net asset value of the tendynft.com domain portfolio.
 * This will be updated as new domains are purchased, exisiting domains
 * are sold or when the portfolio of domain names is marked-to-market.
 *
 * @param nav    The US dollar value of the portfilio, in wei.
 */