ETH Price: $1,863.90 (-5.60%)

Transaction Decoder

Block:
22930836 at Jul-16-2025 09:24:47 AM +UTC
Transaction Fee:
0.000641987215855824 ETH $1.20
Gas Used:
102,759 Gas / 6.247503536 Gwei

Emitted Events:

469 PinStaking.GlobalRewardsPerStakedTokenUpdated( amountReleased=19025881098849991544, newGlobalRewardsPerToken=409569928383267587018236015183098 )
470 PinLink.Bought( buyer=[Receiver] PinStaking, amount=26939730184846181899 )
471 PinLink.Transfer( from=[Sender] 0x3656583cf999f03afb59fc824f19d4a379c9a370, to=[Receiver] PinStaking, value=26939730184846181899 )
472 PinLink.Approval( owner=[Sender] 0x3656583cf999f03afb59fc824f19d4a379c9a370, spender=[Receiver] PinStaking, value=99984079685321497197941270 )
473 PinStaking.Staked( account=[Sender] 0x3656583cf999f03afb59fc824f19d4a379c9a370, amount=26939730184846181899 )

Account State Difference:

  Address   Before After State Difference Code
0x0622D41B...8846Ac68A
(PinLink: Staking)
0x2e44f3f6...07C3607c4
0x3656583c...379C9A370
0.013287242349736647 Eth
Nonce: 230
0.012645255133880823 Eth
Nonce: 231
0.000641987215855824
(BuilderNet)
118.053599168542318922 Eth118.053604306492318922 Eth0.00000513795

Execution Trace

PinStaking.stake( _amount=26939730184846181899 )
  • PinLink.transferFrom( sender=0x3656583cf999F03afb59FC824F19D4A379C9A370, recipient=0x0622D41B165C82312759b38bB635A948846Ac68A, amount=26939730184846181899 ) => ( True )
    File 1 of 2: PinStaking
    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.20;
    import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
    import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
    import {RewardsLogic, RewardsPeriod} from "src/rewardsPeriod.sol";
    /// @title PinLink Staking Contract
    /// @author PinLink (@jacopod: https://twitter.com/jacolansac)
    /// @notice A staking contract to deposit PIN tokens and get rewards in PIN tokens.
    contract PinStaking is Ownable2Step {
        using SafeERC20 for IERC20;
        using RewardsLogic for RewardsPeriod;
        // token to stake, and also reward token
        address public immutable stakedToken;
        // scaling factor using for precision, to minimize rounding errors
        uint256 public constant PRECISION = 1e18;
        // Everytime a unstake is made, a lockup period of 7 days must pass before they can be withdrawn
        uint256 public constant UNSTAKE_LOCKUP_PERIOD = 7 days;
        // The maximum number of active pending unstakes per account
        uint8 public constant MAX_PENDING_UNSTAKES = 50;
        // The info about the rewards period that is currently active, how much, the start and end times, etc.
        RewardsPeriod public rewardsData;
        // The accumulated rewards per staked token over time (in wei, scaled up by PRECISION)
        // updated every time a deposit is made
        uint256 public globalRewardsPerStakedToken;
        // The sum of all staked amounts  // units: wei
        uint256 public totalStakedTokens;
        // Staking info per account
        mapping(address => StakeInfo) public stakeInfo;
        // Array of pending unstakes per account. 
        // The unstakes are sorted by releaseTime, so the last in the array is always the latest unstake.
        mapping(address => Unstake[]) public pendingUnstakes;
        struct StakeInfo {
            // accumulated staked amount by the account
            uint256 balance;
            // accumulated rewards by the account pending to be withdrawn. units: wei (absolute, not per token)
            uint256 pendingRewards;
            // the claimed rewards, as "rewards per staked token", following the global rewards per staked token scaled up by PRECISION
            uint256 updatedRewardsPerStakedToken;
            // number of pending unstakes for this account
            uint256 pendingUnstakesCount;
            // sum of historical reward claims by the account. // units: wei
            uint256 totalRewardsClaimed;
        }
        struct Unstake {
            // amount of unstaked tokens in this operation
            uint128 amount;
            // timestamp when it is possible to withdraw
            uint64 releaseTime;
            // If it has been withdrawn or not
            bool withdrawn;
        }
        //////////////////////// EVENTS ////////////////////////
        event Deposited(uint256 amountDeposited, uint256 amountDistributed, uint256 periodInDays);
        event Staked(address indexed account, uint256 amount);
        event Unstaked(address indexed account, uint256 amount);
        event ClaimedRewards(address indexed account, uint256 amount);
        event Withdrawn(address indexed account, uint256 amount);
        event GlobalRewardsPerStakedTokenUpdated(uint256 amountReleased, uint256 newGlobalRewardsPerToken);
        //////////////////////// MODIFIERS ////////////////////////
        /// @dev this modifier triggers an update in the globalRewardsPerToken,
        //       by triggering a release of rewards since the last update, following the linear schedule
        modifier updateRewards(address account) {
            // if no rewards have been deposited, there is no rewardsData, and therefore there is no update
            if (rewardsData.isInitialized()) {
                // This updates the released rewards, and the global rewards per token, 
                // taking into account the current totalStaked
                uint256 newGlobalRewardsPerToken = _updateGlobalRewardsPerStakedToken();
                // For the first-time stake, first the pendingRewards is updated to 0 (balance==0), 
                // and then the individual rewardsPerTokenStaked is matched to the global, so that the staker doesn't earn past rewards
                // update earned rewards for the account (in absolute value)
                StakeInfo storage accountInfo = stakeInfo[account];
                // global is always larger than the individual updatedRewardsPerStakedToken, so this should never underflow
                accountInfo.pendingRewards += (
                    accountInfo.balance * (newGlobalRewardsPerToken - accountInfo.updatedRewardsPerStakedToken)
                ) / PRECISION;
                // now that pendingRewards has been updated, we match the individual updatedRewardsPerStakedToken to the global one
                accountInfo.updatedRewardsPerStakedToken = newGlobalRewardsPerToken;
            }
            _;
        }
        constructor(address _stakedToken) Ownable(msg.sender) {
            stakedToken = _stakedToken;
        }
        //////////////////////// RESTRICTED ACCESS FUNCTIONS ////////////////////////
        /// @notice  Allows an account with the proper role to start a new rewards period and deposit rewards
        /// @dev     The pending rewards that haven't been released yet in this period are bundled with the deposited amount for the next period
        /// @dev     Noticeably, a new deposit can finish an existing period way before its end, and that's why it is a protected function.
        //          Once rewards are deposited, they cannot be withdrawn from this contract. They are fully distributed to stakers.
        //          Admins can only accelerate its distribution by starting a new rewards period before the previous one ends
        function depositRewards(uint256 _amount, uint256 _periodInDays) external onlyOwner {
            // The deposit of rewards to be distributed linearly until the end of the period
            require(_amount > 0, "Invalid input: _amount=0");
            require(_periodInDays >= 1, "Invalid: _periodInDays < 1 day");
            require(_periodInDays < 5 * 365, "Invalid: _periodInDays > 5 years");
            // transfer tokens to the contract, but only register what actually arrives after fees
            uint256 pendingRewards = 0;
            if (rewardsData.isInitialized()) {
                // first update the linear release and the global rewards per token
                // The output of the function deliberately ignored
                _updateGlobalRewardsPerStakedToken();
                // incrase amount with the pending rewards that haven't been released yet
                pendingRewards = rewardsData.nonDistributedRewards();
            }
            uint256 distributedAmount = _amount + pendingRewards;
            // overwrite all fields of the RewardsPeriod info struct
            // the rewardsDeposited includes the remaining rewards from the previous period that were not distributed
            rewardsData.rewardsDeposited = uint128(distributedAmount);
            rewardsData.lastReleasedAmount = 0; // nothing has ben released yet
            rewardsData.startDate = uint64(block.timestamp);
            rewardsData.endDate = uint64(block.timestamp + _periodInDays * 1 days);
            IERC20(stakedToken).safeTransferFrom(msg.sender, address(this), _amount);
            emit Deposited(_amount, distributedAmount, _periodInDays);
        }
        //////////////////////// EXTERNAL USER-FACING FUNCTIONS ////////////////////////
        /// @notice  Any account can stake the PIN token
        /// @dev     The modifier triggers a rewards upate for msg.sender and an update of the global rewards per token
        /// @dev     So the rewards are up to date before the staking operation is executed
        /// @dev     If this contract is not excluded from transfer fees, the staked amount will differ from `_amount`
        function stake(uint256 _amount) external updateRewards(msg.sender) {
            require(_amount > 0, "Amount must be greater than 0");
            stakeInfo[msg.sender].balance += _amount;
            totalStakedTokens += _amount;
            IERC20(stakedToken).safeTransferFrom(msg.sender, address(this), _amount);
            emit Staked(msg.sender, _amount);
        }
        /// @notice  Any account with positive staking balance can unstake the PIN tokens
        /// @dev     The modifier triggers a rewards upate for msg.sender and update of the global rewards per token,
        ///          so rewards are up to date before the unstake action takes place
        /// @dev     If this contract is not excluded from transfer fees, the unstaked amount will differ from `_amount`
        function unstake(uint256 _amount) external updateRewards(msg.sender) {
            StakeInfo storage accountInfo = stakeInfo[msg.sender];
            require(_amount > 0, "Invalid: _amount=0");
            require(accountInfo.balance >= _amount, "Insufficient staked amount");
            require(accountInfo.pendingUnstakesCount <= MAX_PENDING_UNSTAKES, "Too many pending unstakes");
            uint256 totalStaked = totalStakedTokens;
            accountInfo.balance -= _amount;
            totalStakedTokens = totalStaked - _amount;
            pendingUnstakes[msg.sender].push(
                Unstake({
                    amount: uint128(_amount),
                    releaseTime: uint64(block.timestamp + UNSTAKE_LOCKUP_PERIOD),
                    withdrawn: false
                })
            );
            // the pending unstakes are always at the tail of `pendingUnstakes[msg.sender]`
            // With this counter, we know how long the tail is, and we can iterate only the pending ones
            accountInfo.pendingUnstakesCount++;
            // if we reach totalStaked==0 due to an unstake, during an active period
            // we wrapup the rewards period so rewards in no-mans-land period are pushed forward
            if ((totalStaked == _amount) && (rewardsData.endDate > block.timestamp)) {
                uint256 pendingForDistribution = rewardsData.nonDistributedRewards();
                // the end Date is not altered, only the start date and the remaining rewards
                rewardsData.rewardsDeposited = uint128(pendingForDistribution);
                rewardsData.startDate = uint64(block.timestamp);
                rewardsData.lastReleasedAmount = 0;
            }
            emit Unstaked(msg.sender, _amount);
        }
        /// @notice  Allows an account to claim pending staking rewards
        /// @dev     The modifier triggers a rewards upate for msg.sender, 
        ///          so the `pendingRewards` are updated before sending the rewards
        function claimRewards() external updateRewards(msg.sender) {
            // the pendingRewards have just been upated in the `updateRewards` modifer, so this value is up-to-date
            uint256 pendingRewards = stakeInfo[msg.sender].pendingRewards;
            // delete to get some gas back
            delete stakeInfo[msg.sender].pendingRewards;
            stakeInfo[msg.sender].totalRewardsClaimed += pendingRewards;
            IERC20(stakedToken).safeTransfer(msg.sender, pendingRewards);
            emit ClaimedRewards(msg.sender, pendingRewards);
        }
        /// @notice  This withdraws ALL pending unstakes that have fulfilled the lockup period.
        /// @dev     The modifier updating rewards has no effect in the withdrawn tokens, but better keep the system updated as frequently as possible
        function withdraw() external updateRewards(msg.sender) {
            uint256 totalToWithdraw;
            uint256 stakesWithdrawn;
            uint256 length = pendingUnstakes[msg.sender].length;
            uint256 firstPendingUnstake = length - stakeInfo[msg.sender].pendingUnstakesCount;
            // here we iterate since he first unstake that hasn't been withdrawn yet, and we "break" when we find one that hasn't been released yet
            // this ensures that we never iterate unstakes that have been already withdrawn
            for (uint256 i = firstPendingUnstake; i < length; i++) {
                Unstake storage pendingUnstake = pendingUnstakes[msg.sender][i];
                // as soon as we hit a unstake that is not ready yet, we know that all the following ones are not ready either,
                // because the unstakes are sorted by `releaseTime`
                if (pendingUnstake.releaseTime > block.timestamp) break;
                pendingUnstake.withdrawn = true;
                stakesWithdrawn++;
                totalToWithdraw += pendingUnstake.amount;
            }
            if (totalToWithdraw > 0) {
                // update the storage count only after the loop
                stakeInfo[msg.sender].pendingUnstakesCount -= stakesWithdrawn;
                IERC20(stakedToken).safeTransfer(msg.sender, totalToWithdraw);
                emit Withdrawn(msg.sender, totalToWithdraw);
            }
        }
        /// @notice updates the rewards release, and the global rewards per token 
        /// @dev    The rewards release update is triggered by all functions with the updateRewards modifier.
        /// @dev    But this function allows to manually triggering the rewards update, to minimize the step sizes
        function updateRewardsRelease() external {
            _updateGlobalRewardsPerStakedToken();
        }
        //////////////////////// VIEW FUNCTIONS ////////////////////////
        /// @notice returns the sum of all active pending unstakes that can be withdrawn now
        /// @dev see withdraw() for more info about the for-loop iteration boundaries
        function getWithdrawableAmount(address account) public view returns (uint256 totalWithdrawable) {
            uint256 length = pendingUnstakes[account].length;
            uint256 firstPendingUnstake = length - stakeInfo[account].pendingUnstakesCount;
            for (uint256 i = firstPendingUnstake; i < length; i++) {
                if (pendingUnstakes[account][i].releaseTime > block.timestamp) break;
                totalWithdrawable += pendingUnstakes[account][i].amount;
            }
        }
        /// @notice returns the sum of all active pending unstakes of `account` that cannot be withdrawn yet
        /// @dev see withdraw() for more info about the for-loop iteration boundaries
        function getLockedUnstakedAmount(address account) public view returns (uint256 totalLocked) {
            uint256 length = pendingUnstakes[account].length;
            if (length == 0) return 0;
            uint256 firstPendingUnstake = length - stakeInfo[account].pendingUnstakesCount;
            if (firstPendingUnstake == length) return 0; // all unstakes are withdrawable (or there are no unstakes at all
            // here we start iterating from the tail, and go backwards until we hit an unstake that is already withdrawable
            for (uint256 i = length; i > firstPendingUnstake; i--) {
                uint256 index = i - 1;
                if (pendingUnstakes[account][index].releaseTime <= block.timestamp) break;
                totalLocked += pendingUnstakes[account][index].amount;
            }
            return totalLocked;
        }
        /// @notice returns the sum of all staked tokens for `account`
        function getStakingBalance(address account) public view returns (uint256) {
            return stakeInfo[account].balance;
        }
        // @notice returns the sum of all historical rewards claimed plus the pending rewards.
        function getHistoricalRewardsEarned(address account) public view returns (uint256) {
            return stakeInfo[account].totalRewardsClaimed + getClaimableRewards(account);
        }
        /// @notice  returns the amount of rewards that would be received by `account` if he/she called `claimRewards()`
        /// @dev     includes an estimation of the pending linear release since the last time it was updated,
        //          because we cannot run the updateRewards modifier here as it is a view function
        function getClaimableRewards(address account) public view returns (uint256 estimatedRewards) {
            // the below calculations would revert when the array has no elements
            if (!rewardsData.isInitialized()) return 0;
            StakeInfo storage accountInfo = stakeInfo[account];
            // here we estimate the increase in globalRewardsPerStaked token if the pending rewards were released
            uint256 globalRewardPerToken = globalRewardsPerStakedToken;
            // only update globalRewardPerToken if there are staked tokens to distribute among
            uint256 estimatedRewardsFromUnreleased;
            if (totalStakedTokens > 0) {
                globalRewardPerToken += (rewardsData.releasedSinceLastUpdate() * PRECISION) / totalStakedTokens;
                // this estimated rewards are only relevant if there is any balance in the account (and then necessarily totalStakeTokens>0)
                estimatedRewardsFromUnreleased =
                    (accountInfo.balance * (globalRewardPerToken - accountInfo.updatedRewardsPerStakedToken)) / PRECISION;
            }
            return estimatedRewardsFromUnreleased + accountInfo.pendingRewards;
        }
        /// @notice returns an array of Unstake objects that haven't been withdrawn yet.
        /// @dev    This includes the ones that are in lockup period, and the ones that are already withdrawable
        /// @dev    The unstakes that have been already withdrawn are not included here.
        /// @dev    Note that the withdrawn field in the Unstake struct will always be `false` in these ones
        /// @dev    The length of the array can be read in advace with `unstakeInfo[account].pendingUnstakesCount`
        function getPendingUnstakes(address account) public view returns (Unstake[] memory unstakes) {
            uint256 length = pendingUnstakes[account].length;
            uint256 pendingUnstakesCount = stakeInfo[account].pendingUnstakesCount;
            uint256 firstPendingUnstake = length - pendingUnstakesCount;
            // the lenght of the output arrays is known before iteration
            unstakes = new Unstake[](pendingUnstakesCount);
            // item `firstPendinUnstake` goes into index=0 of the output array
            for (uint256 i = firstPendingUnstake; i < length; i++) {
                unstakes[i - firstPendingUnstake] = Unstake({
                    amount: pendingUnstakes[account][i].amount,
                    releaseTime: pendingUnstakes[account][i].releaseTime,
                    withdrawn: false // because we are only returning the pending ones
                });
            }
        }
        /// @notice     gives an approximated APR for the current rewards period and the current totalStakedTokens
        /// @dev        This is only a rough estimation which makes the following assumptions:
        ///             - It uses the current period rewards and duration: as soon as a new period is created, the APR can change.
        ///             - It uses the current totalStakedTokens: the APR will change with every stake/unstake
        ///             - If the period duration is 0, or there are no staked tokens, this function returns APR=0
        function getEstimatedAPR() public view returns (uint256) {
            return rewardsData.estimatedAPR(totalStakedTokens);
        }
        //////////////////////// INTERNAL FUNCTIONS ////////////////////////
        /// @notice     Triggers a release of the linear rewards distribution since the last update, 
        //              and with the released rewards, the global rewards per token is updated
        /// @dev        If there are no staked tokens, there is no update
        function _updateGlobalRewardsPerStakedToken() internal returns (uint256 globalRewardPerToken) {
            // cache storage variables for gas savings
            uint256 totalTokens = totalStakedTokens;
            globalRewardPerToken = globalRewardsPerStakedToken;
            // if there are no staked tokens, there is no distribution, so the global rewards per token is not updated
            if (totalTokens == 0) {
                if (rewardsData.endDate > block.timestamp) {
                    // push the start date forward until there are staked tokens
                    rewardsData.startDate = uint64(block.timestamp);
                }
                return globalRewardPerToken;
            }
            // The difference between the last distribution and the released tokens following the linear release
            // is what needs to be distributed in this update
            uint256 released = rewardsData.releasedSinceLastUpdate();
            // The rounding error here will be included in the next time `released` is calculated
            uint256 extraRewardsPerToken = (released * PRECISION) / totalTokens;
            // globalRewardsPerStakedToken is always incremented, it can never go down
            globalRewardPerToken += extraRewardsPerToken;
            // update storage
            globalRewardsPerStakedToken = globalRewardPerToken;
            // the actual amount of distributed tokens is (extraRewardsPerToken * totalTokens) / PRECISION, 
            // however, as this result is rounded down, it can break some critical invariants by dust amounts. 
            // Instead we store the last released amount, knowing that the difference between released and actually distributed
            // will be lost as dust wei in the contract
            // trying to keep track of those dust amounts would require more storage operations 
            // and are not be worth the gas spent
            rewardsData.lastReleasedAmount += uint128(released);
            emit GlobalRewardsPerStakedTokenUpdated(released, globalRewardPerToken);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
    pragma solidity ^0.8.20;
    import {IERC20} from "../IERC20.sol";
    import {IERC20Permit} from "../extensions/IERC20Permit.sol";
    import {Address} from "../../../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;
        /**
         * @dev An operation with an ERC20 token failed.
         */
        error SafeERC20FailedOperation(address token);
        /**
         * @dev Indicates a failed `decreaseAllowance` request.
         */
        error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
        /**
         * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
         * non-reverting calls are assumed to be successful.
         */
        function safeTransfer(IERC20 token, address to, uint256 value) internal {
            _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
        }
        /**
         * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
         * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
         */
        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
            _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
        }
        /**
         * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
         * non-reverting calls are assumed to be successful.
         */
        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
            uint256 oldAllowance = token.allowance(address(this), spender);
            forceApprove(token, spender, oldAllowance + value);
        }
        /**
         * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
         * value, non-reverting calls are assumed to be successful.
         */
        function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
            unchecked {
                uint256 currentAllowance = token.allowance(address(this), spender);
                if (currentAllowance < requestedDecrease) {
                    revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
                }
                forceApprove(token, spender, currentAllowance - requestedDecrease);
            }
        }
        /**
         * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
         * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
         * to be set to zero before setting it to a non-zero value, such as USDT.
         */
        function forceApprove(IERC20 token, address spender, uint256 value) internal {
            bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
            if (!_callOptionalReturnBool(token, approvalCall)) {
                _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
                _callOptionalReturn(token, approvalCall);
            }
        }
        /**
         * @dev 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);
            if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
                revert SafeERC20FailedOperation(address(token));
            }
        }
        /**
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
         * on the return value: the return value is optional (but if data is returned, it must not be false).
         * @param token The token targeted by the call.
         * @param data The call data (encoded using abi.encode or one of its variants).
         *
         * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
         */
        function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
            // 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 cannot use {Address-functionCall} here since this should return false
            // and not revert is the subcall reverts.
            (bool success, bytes memory returndata) = address(token).call(data);
            return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
    pragma solidity ^0.8.20;
    /**
     * @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 value of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the value of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves a `value` amount of tokens from the caller's account to `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, uint256 value) external returns (bool);
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
        /**
         * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
         * caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 value) external returns (bool);
        /**
         * @dev Moves a `value` amount of tokens from `from` to `to` using the
         * allowance mechanism. `value` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address from, address to, uint256 value) external returns (bool);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
    pragma solidity ^0.8.20;
    import {Context} from "../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.
     *
     * The initial owner is set to the address provided by the deployer. 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;
        /**
         * @dev The caller account is not authorized to perform an operation.
         */
        error OwnableUnauthorizedAccount(address account);
        /**
         * @dev The owner is not a valid owner account. (eg. `address(0)`)
         */
        error OwnableInvalidOwner(address owner);
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
         */
        constructor(address initialOwner) {
            if (initialOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(initialOwner);
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            if (owner() != _msgSender()) {
                revert OwnableUnauthorizedAccount(_msgSender());
            }
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling 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 {
            if (newOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _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 (last updated v5.0.0) (access/Ownable2Step.sol)
    pragma solidity ^0.8.20;
    import {Ownable} from "./Ownable.sol";
    /**
     * @dev Contract module which provides access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is specified at deployment time in the constructor for `Ownable`. This
     * can later be changed with {transferOwnership} and {acceptOwnership}.
     *
     * This module is used through inheritance. It will make available all functions
     * from parent (Ownable).
     */
    abstract contract Ownable2Step is Ownable {
        address private _pendingOwner;
        event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Returns the address of the pending owner.
         */
        function pendingOwner() public view virtual returns (address) {
            return _pendingOwner;
        }
        /**
         * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual override onlyOwner {
            _pendingOwner = newOwner;
            emit OwnershipTransferStarted(owner(), newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual override {
            delete _pendingOwner;
            super._transferOwnership(newOwner);
        }
        /**
         * @dev The new owner accepts the ownership transfer.
         */
        function acceptOwnership() public virtual {
            address sender = _msgSender();
            if (pendingOwner() != sender) {
                revert OwnableUnauthorizedAccount(sender);
            }
            _transferOwnership(sender);
        }
    }
    pragma solidity 0.8.20;
    struct RewardsPeriod {
        // amount of rewards to be distributed linearly until the end of the period. // units: wei
        uint128 rewardsDeposited;
        // released amount in the last update // units: wei
        uint128 lastReleasedAmount;
        // timestamp when the period starts
        uint64 startDate;
        // timestamp when the period ends
        uint64 endDate;
    }
    library RewardsLogic {
        using RewardsLogic for RewardsPeriod;
        uint256 public constant PRECISSION = 1e18;
        /// @notice Reward tokens that have been released in this period according to the linear release
        function releasedRewardsSincePeriodStarted(RewardsPeriod storage self)
            internal
            view
            returns (uint256 releasedAmount)
        {
            // once the end date has passed all rewards are released
            if (block.timestamp > self.endDate) return self.rewardsDeposited;
            // before the period starts, no rewards are released
            if (block.timestamp < self.startDate) return 0;
            // between start and end, there is a linear release of the rewardsDeposited
            return (self.rewardsDeposited * (block.timestamp - self.startDate)) / (self.endDate - self.startDate);
        }
        /// @notice difference between the released amount according to the linear release, and the total released amount up to last update
        function releasedSinceLastUpdate(RewardsPeriod storage self) internal view returns (uint256 releasedAmount) {
            return self.releasedRewardsSincePeriodStarted() - self.lastReleasedAmount;
        }
        /// @notice This returns the value of rewards that haven't been distributed in a storage operation.
        /// @dev It does not take into account potential amounts that
        //      might be released since the last update until now.
        function nonDistributedRewards(RewardsPeriod storage self) internal view returns (uint256 pendingToDistribute) {
            return self.rewardsDeposited - self.lastReleasedAmount;
        }
        /// @dev if endDate==0 it means that no rewards have been deposited yet any time
        /// @notice determines if there was at least one rewards deposit
        function isInitialized(RewardsPeriod storage self) internal view returns (bool) {
            return self.endDate > 0;
        }
        /// @notice This estimates the APR of the current period for the CURRENT TOTAL STAKED
        /// @dev This assumes that the totalStaked is constant over the entire period, which is of course a very relaxed assumption.
        /// @dev This therefore only provides a snapshot of the APR in this moment for the current totalStaked
        /// @dev units: ratio APR scaled up by PRECISION. Examples:
        ///         - for 5% APR, the function would return 0.05 * 1e18.
        ///         - for 100% APR, the function would return 1e18.
        function estimatedAPR(RewardsPeriod storage self, uint256 totalStaked) internal view returns (uint256) {
            // If there are no staked tokens, nobody is getting rewards, so APR is 0.
            if (totalStaked == 0) return 0;
            uint256 periodDuration = self.endDate - self.startDate;
            // This can only happen when no rewards have been distributed yet, in which case APR is also 0
            if (periodDuration == 0) return 0;
            return (PRECISSION * self.rewardsDeposited * 365 days) / (totalStaked * periodDuration);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     *
     * ==== Security Considerations
     *
     * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
     * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
     * considered as an intention to spend the allowance in any specific way. The second is that because permits have
     * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
     * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
     * generally recommended is:
     *
     * ```solidity
     * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
     *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
     *     doThing(..., value);
     * }
     *
     * function doThing(..., uint256 value) public {
     *     token.safeTransferFrom(msg.sender, address(this), value);
     *     ...
     * }
     * ```
     *
     * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
     * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
     * {SafeERC20-safeTransferFrom}).
     *
     * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
     * contracts should have entry points that don't rely on permit.
     */
    interface IERC20Permit {
        /**
         * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
         * given ``owner``'s signed approval.
         *
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
         * ordering also apply here.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `deadline` must be a timestamp in the future.
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
         * over the EIP712-formatted function arguments.
         * - the signature must use ``owner``'s current nonce (see {nonces}).
         *
         * For more information on the signature format, see the
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
         * section].
         *
         * CAUTION: See Security Considerations above.
         */
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
        /**
         * @dev Returns the current nonce for `owner`. This value must be
         * included whenever a signature is generated for {permit}.
         *
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
         * prevents a signature from being used multiple times.
         */
        function nonces(address owner) external view returns (uint256);
        /**
         * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev The ETH balance of the account is not enough to perform the operation.
         */
        error AddressInsufficientBalance(address account);
        /**
         * @dev There's no code at `target` (it is not a contract).
         */
        error AddressEmptyCode(address target);
        /**
         * @dev A call to an address target failed. The target may have reverted.
         */
        error FailedInnerCall();
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            if (address(this).balance < amount) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, ) = recipient.call{value: amount}("");
            if (!success) {
                revert FailedInnerCall();
            }
        }
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason or custom error, it is bubbled
         * up by this function (like regular Solidity function calls). However, if
         * the call reverted with no returned reason, this function reverts with a
         * {FailedInnerCall} error.
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            if (address(this).balance < value) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
         * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
         * unsuccessful call.
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata
        ) internal view returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                // only check if target is a contract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                if (returndata.length == 0 && target.code.length == 0) {
                    revert AddressEmptyCode(target);
                }
                return returndata;
            }
        }
        /**
         * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
         * revert reason or with a default {FailedInnerCall} error.
         */
        function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                return returndata;
            }
        }
        /**
         * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
         */
        function _revert(bytes memory returndata) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert FailedInnerCall();
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    pragma solidity ^0.8.20;
    /**
     * @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;
        }
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    

    File 2 of 2: PinLink
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (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 Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling 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 (interfaces/IERC20.sol)
    pragma solidity ^0.8.0;
    import "../token/ERC20/IERC20.sol";
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/draft-IERC20Permit.sol)
    pragma solidity ^0.8.0;
    // EIP-2612 is Final as of 2022-11-01. This file is deprecated.
    import "./IERC20Permit.sol";
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     */
    interface IERC20Permit {
        /**
         * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
         * given ``owner``'s signed approval.
         *
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
         * ordering also apply here.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `deadline` must be a timestamp in the future.
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
         * over the EIP712-formatted function arguments.
         * - the signature must use ``owner``'s current nonce (see {nonces}).
         *
         * For more information on the signature format, see the
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
         * section].
         */
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
        /**
         * @dev Returns the current nonce for `owner`. This value must be
         * included whenever a signature is generated for {permit}.
         *
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
         * prevents a signature from being used multiple times.
         */
        function nonces(address owner) external view returns (uint256);
        /**
         * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.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 (last updated v4.9.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
         *
         * Furthermore, `isContract` will also return true if the target contract within
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
         * which only has an effect at the end of a transaction.
         * ====
         *
         * [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://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.0/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 functionCallWithValue(target, data, 0, "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");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, 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) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, 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) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
         *
         * _Available since v4.8._
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            if (success) {
                if (returndata.length == 0) {
                    // only check isContract if the call was successful and the return data is empty
                    // otherwise we already know that it was a contract
                    require(isContract(target), "Address: call to non-contract");
                }
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
        /**
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason or 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 {
                _revert(returndata, errorMessage);
            }
        }
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-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 (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;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
    pragma solidity ^0.8.0;
    /**
     * @title Counters
     * @author Matt Condon (@shrugs)
     * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
     * of elements in a mapping, issuing ERC721 ids, or counting request ids.
     *
     * Include with `using Counters for Counters.Counter;`
     */
    library Counters {
        struct Counter {
            // This variable should never be directly accessed by users of the library: interactions must be restricted to
            // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
            // this feature: see https://github.com/ethereum/solidity/issues/4637
            uint256 _value; // default: 0
        }
        function current(Counter storage counter) internal view returns (uint256) {
            return counter._value;
        }
        function increment(Counter storage counter) internal {
            unchecked {
                counter._value += 1;
            }
        }
        function decrement(Counter storage counter) internal {
            uint256 value = counter._value;
            require(value > 0, "Counter: decrement overflow");
            unchecked {
                counter._value = value - 1;
            }
        }
        function reset(Counter storage counter) internal {
            counter._value = 0;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
    pragma solidity ^0.8.0;
    import "../Strings.sol";
    /**
     * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
     *
     * These functions can be used to verify that a message was signed by the holder
     * of the private keys of a given address.
     */
    library ECDSA {
        enum RecoverError {
            NoError,
            InvalidSignature,
            InvalidSignatureLength,
            InvalidSignatureS,
            InvalidSignatureV // Deprecated in v4.8
        }
        function _throwError(RecoverError error) private pure {
            if (error == RecoverError.NoError) {
                return; // no error: do nothing
            } else if (error == RecoverError.InvalidSignature) {
                revert("ECDSA: invalid signature");
            } else if (error == RecoverError.InvalidSignatureLength) {
                revert("ECDSA: invalid signature length");
            } else if (error == RecoverError.InvalidSignatureS) {
                revert("ECDSA: invalid signature 's' value");
            }
        }
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature` or error string. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         *
         * Documentation for signature generation:
         * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
         * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
            if (signature.length == 65) {
                bytes32 r;
                bytes32 s;
                uint8 v;
                // ecrecover takes the signature parameters, and the only way to get them
                // currently is to use assembly.
                /// @solidity memory-safe-assembly
                assembly {
                    r := mload(add(signature, 0x20))
                    s := mload(add(signature, 0x40))
                    v := byte(0, mload(add(signature, 0x60)))
                }
                return tryRecover(hash, v, r, s);
            } else {
                return (address(0), RecoverError.InvalidSignatureLength);
            }
        }
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature`. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         */
        function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, signature);
            _throwError(error);
            return recovered;
        }
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
         *
         * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
        /**
         * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
         *
         * _Available since v4.2._
         */
        function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, r, vs);
            _throwError(error);
            return recovered;
        }
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
         * `r` and `s` signature fields separately.
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
            // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
            // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
            // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
            // signatures from current libraries generate a unique signature with an s-value in the lower half order.
            //
            // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
            // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
            // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
            // these malleable signatures as well.
            if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                return (address(0), RecoverError.InvalidSignatureS);
            }
            // If the signature is valid (and not malleable), return the signer address
            address signer = ecrecover(hash, v, r, s);
            if (signer == address(0)) {
                return (address(0), RecoverError.InvalidSignature);
            }
            return (signer, RecoverError.NoError);
        }
        /**
         * @dev Overload of {ECDSA-recover} that receives the `v`,
         * `r` and `s` signature fields separately.
         */
        function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
            _throwError(error);
            return recovered;
        }
        /**
         * @dev Returns an Ethereum Signed Message, created from a `hash`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
            // 32 is the length in bytes of hash,
            // enforced by the type signature above
            /// @solidity memory-safe-assembly
            assembly {
                mstore(0x00, "\\x19Ethereum Signed Message:\
    32")
                mstore(0x1c, hash)
                message := keccak256(0x00, 0x3c)
            }
        }
        /**
         * @dev Returns an Ethereum Signed Message, created from `s`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
    ", Strings.toString(s.length), s));
        }
        /**
         * @dev Returns an Ethereum Signed Typed Data, created from a
         * `domainSeparator` and a `structHash`. This produces hash corresponding
         * to the one signed with the
         * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
         * JSON-RPC method as part of EIP-712.
         *
         * See {recover}.
         */
        function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
            /// @solidity memory-safe-assembly
            assembly {
                let ptr := mload(0x40)
                mstore(ptr, "\\x19\\x01")
                mstore(add(ptr, 0x02), domainSeparator)
                mstore(add(ptr, 0x22), structHash)
                data := keccak256(ptr, 0x42)
            }
        }
        /**
         * @dev Returns an Ethereum Signed Data with intended validator, created from a
         * `validator` and `data` according to the version 0 of EIP-191.
         *
         * See {recover}.
         */
        function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\\x19\\x00", validator, data));
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Standard math utilities missing in the Solidity language.
     */
    library Math {
        enum Rounding {
            Down, // Toward negative infinity
            Up, // Toward infinity
            Zero // Toward zero
        }
        /**
         * @dev Returns the largest of two numbers.
         */
        function max(uint256 a, uint256 b) internal pure returns (uint256) {
            return a > b ? a : b;
        }
        /**
         * @dev Returns the smallest of two numbers.
         */
        function min(uint256 a, uint256 b) internal pure returns (uint256) {
            return a < b ? a : b;
        }
        /**
         * @dev Returns the average of two numbers. The result is rounded towards
         * zero.
         */
        function average(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b) / 2 can overflow.
            return (a & b) + (a ^ b) / 2;
        }
        /**
         * @dev Returns the ceiling of the division of two numbers.
         *
         * This differs from standard division with `/` in that it rounds up instead
         * of rounding down.
         */
        function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b - 1) / b can overflow on addition, so we distribute.
            return a == 0 ? 0 : (a - 1) / b + 1;
        }
        /**
         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
         * with further edits by Uniswap Labs also under MIT license.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
            unchecked {
                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                // variables such that product = prod1 * 2^256 + prod0.
                uint256 prod0; // Least significant 256 bits of the product
                uint256 prod1; // Most significant 256 bits of the product
                assembly {
                    let mm := mulmod(x, y, not(0))
                    prod0 := mul(x, y)
                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                }
                // Handle non-overflow cases, 256 by 256 division.
                if (prod1 == 0) {
                    // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                    // The surrounding unchecked block does not change this fact.
                    // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                    return prod0 / denominator;
                }
                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                require(denominator > prod1, "Math: mulDiv overflow");
                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////
                // Make division exact by subtracting the remainder from [prod1 prod0].
                uint256 remainder;
                assembly {
                    // Compute remainder using mulmod.
                    remainder := mulmod(x, y, denominator)
                    // Subtract 256 bit number from 512 bit number.
                    prod1 := sub(prod1, gt(remainder, prod0))
                    prod0 := sub(prod0, remainder)
                }
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                // See https://cs.stackexchange.com/q/138556/92363.
                // Does not overflow because the denominator cannot be zero at this stage in the function.
                uint256 twos = denominator & (~denominator + 1);
                assembly {
                    // Divide denominator by twos.
                    denominator := div(denominator, twos)
                    // Divide [prod1 prod0] by twos.
                    prod0 := div(prod0, twos)
                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                    twos := add(div(sub(0, twos), twos), 1)
                }
                // Shift in bits from prod1 into prod0.
                prod0 |= prod1 * twos;
                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                // four bits. That is, denominator * inv = 1 mod 2^4.
                uint256 inverse = (3 * denominator) ^ 2;
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                // in modular arithmetic, doubling the correct bits in each step.
                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                inverse *= 2 - denominator * inverse; // inverse mod 2^256
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                // is no longer required.
                result = prod0 * inverse;
                return result;
            }
        }
        /**
         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
            uint256 result = mulDiv(x, y, denominator);
            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                result += 1;
            }
            return result;
        }
        /**
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
         *
         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
         */
        function sqrt(uint256 a) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
            //
            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
            //
            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
            //
            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
            uint256 result = 1 << (log2(a) >> 1);
            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
            // into the expected uint128 result.
            unchecked {
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                return min(result, a / result);
            }
        }
        /**
         * @notice Calculates sqrt(a), following the selected rounding direction.
         */
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = sqrt(a);
                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 2, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 128;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 64;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 32;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 16;
                }
                if (value >> 8 > 0) {
                    value >>= 8;
                    result += 8;
                }
                if (value >> 4 > 0) {
                    value >>= 4;
                    result += 4;
                }
                if (value >> 2 > 0) {
                    value >>= 2;
                    result += 2;
                }
                if (value >> 1 > 0) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log2(value);
                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 10, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >= 10 ** 64) {
                    value /= 10 ** 64;
                    result += 64;
                }
                if (value >= 10 ** 32) {
                    value /= 10 ** 32;
                    result += 32;
                }
                if (value >= 10 ** 16) {
                    value /= 10 ** 16;
                    result += 16;
                }
                if (value >= 10 ** 8) {
                    value /= 10 ** 8;
                    result += 8;
                }
                if (value >= 10 ** 4) {
                    value /= 10 ** 4;
                    result += 4;
                }
                if (value >= 10 ** 2) {
                    value /= 10 ** 2;
                    result += 2;
                }
                if (value >= 10 ** 1) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log10(value);
                return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 256, rounded down, of a positive value.
         * Returns 0 if given 0.
         *
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
         */
        function log256(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 16;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 8;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 4;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 2;
                }
                if (value >> 8 > 0) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log256(value);
                return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)
    pragma solidity ^0.8.0;
    // CAUTION
    // This version of SafeMath should only be used with Solidity 0.8 or later,
    // because it relies on the compiler's built in overflow checks.
    /**
     * @dev Wrappers over Solidity's arithmetic operations.
     *
     * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
     * now has built in overflow checking.
     */
    library SafeMath {
        /**
         * @dev Returns the addition of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                uint256 c = a + b;
                if (c < a) return (false, 0);
                return (true, c);
            }
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                if (b > a) return (false, 0);
                return (true, a - b);
            }
        }
        /**
         * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                // benefit is lost if 'b' is also tested.
                // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                if (a == 0) return (true, 0);
                uint256 c = a * b;
                if (c / a != b) return (false, 0);
                return (true, c);
            }
        }
        /**
         * @dev Returns the division of two unsigned integers, with a division by zero flag.
         *
         * _Available since v3.4._
         */
        function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                if (b == 0) return (false, 0);
                return (true, a / b);
            }
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
         *
         * _Available since v3.4._
         */
        function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                if (b == 0) return (false, 0);
                return (true, a % b);
            }
        }
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         *
         * - Addition cannot overflow.
         */
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            return a + b;
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         *
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            return a - b;
        }
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         *
         * - Multiplication cannot overflow.
         */
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            return a * b;
        }
        /**
         * @dev Returns the integer division of two unsigned integers, reverting on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator.
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            return a / b;
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * reverting when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
            return a % b;
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * CAUTION: This function is deprecated because it requires allocating memory for the error
         * message unnecessarily. For custom revert reasons use {trySub}.
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         *
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            unchecked {
                require(b <= a, errorMessage);
                return a - b;
            }
        }
        /**
         * @dev Returns the integer division of two unsigned integers, reverting with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            unchecked {
                require(b > 0, errorMessage);
                return a / b;
            }
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * reverting with custom message when dividing by zero.
         *
         * CAUTION: This function is deprecated because it requires allocating memory for the error
         * message unnecessarily. For custom revert reasons use {tryMod}.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            unchecked {
                require(b > 0, errorMessage);
                return a % b;
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Standard signed math utilities missing in the Solidity language.
     */
    library SignedMath {
        /**
         * @dev Returns the largest of two signed numbers.
         */
        function max(int256 a, int256 b) internal pure returns (int256) {
            return a > b ? a : b;
        }
        /**
         * @dev Returns the smallest of two signed numbers.
         */
        function min(int256 a, int256 b) internal pure returns (int256) {
            return a < b ? a : b;
        }
        /**
         * @dev Returns the average of two signed numbers without overflow.
         * The result is rounded towards zero.
         */
        function average(int256 a, int256 b) internal pure returns (int256) {
            // Formula from the book "Hacker's Delight"
            int256 x = (a & b) + ((a ^ b) >> 1);
            return x + (int256(uint256(x) >> 255) & (a ^ b));
        }
        /**
         * @dev Returns the absolute unsigned value of a signed value.
         */
        function abs(int256 n) internal pure returns (uint256) {
            unchecked {
                // must be unchecked in order to support `n = type(int256).min`
                return uint256(n >= 0 ? n : -n);
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
    pragma solidity ^0.8.0;
    import "./math/Math.sol";
    import "./math/SignedMath.sol";
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            unchecked {
                uint256 length = Math.log10(value) + 1;
                string memory buffer = new string(length);
                uint256 ptr;
                /// @solidity memory-safe-assembly
                assembly {
                    ptr := add(buffer, add(32, length))
                }
                while (true) {
                    ptr--;
                    /// @solidity memory-safe-assembly
                    assembly {
                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                    }
                    value /= 10;
                    if (value == 0) break;
                }
                return buffer;
            }
        }
        /**
         * @dev Converts a `int256` to its ASCII `string` decimal representation.
         */
        function toString(int256 value) internal pure returns (string memory) {
            return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            unchecked {
                return toHexString(value, Math.log256(value) + 1);
            }
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
        /**
         * @dev Returns true if the two strings are equal.
         */
        function equal(string memory a, string memory b) internal pure returns (bool) {
            return keccak256(bytes(a)) == keccak256(bytes(b));
        }
    }
    pragma solidity >=0.5.0;
    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;
    }
    pragma solidity >=0.5.0;
    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;
    }
    pragma solidity >=0.6.2;
    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);
    }
    pragma solidity >=0.6.2;
    import './IUniswapV2Router01.sol';
    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;
    }
    pragma solidity >=0.5.0;
    interface IWETH {
        function deposit() external payable;
        function transfer(address to, uint value) external returns (bool);
        function withdraw(uint) external;
    }
    /**
     * A Multisend interface
     * SPDX-License-Identifier: MIT
     */
    pragma solidity ^0.8.15;
    interface IMultisend {
        /// @notice Allows a multi-send to save on gas
        /// @param addr array of addresses to send to
        /// @param val array of values to go with addresses
        function multisend(address[] calldata addr, uint256[] calldata val) external;
        /// @notice Allows a multi-send to save on gas on behalf of someone - need approvals
        /// @param sender sender to use - must be approved to spend
        /// @param addrRecipients array of addresses to send to
        /// @param vals array of values to go with addresses
        function multisendFrom(address sender, address[] calldata addrRecipients, uint256[] calldata vals) external;
    }//SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.15;
    import "@openzeppelin/contracts/utils/Address.sol";
    import "@openzeppelin/contracts/utils/Context.sol";
    import "@openzeppelin/contracts/interfaces/IERC20.sol";
    import "@openzeppelin/contracts/utils/math/SafeMath.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";
    import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
    import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
    import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
    import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol";
    import "@openzeppelin/contracts/utils/math/SafeMath.sol";
    import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
    import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
    import "@openzeppelin/contracts/utils/Counters.sol";
    import "./IMultisend.sol";
    contract PinLink is Context, IERC20, Ownable, IMultisend, IERC20Permit {
        event Bought(address indexed buyer, uint256 amount);
        event Sold(address indexed seller, uint256 amount);
        using SafeMath for uint256;
        // Constants
        string private constant _name = "PinLink";
        string private constant _symbol = "Pin";
        // Standard decimals
        uint8 private constant _decimals = 18;
        uint256 private constant totalTokens = 100000000000000000000000000;
        // Mappings
        mapping(address => uint256) private balances;
        mapping(address => mapping(address => uint256)) private _allowances;
        
        /** START OF EIP2612/EIP712 VARS */
        using Counters for Counters.Counter;
        mapping(address => Counters.Counter) private _nonces;
        /* solhint-disable var-name-mixedcase */
        // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
        // invalidate the cached domain separator if the chain id changes.
        bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
        uint256 private immutable _CACHED_CHAIN_ID;
        address private immutable _CACHED_THIS;
        bytes32 private immutable _HASHED_NAME;
        bytes32 private immutable _HASHED_VERSION;
        bytes32 private immutable _TYPE_HASH;
        /** END OF EIP2612/EIP712 VARS */
        struct mappingStructs {
            bool _isExcludedFromFee;
            uint32 _lastTxBlock;
            uint32 botBlock;
            bool isLPPair;
        }
        struct InitialData {
            uint32 buyTax;
            uint32 sellTax;
            uint32 maxWalletDiv;
            uint32 maxTxDiv;
            uint32 maxSwapDivisor;
        }
        struct TaxWallet {
            address wallet;
            uint32 ratio;
        }
        mapping(address => mappingStructs) mappedAddresses;
        // Arrays
        TaxWallet[] private taxWallets;
        // Global variables
        // Block of 256 bits
        address public dividendTracker;
        uint32 private openBlock;
        uint32 private sellTax;
        uint32 private buyTax;
        // Storage block closed
        // Block of 256 bits
        address private _controller;
        uint32 private maxTxRatio;
        uint32 private maxWalletRatio;
        bool private tradingOpen;
        bool private inSwap = false;
        bool private swapEnabled = false;
        bool disableAddToBlocklist = false;
        // Storage block closed
        // Block of 256 bits
        address private devWallet;
        uint32 ethSendThresholdDivisor = 1000;
        uint32 private totalRatio;
        uint32 private taxSwapDivisor;
        // Block of 256 bits
        uint64 lastTxBlockNumber;
        IUniswapV2Router02 private uniswapV2Router;
        modifier onlyERC20Controller() {
            require(
                _msgSender() == _controller,
                "Caller is not the ERC20 controller."
            );
            _;
        }
        modifier onlyDev() {
            require(_msgSender() == devWallet, "Only developer can set this.");
            _;
        }
        constructor(
            address controller,
            address dev,
            InitialData memory id,
            TaxWallet[] memory wallets
        ) {
            // Set up EIP712
            bytes32 hashedName = keccak256(bytes(_name));
            bytes32 hashedVersion = keccak256(bytes("1"));
            bytes32 typeHash = keccak256(
                "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
            );
            _HASHED_NAME = hashedName;
            _HASHED_VERSION = hashedVersion;
            _CACHED_CHAIN_ID = block.chainid;
            _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(
                typeHash,
                hashedName,
                hashedVersion
            );
            _CACHED_THIS = address(this);
            _TYPE_HASH = typeHash;
            // ERC20 controller
            _controller = payable(controller);
            devWallet = dev;
            buyTax = id.buyTax;
            sellTax = id.sellTax;
            taxSwapDivisor = id.maxSwapDivisor;
            maxTxRatio = id.maxTxDiv;
            maxWalletRatio = id.maxWalletDiv;
            mappedAddresses[_msgSender()] = mappingStructs({
                _isExcludedFromFee: true,
                _lastTxBlock: 0,
                botBlock: 0,
                isLPPair: false
            });
            mappedAddresses[address(this)] = mappingStructs({
                _isExcludedFromFee: true,
                _lastTxBlock: 0,
                botBlock: 0,
                isLPPair: false
            });
            // For instrumentation, we have to make this copy ourselves
            uint32 initialRatio = 0;
            for (uint256 i = 0; i < wallets.length; i++) {
                mappedAddresses[wallets[i].wallet] = mappingStructs({
                    _isExcludedFromFee: true,
                    _lastTxBlock: 0,
                    botBlock: 0,
                    isLPPair: false
                });
                initialRatio += wallets[i].ratio;
                // Copy across now as the "classic" non-IR compiler can't do this copy
                taxWallets.push(TaxWallet(wallets[i].wallet, wallets[i].ratio));
            }
            totalRatio = initialRatio;
            addTokens(_msgSender(), totalTokens);
            emit Transfer(address(0), _msgSender(), totalTokens);
        }
        function name() public pure returns (string memory) {
            return _name;
        }
        function symbol() public pure returns (string memory) {
            return _symbol;
        }
        function decimals() public pure returns (uint8) {
            return _decimals;
        }
        function totalSupply() public pure override returns (uint256) {
            return totalTokens;
        }
        function balanceOf(address account) public view override returns (uint256) {
            return balances[account];
        }
        function transfer(
            address recipient,
            uint256 amount
        ) public override returns (bool) {
            _transfer(_msgSender(), recipient, amount);
            return true;
        }
        function allowance(
            address owner,
            address spender
        ) public view override returns (uint256) {
            return _allowances[owner][spender];
        }
        function approve(
            address spender,
            uint256 amount
        ) public override returns (bool) {
            _approve(_msgSender(), spender, amount);
            return true;
        }
        function transferFrom(
            address sender,
            address recipient,
            uint256 amount
        ) public override returns (bool) {
            _transfer(sender, recipient, amount);
            _approve(
                sender,
                _msgSender(),
                _allowances[sender][_msgSender()].sub(
                    amount,
                    "ERC20: transfer amount exceeds allowance"
                )
            );
            return true;
        }
        /// @notice Starts trading. Only callable by owner.
        function openTrading() public onlyOwner {
            require(!tradingOpen, "Can't open trading that's already open.");
            IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(
                0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
            );
            uniswapV2Router = _uniswapV2Router;
            _approve(address(this), address(uniswapV2Router), totalTokens);
            address uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory())
                .createPair(address(this), _uniswapV2Router.WETH());
            uniswapV2Router.addLiquidityETH{value: address(this).balance}(
                address(this),
                balanceOf(address(this)),
                0,
                0,
                owner(),
                block.timestamp
            );
            swapEnabled = true;
            tradingOpen = true;
            // Add the pairs to the list
            mappedAddresses[uniswapV2Pair] = mappingStructs({
                _isExcludedFromFee: false,
                _lastTxBlock: 0,
                botBlock: 0,
                isLPPair: true
            });
        }
        function _approve(address owner, address spender, uint256 amount) private {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
        function _transfer(address from, address to, uint256 amount) private {
            require(from != address(0), "ERC20: transfer from the zero address");
            require(to != address(0), "ERC20: transfer to the zero address");
            require(amount > 0, "Transfer amount must be greater than zero");
            uint32 _taxAmt;
            bool isSell = false;
            if (
                from != owner() &&
                to != owner() &&
                from != address(this) &&
                !mappedAddresses[to]._isExcludedFromFee &&
                !mappedAddresses[from]._isExcludedFromFee
            ) {
                // Max tx check
                require(amount <= totalTokens / maxTxRatio, "Max tx exceeded.");
                // Buys
                if (
                    (mappedAddresses[from].isLPPair) &&
                    to != address(uniswapV2Router)
                ) {
                    _taxAmt = buyTax;
                    // Max wallet check
                    require(
                        balanceOf(to) + amount <= totalTokens / maxWalletRatio,
                        "Max wallet will be exceeded."
                    );
                } else if (
                    (mappedAddresses[to].isLPPair) &&
                    from != address(uniswapV2Router)
                ) {
                    isSell = true;
                    // Sells
                    // Don't check max wallet or you fuck up LP
                    // Check if last tx occurred this block - prevents sandwich attacks
                    // Sells
                    _taxAmt = sellTax;
                } else {
                    // No code to change transfer tax
                    _taxAmt = 0;
                    // Still check max wallet
                    require(
                        balanceOf(to) + amount <= totalTokens / maxWalletRatio,
                        "Max wallet will be exceeded."
                    );
                }
            } else {
                // Only make it here if it's from or to owner or from contract address.
                _taxAmt = 0;
            }
            _tokenTransfer(from, to, amount, _taxAmt, isSell);
        }
        function doTaxes(uint256 tokenAmount) private {
            if (lastTxBlockNumber != block.number) {
                // Tax not sold this block yet
                lastTxBlockNumber = uint64(block.number);
            } else {
                // Tax has been sold this block already, don't bother doing it again
                return;
            }
            // Reentrancy guard/stop infinite tax sells mainly
            inSwap = true;
            if (
                _allowances[address(this)][address(uniswapV2Router)] < tokenAmount
            ) {
                // Our approvals run low, redo it
                _approve(address(this), address(uniswapV2Router), totalTokens);
            }
            address[] memory path = new address[](2);
            path[0] = address(this);
            path[1] = uniswapV2Router.WETH();
            // Swap direct to WETH and let router unwrap
            uniswapV2Router.swapExactTokensForETH(
                tokenAmount,
                0,
                path,
                address(this),
                block.timestamp
            );
            sendETHToFee(address(this).balance);
            inSwap = false;
        }
        function sendETHToFee(uint256 amount) private {
            // This fixes gas reprice issues - reentrancy is not an issue as the fee wallets are trusted.
            for (uint256 i = 0; i < taxWallets.length; i++) {
                Address.sendValue(
                    payable(taxWallets[i].wallet),
                    (amount * taxWallets[i].ratio) / totalRatio
                );
            }
        }
        receive() external payable {}
        // Underlying transfer functions go here
        function _tokenTransfer(
            address sender,
            address recipient,
            uint256 amount,
            uint32 _taxAmt,
            bool isSell
        ) private {
            // Do taxes
            uint256 receiverAmount = amount;
            if (_taxAmt > 0) {
                
                uint256 taxAmount = calculateTaxesFee(amount, _taxAmt);
                receiverAmount = amount - taxAmount;
                addTokens(address(this), taxAmount);
                
                emit Transfer(sender, address(this), taxAmount);
            }
            // Only sell tokens on a sell, as we can't interfere on a buy
            if (isSell) {
                emit Sold(sender, amount);
                uint256 bal = balanceOf(address(this));
                // Swap a max of totalTokens/taxSwapDivisor, or the current balance
                if (bal > 0) {
                    if (bal > totalTokens / taxSwapDivisor) {
                        doTaxes(totalTokens / taxSwapDivisor);
                    } else {
                        doTaxes(bal);
                    }
                }
            } else {
                emit Bought(recipient, amount);
            }
            // Actually do token balances
            subtractTokens(sender, amount);
            addTokens(recipient, receiverAmount);
            // Emit transfer, because the specs say to
            emit Transfer(sender, recipient, receiverAmount);
        }
        /// @dev Does holder count maths
        function subtractTokens(address account, uint256 amount) private {
            balances[account] = balances[account] - amount;
        }
        function addTokens(address account, uint256 amount) private {
            balances[account] = balances[account] + amount;
        }
        function calculateTaxesFee(
            uint256 _amount,
            uint32 _taxAmt
        ) private pure returns (uint256 tax) {
            tax = (_amount * _taxAmt) / 100000;
        }
        /// @notice Sets an ETH send divisor. Only callable by owner.
        /// @param newDivisor the new divisor to set.
        function setEthSendDivisor(uint32 newDivisor) public onlyOwner {
            ethSendThresholdDivisor = newDivisor;
        }
        function addTaxWallet(TaxWallet calldata wall) external onlyOwner {
            taxWallets.push(wall);
            mappedAddresses[wall.wallet]._isExcludedFromFee = true;
            // Recalculate the ratio, as we're adding, just add that ratio on
            totalRatio += wall.ratio;
        }
        function removeTaxWallet(address wallet) external onlyOwner {
            mappedAddresses[wallet]._isExcludedFromFee = false;
            bool found = false;
            for (uint256 i = 0; i < taxWallets.length; i++) {
                if (taxWallets[i].wallet == wallet) {
                    // Fill this with the end
                    taxWallets[i] = taxWallets[taxWallets.length - 1];
                    taxWallets.pop();
                    found = true;
                }
            }
            require(found, "Not in tax list.");
            // Have to recalculate the entire ratio as we dunno what was removed
            uint32 initialRatio = 0;
            for (uint256 i = 0; i < taxWallets.length; i++) {
                initialRatio += taxWallets[i].ratio;
            }
            totalRatio = initialRatio;
        }
        /// @notice Changes ERC20 controller address. Only callable by dev.
        /// @param newWallet the address to set as the controller.
        function changeERC20Controller(address newWallet) external onlyDev {
            _controller = payable(newWallet);
        }
        /// @notice Allows new pairs to be added to the "watcher" code
        /// @param pair the address to add as the liquidity pair
        function addNewLPPair(address pair) external onlyOwner {
            mappedAddresses[pair].isLPPair = true;
        }
        /// @notice Irreversibly disables blocklist additions after launch has settled.
        /// @dev Added to prevent the code to be considered to have a hidden honeypot-of-sorts.
        function disableBlocklistAdd() external onlyOwner {
            disableAddToBlocklist = true;
        }
        /// @notice Sets an account exclusion or inclusion from fees.
        /// @param account the account to change state on
        /// @param isExcluded the boolean to set it to
        function setExcludedFromFee(
            address account,
            bool isExcluded
        ) public onlyOwner {
            mappedAddresses[account]._isExcludedFromFee = isExcluded;
        }
        /// @notice Sets the sell tax, out of 100000. Only callable by owner. Max of 20000.
        /// @param amount the tax out of 100000.
        function setSellTax(uint32 amount) external onlyOwner {
            require(amount <= 20000, "Maximum sell tax of 10%.");
            sellTax = amount;
        }
        function setBuyTax(uint32 amount) external onlyOwner {
            require(amount <= 20000, "Maximum buy tax of 10%.");
            buyTax = amount;
        }
        function setSwapDivisor(uint32 amount) external onlyOwner {
            require(amount <= 10000, "No lower than .01%.");
            taxSwapDivisor = amount;
        }
        function setMaxTxRatio(uint32 ratio) external onlyOwner {
            require(ratio < 10000, "No lower than .01%");
            maxTxRatio = ratio;
        }
        function setMaxWalletRatio(uint32 ratio) external onlyOwner {
            require(ratio < 1000, "No lower than .1%");
            maxWalletRatio = ratio;
        }
        /// @notice Returns if an account is excluded from fees.
        /// @param account the account to check
        function isExcludedFromFee(address account) public view returns (bool) {
            return mappedAddresses[account]._isExcludedFromFee;
        }
        // IMultisend implementation
        /// @notice Allows a multi-send to save on gas
        /// @param addr array of addresses to send to
        /// @param val array of values to go with addresses
        function multisend(
            address[] calldata addr,
            uint256[] calldata val
        ) external override {
            require(addr.length == val.length, "Muyltisend: Length mismatch.");
            for (uint i = 0; i < addr.length; i++) {
                // There's gas savings to be had to do this - we bypass top-level
                subtractTokens(_msgSender(), val[i]);
                addTokens(addr[i], val[i]);
                // Emit transfers, because the specs say to
                emit Transfer(_msgSender(), addr[i], val[i]);
            }
        }
        /// @notice Allows a multi-send to save on gas on behalf of someone - need approvals
        /// @param sender sender to use - must be approved to spend
        /// @param addrRecipients array of addresses to send to
        /// @param vals array of values to go with addresses
        function multisendFrom(
            address sender,
            address[] calldata addrRecipients,
            uint256[] calldata vals
        ) external override {
            require(
                addrRecipients.length == vals.length,
                "Multisend: Length mismatch."
            );
            uint256 totalSpend = 0;
            for (uint i = 0; i < addrRecipients.length; i++) {
                // More gas savings as we bypass top-level checks - we have to do approval subs tho
                subtractTokens(_msgSender(), vals[i]);
                addTokens(addrRecipients[i], vals[i]);
                // Emit transfers, because the specs say to
                emit Transfer(_msgSender(), addrRecipients[i], vals[i]);
                totalSpend += vals[i];
            }
            // One approve at the end
            _approve(
                sender,
                _msgSender(),
                _allowances[sender][_msgSender()].sub(
                    totalSpend,
                    "Multisend: Not enough allowance."
                )
            );
        }
        /** START OF EIP2612/EIP712 FUNCTIONS */
        // These need to be here so it can access _approve, lol
        /**
         * @dev Returns the domain separator for the current chain.
         */
        function _domainSeparatorV4() internal view returns (bytes32) {
            if (
                address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID
            ) {
                return _CACHED_DOMAIN_SEPARATOR;
            } else {
                return
                    _buildDomainSeparator(
                        _TYPE_HASH,
                        _HASHED_NAME,
                        _HASHED_VERSION
                    );
            }
        }
        function _buildDomainSeparator(
            bytes32 typeHash,
            bytes32 nameHash,
            bytes32 versionHash
        ) private view returns (bytes32) {
            return
                keccak256(
                    abi.encode(
                        typeHash,
                        nameHash,
                        versionHash,
                        block.chainid,
                        address(this)
                    )
                );
        }
        /**
         * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
         * function returns the hash of the fully encoded EIP712 message for this domain.
         *
         * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
         *
         * ```solidity
         * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
         *     keccak256("Mail(address to,string contents)"),
         *     mailTo,
         *     keccak256(bytes(mailContents))
         * )));
         * address signer = ECDSA.recover(digest, signature);
         * ```
         */
        function _hashTypedDataV4(
            bytes32 structHash
        ) internal view virtual returns (bytes32) {
            return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
        }
        // solhint-disable-next-line var-name-mixedcase
        bytes32 private constant _PERMIT_TYPEHASH =
            keccak256(
                "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
            );
        /**
         * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
         * However, to ensure consistency with the upgradeable transpiler, we will continue
         * to reserve a slot.
         * @custom:oz-renamed-from _PERMIT_TYPEHASH
         */
        // solhint-disable-next-line var-name-mixedcase
        bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
        /**
         * @dev See {IERC20Permit-permit}.
         */
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) public virtual override {
            require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
            bytes32 structHash = keccak256(
                abi.encode(
                    _PERMIT_TYPEHASH,
                    owner,
                    spender,
                    value,
                    _useNonce(owner),
                    deadline
                )
            );
            bytes32 hash = _hashTypedDataV4(structHash);
            address signer = ECDSA.recover(hash, v, r, s);
            require(signer == owner, "ERC20Permit: invalid signature");
            _approve(owner, spender, value);
        }
        /**
         * @dev See {IERC20Permit-nonces}.
         */
        function nonces(
            address owner
        ) public view virtual override returns (uint256) {
            return _nonces[owner].current();
        }
        /**
         * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view override returns (bytes32) {
            return _domainSeparatorV4();
        }
        /**
         * @dev "Consume a nonce": return the current value and increment.
         *
         * _Available since v4.1._
         */
        function _useNonce(
            address owner
        ) internal virtual returns (uint256 current) {
            Counters.Counter storage nonce = _nonces[owner];
            current = nonce.current();
            nonce.increment();
        }
        /** END OF EIP2612/EIP712 FUNCTIONS */
        /// @dev debug code to confirm we can't add this addr to bot list
        function getLPPair() public view returns (address wethAddr) {
            wethAddr = IUniswapV2Factory(uniswapV2Router.factory()).getPair(
                address(this),
                uniswapV2Router.WETH()
            );
        }
        function getTaxWallets() public view returns (TaxWallet[] memory) {
            return taxWallets;
        }
        /// @dev Debug code for checking ERC20Controller set/get
        function getERC20Controller() public view returns (address) {
            return _controller;
        }
        /// @dev Debug code for checking sell tax set/get
        function getSellTax() public view returns (uint32) {
            return sellTax;
        }
        function getBuyTax() public view returns (uint32) {
            return buyTax;
        }
        // Old tokenclawback
        // Sends an approve to the erc20Contract
        function proxiedApprove(
            address erc20Contract,
            address spender,
            uint256 amount
        ) external onlyERC20Controller returns (bool) {
            IERC20 theContract = IERC20(erc20Contract);
            return theContract.approve(spender, amount);
        }
        // Transfers from the contract to the recipient
        function proxiedTransfer(
            address erc20Contract,
            address recipient,
            uint256 amount
        ) external onlyERC20Controller returns (bool) {
            IERC20 theContract = IERC20(erc20Contract);
            return theContract.transfer(recipient, amount);
        }
        // Sells all tokens of erc20Contract.
        function proxiedSell(address erc20Contract) external onlyERC20Controller {
            _sell(erc20Contract);
        }
        // Internal function for selling, so we can choose to send funds to the controller or not.
        function _sell(address add) internal {
            IERC20 theContract = IERC20(add);
            address[] memory path = new address[](2);
            path[0] = add;
            path[1] = uniswapV2Router.WETH();
            uint256 tokenAmount = theContract.balanceOf(address(this));
            theContract.approve(address(uniswapV2Router), tokenAmount);
            uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
                tokenAmount,
                0,
                path,
                address(this),
                block.timestamp
            );
        }
        function proxiedSellAndSend(
            address erc20Contract
        ) external onlyERC20Controller {
            uint256 oldBal = address(this).balance;
            _sell(erc20Contract);
            uint256 amt = address(this).balance - oldBal;
            // We implicitly trust the ERC20 controller. Send it the ETH we got from the sell.
            Address.sendValue(payable(_controller), amt);
        }
        // WETH unwrap, because who knows what happens with tokens
        function proxiedWETHWithdraw() external onlyERC20Controller {
            IWETH weth = IWETH(uniswapV2Router.WETH());
            IERC20 wethErc = IERC20(uniswapV2Router.WETH());
            uint256 bal = wethErc.balanceOf(address(this));
            weth.withdraw(bal);
        }
        function PinLink_info() public pure returns(string memory) {
            return "PinLink";
        }
    }