HYPE Price: $38.66 (-2.85%)

Token

veNFT (18)

Overview

Max Total Supply

267,892,845,498,421,167,000,241,196 18

Holders

4,125

Market

Onchain Market Cap

-

Circulating Supply Market Cap

-
Balance
1 18
0x6fe7f3bc9a5f94a0a4bb3513ce23c8a2a17fc367
Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information

Contract Source Code Verified (Exact Match)

Contract Name:
VotingEscrow

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion
File 1 of 10 : vePeg.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import {SystemEpoch} from "./SystemEpoch.sol";
import {IVotingEscrow} from "../interfaces/IVotingEscrow.sol";
import {IVeArtProxy} from "../interfaces/IVeArtProxy.sol";

/// @title Voting Escrow
/// @notice veNFT implementation that escrows ERC-20 tokens in the form of an ERC-721 NFT
/// @notice Votes have a weight depending on time, so that users are committed to the future of (whatever they are voting for)
/// @author Modified from Solidly (https://github.com/solidlyexchange/solidly/blob/master/contracts/ve.sol)
/// @author Modified from Curve (https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy)
/// @author Modified from Nouns DAO (https://github.com/withtally/my-nft-dao-project/blob/main/contracts/ERC721Checkpointable.sol)
/// @dev Vote weight decays linearly over time. Lock time cannot be more than `MAXTIME` (4 years).
contract VotingEscrow is IERC721, IERC721Metadata, SystemEpoch {
    enum DepositType {
        DEPOSIT_FOR_TYPE,
        CREATE_LOCK_TYPE,
        INCREASE_LOCK_AMOUNT,
        INCREASE_UNLOCK_TIME,
        MERGE_TYPE
    }

    struct LockedBalance {
        int128 amount;
        uint256 end;
        bool perpetuallyLocked;
    }

    struct Point {
        int128 bias;
        int128 slope; // # -dweight / dt
        uint256 ts;
        uint256 blk; // block
        uint256 perpetualBalance;
    }
    /* We cannot really do block numbers per se b/c slope is per time, not per block
     * and per block could be fairly bad b/c Ethereum changes blocktimes.
     * What we can do is to extrapolate ***At functions */

    /// @notice A checkpoint for marking delegated tokenIds from a given timestamp
    struct Checkpoint {
        uint256 timestamp;
        uint256[] tokenIds;
    }

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Deposit(
        address indexed provider,
        uint256 tokenId,
        uint256 value,
        uint256 indexed locktime,
        DepositType deposit_type,
        uint256 ts
    );
    event Withdraw(address indexed provider, uint256 tokenId, uint256 value, uint256 ts);
    event Supply(uint256 prevSupply, uint256 supply);
    event DelegateChanged(address owner, address source, address dest);

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    address public immutable token;
    address public voter;
    address public team;
    address public artProxy;

    mapping(uint256 => Point) public point_history; // epoch -> unsigned point

    /// @dev Mapping of interface id to bool about whether or not it's supported
    mapping(bytes4 => bool) internal supportedInterfaces;

    /// @dev ERC165 interface ID of ERC165
    bytes4 internal constant ERC165_INTERFACE_ID = 0x01ffc9a7;

    /// @dev ERC165 interface ID of ERC721
    bytes4 internal constant ERC721_INTERFACE_ID = 0x80ac58cd;

    /// @dev ERC165 interface ID of ERC721Metadata
    bytes4 internal constant ERC721_METADATA_INTERFACE_ID = 0x5b5e139f;

    /// @dev Current count of token
    uint256 internal tokenId;

    uint256 public perpetuallyLockedBalance;

    /// @notice Contract constructor
    /// @param token_addr `Peg` token address
    constructor(address token_addr, address art_proxy) {
        token = token_addr;
        voter = msg.sender;
        team = msg.sender;
        artProxy = art_proxy;

        point_history[0].blk = block.number;
        point_history[0].ts = block.timestamp;

        supportedInterfaces[ERC165_INTERFACE_ID] = true;
        supportedInterfaces[ERC721_INTERFACE_ID] = true;
        supportedInterfaces[ERC721_METADATA_INTERFACE_ID] = true;

        // mint-ish
        emit Transfer(address(0), address(this), tokenId);
        // burn-ish
        emit Transfer(address(this), address(0), tokenId);
    }

    /*//////////////////////////////////////////////////////////////
                                MODIFIERS
    //////////////////////////////////////////////////////////////*/

    /// @dev reentrancy guard
    uint8 internal constant _not_entered = 1;
    uint8 internal constant _entered = 2;
    uint8 internal _entered_state = 1;

    modifier nonreentrant() {
        require(_entered_state == _not_entered);
        _entered_state = _entered;
        _;
        _entered_state = _not_entered;
    }

    /*///////////////////////////////////////////////////////////////
                             METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public constant name = "veNFT";
    string public constant symbol = "veNFT";
    string public constant version = "1.0.0";
    uint8 public constant decimals = 18;

    function setTeam(address _team) external {
        require(msg.sender == team);
        team = _team;
    }

    function setArtProxy(address _proxy) external {
        require(msg.sender == team);
        artProxy = _proxy;
    }

    /// @dev Returns current token URI metadata
    /// @param _tokenId Token ID to fetch URI for.
    function tokenURI(uint256 _tokenId) external view returns (string memory) {
        require(idToOwner[_tokenId] != address(0), "Query for nonexistent token");
        LockedBalance memory _locked = locked[_tokenId];
        return IVeArtProxy(artProxy)._tokenURI(
            _tokenId, _balanceOfNFT(_tokenId, block.timestamp), _locked.end, uint256(int256(_locked.amount))
        );
    }

    /*//////////////////////////////////////////////////////////////
                      ERC721 BALANCE/OWNER STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @dev Mapping from NFT ID to the address that owns it.
    mapping(uint256 => address) internal idToOwner;

    /// @dev Mapping from owner address to count of his tokens.
    mapping(address => uint256) internal ownerToNFTokenCount;

    /// @dev Returns the address of the owner of the NFT.
    /// @param _tokenId The identifier for an NFT.
    function ownerOf(uint256 _tokenId) public view returns (address) {
        return idToOwner[_tokenId];
    }

    /// @dev Returns the number of NFTs owned by `_owner`.
    ///      Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
    /// @param _owner Address for whom to query the balance.
    function _balance(address _owner) internal view returns (uint256) {
        return ownerToNFTokenCount[_owner];
    }

    /// @dev Returns the number of NFTs owned by `_owner`.
    ///      Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
    /// @param _owner Address for whom to query the balance.
    function balanceOf(address _owner) external view returns (uint256) {
        return _balance(_owner);
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @dev Mapping from NFT ID to approved address.
    mapping(uint256 => address) internal idToApprovals;

    /// @dev Mapping from owner address to mapping of operator addresses.
    mapping(address => mapping(address => bool)) internal ownerToOperators;

    mapping(uint256 => uint256) public ownership_change;

    /// @dev Get the approved address for a single NFT.
    /// @param _tokenId ID of the NFT to query the approval of.
    function getApproved(uint256 _tokenId) external view returns (address) {
        return idToApprovals[_tokenId];
    }

    /// @dev Checks if `_operator` is an approved operator for `_owner`.
    /// @param _owner The address that owns the NFTs.
    /// @param _operator The address that acts on behalf of the owner.
    function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
        return (ownerToOperators[_owner])[_operator];
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.
    ///      Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
    ///      Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)
    ///      Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)
    /// @param _approved Address to be approved for the given NFT ID.
    /// @param _tokenId ID of the token to be approved.
    function approve(address _approved, uint256 _tokenId) public {
        address owner = idToOwner[_tokenId];
        // Throws if `_tokenId` is not a valid NFT
        require(owner != address(0));
        // Throws if `_approved` is the current owner
        require(_approved != owner);
        // Check requirements
        bool senderIsOwner = (idToOwner[_tokenId] == msg.sender);
        bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender];
        require(senderIsOwner || senderIsApprovedForAll);
        // Set the approval
        idToApprovals[_tokenId] = _approved;
        emit Approval(owner, _approved, _tokenId);
    }

    /// @dev Enables or disables approval for a third party ("operator") to manage all of
    ///      `msg.sender`'s assets. It also emits the ApprovalForAll event.
    ///      Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)
    /// @notice This works even if sender doesn't own any tokens at the time.
    /// @param _operator Address to add to the set of authorized operators.
    /// @param _approved True if the operators is approved, false to revoke approval.
    function setApprovalForAll(address _operator, bool _approved) external {
        // Throws if `_operator` is the `msg.sender`
        assert(_operator != msg.sender);
        ownerToOperators[msg.sender][_operator] = _approved;
        emit ApprovalForAll(msg.sender, _operator, _approved);
    }

    /* TRANSFER FUNCTIONS */
    /// @dev Clear an approval of a given address
    ///      Throws if `_owner` is not the current owner.
    function _clearApproval(address _owner, uint256 _tokenId) internal {
        // Throws if `_owner` is not the current owner
        assert(idToOwner[_tokenId] == _owner);
        if (idToApprovals[_tokenId] != address(0)) {
            // Reset approvals
            idToApprovals[_tokenId] = address(0);
        }
    }

    /// @dev Returns whether the given spender can transfer a given token ID
    /// @param _spender address of the spender to query
    /// @param _tokenId uint ID of the token to be transferred
    /// @return bool whether the msg.sender is approved for the given token ID, is an operator of the owner, or is the owner of the token
    function _isApprovedOrOwner(address _spender, uint256 _tokenId) internal view returns (bool) {
        address owner = idToOwner[_tokenId];
        bool spenderIsOwner = owner == _spender;
        bool spenderIsApproved = _spender == idToApprovals[_tokenId];
        bool spenderIsApprovedForAll = (ownerToOperators[owner])[_spender];
        return spenderIsOwner || spenderIsApproved || spenderIsApprovedForAll;
    }

    function isApprovedOrOwner(address _spender, uint256 _tokenId) external view returns (bool) {
        return _isApprovedOrOwner(_spender, _tokenId);
    }

    /// @dev Exeute transfer of a NFT.
    ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
    ///      address for this NFT. (NOTE: `msg.sender` not allowed in internal function so pass `_sender`.)
    ///      Throws if `_to` is the zero address.
    ///      Throws if `_from` is not the current owner.
    ///      Throws if `_tokenId` is not a valid NFT.
    function _transferFrom(address _from, address _to, uint256 _tokenId, address _sender) internal {
        require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
        // Check requirements
        require(_isApprovedOrOwner(_sender, _tokenId));
        // Clear approval. Throws if `_from` is not the current owner
        _clearApproval(_from, _tokenId);
        // Remove NFT. Throws if `_tokenId` is not a valid NFT
        _removeTokenFrom(_from, _tokenId);
        // Add NFT
        _addTokenTo(_to, _tokenId);
        // Set the block of ownership transfer (for Flash NFT protection)
        ownership_change[_tokenId] = block.number;
        // Log the transfer
        emit Transfer(_from, _to, _tokenId);
    }

    /// @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT.
    ///      Throws if `_from` is not the current owner.
    ///      Throws if `_to` is the zero address.
    ///      Throws if `_tokenId` is not a valid NFT.
    /// @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else
    ///        they maybe be permanently lost.
    /// @param _from The current owner of the NFT.
    /// @param _to The new owner.
    /// @param _tokenId The NFT to transfer.
    function transferFrom(address _from, address _to, uint256 _tokenId) external {
        _transferFrom(_from, _to, _tokenId, msg.sender);
    }

    /// @dev Transfers the ownership of an NFT from one address to another address.
    ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the
    ///      approved address for this NFT.
    ///      Throws if `_from` is not the current owner.
    ///      Throws if `_to` is the zero address.
    ///      Throws if `_tokenId` is not a valid NFT.
    ///      If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
    ///      the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.
    /// @param _from The current owner of the NFT.
    /// @param _to The new owner.
    /// @param _tokenId The NFT to transfer.
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
        safeTransferFrom(_from, _to, _tokenId, "");
    }

    function _isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /// @dev Transfers the ownership of an NFT from one address to another address.
    ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the
    ///      approved address for this NFT.
    ///      Throws if `_from` is not the current owner.
    ///      Throws if `_to` is the zero address.
    ///      Throws if `_tokenId` is not a valid NFT.
    ///      If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
    ///      the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.
    /// @param _from The current owner of the NFT.
    /// @param _to The new owner.
    /// @param _tokenId The NFT to transfer.
    /// @param _data Additional data with no specified format, sent in call to `_to`.
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public {
        _transferFrom(_from, _to, _tokenId, msg.sender);

        if (_isContract(_to)) {
            // Throws if transfer destination is a contract which does not implement 'onERC721Received'
            try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4 response) {
                if (response != IERC721Receiver(_to).onERC721Received.selector) {
                    revert("ERC721: ERC721Receiver rejected tokens");
                }
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @dev Interface identification is specified in ERC-165.
    /// @param _interfaceID Id of the interface
    function supportsInterface(bytes4 _interfaceID) external view returns (bool) {
        return supportedInterfaces[_interfaceID];
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @dev Mapping from owner address to mapping of index to tokenIds
    mapping(address => mapping(uint256 => uint256)) internal ownerToNFTokenIdList;

    /// @dev Mapping from NFT ID to index of owner
    mapping(uint256 => uint256) internal tokenToOwnerIndex;

    /// @dev  Get token by index
    function tokenOfOwnerByIndex(address _owner, uint256 _tokenIndex) external view returns (uint256) {
        return ownerToNFTokenIdList[_owner][_tokenIndex];
    }

    /// @dev Add a NFT to an index mapping to a given address
    /// @param _to address of the receiver
    /// @param _tokenId uint ID Of the token to be added
    function _addTokenToOwnerList(address _to, uint256 _tokenId) internal {
        uint256 current_count = _balance(_to);

        ownerToNFTokenIdList[_to][current_count] = _tokenId;
        tokenToOwnerIndex[_tokenId] = current_count;
    }

    /// @dev Add a NFT to a given address
    ///      Throws if `_tokenId` is owned by someone.
    function _addTokenTo(address _to, uint256 _tokenId) internal {
        // Throws if `_tokenId` is owned by someone
        assert(idToOwner[_tokenId] == address(0));
        // Change the owner
        idToOwner[_tokenId] = _to;
        // Update owner token index tracking
        _addTokenToOwnerList(_to, _tokenId);
        // Change count tracking
        ownerToNFTokenCount[_to] += 1;
    }

    /// @dev Function to mint tokens
    ///      Throws if `_to` is zero address.
    ///      Throws if `_tokenId` is owned by someone.
    /// @param _to The address that will receive the minted tokens.
    /// @param _tokenId The token id to mint.
    /// @return A boolean that indicates if the operation was successful.
    function _mint(address _to, uint256 _tokenId) internal returns (bool) {
        // Throws if `_to` is zero address
        assert(_to != address(0));
        // Add NFT. Throws if `_tokenId` is owned by someone
        _addTokenTo(_to, _tokenId);
        emit Transfer(address(0), _to, _tokenId);
        return true;
    }

    /// @dev Remove a NFT from an index mapping to a given address
    /// @param _from address of the sender
    /// @param _tokenId uint ID Of the token to be removed
    function _removeTokenFromOwnerList(address _from, uint256 _tokenId) internal {
        // Delete
        uint256 current_count = _balance(_from) - 1;
        uint256 current_index = tokenToOwnerIndex[_tokenId];

        if (current_count == current_index) {
            // update ownerToNFTokenIdList
            ownerToNFTokenIdList[_from][current_count] = 0;
            // update tokenToOwnerIndex
            tokenToOwnerIndex[_tokenId] = 0;
        } else {
            uint256 lastTokenId = ownerToNFTokenIdList[_from][current_count];

            // Add
            // update ownerToNFTokenIdList
            ownerToNFTokenIdList[_from][current_index] = lastTokenId;
            // update tokenToOwnerIndex
            tokenToOwnerIndex[lastTokenId] = current_index;

            // Delete
            // update ownerToNFTokenIdList
            ownerToNFTokenIdList[_from][current_count] = 0;
            // update tokenToOwnerIndex
            tokenToOwnerIndex[_tokenId] = 0;
        }
    }

    /// @dev Remove a NFT from a given address
    ///      Throws if `_from` is not the current owner.
    function _removeTokenFrom(address _from, uint256 _tokenId) internal {
        // Throws if `_from` is not the current owner
        assert(idToOwner[_tokenId] == _from);
        // Change the owner
        idToOwner[_tokenId] = address(0);
        // Update owner token index tracking
        _removeTokenFromOwnerList(_from, _tokenId);
        // Change count tracking
        ownerToNFTokenCount[_from] -= 1;
    }

    function _burn(uint256 _tokenId) internal {
        require(_isApprovedOrOwner(msg.sender, _tokenId), "caller is not owner nor approved");

        address owner = ownerOf(_tokenId);

        // Clear approval
        approve(address(0), _tokenId);
        // Remove token
        _removeTokenFrom(owner, _tokenId);
        emit Transfer(owner, address(0), _tokenId);
    }

    /*//////////////////////////////////////////////////////////////
                             ESCROW STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => uint256) public user_point_epoch;
    mapping(uint256 => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
    mapping(uint256 => LockedBalance) public locked;
    uint256 public epoch;
    mapping(uint256 => int128) public slope_changes; // time -> signed slope change
    uint256 public supply;

    uint256 internal constant MAXTIME = 365 days;
    int128 internal constant iMAXTIME = 365 days;
    uint256 internal constant MULTIPLIER = 1 ether;

    /*//////////////////////////////////////////////////////////////
                              ESCROW LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Get the most recently recorded rate of voting power decrease for `_tokenId`
    /// @param _tokenId token of the NFT
    /// @return Value of the slope
    function get_last_user_slope(uint256 _tokenId) external view returns (int128) {
        uint256 uepoch = user_point_epoch[_tokenId];
        return user_point_history[_tokenId][uepoch].slope;
    }

    /// @notice Get the timestamp for checkpoint `_idx` for `_tokenId`
    /// @param _tokenId token of the NFT
    /// @param _idx User epoch number
    /// @return Epoch time of the checkpoint
    function user_point_history__ts(uint256 _tokenId, uint256 _idx) external view returns (uint256) {
        return user_point_history[_tokenId][_idx].ts;
    }

    /// @notice Get timestamp when `_tokenId`'s lock finishes
    /// @param _tokenId User NFT
    /// @return Epoch time of the lock end
    function locked__end(uint256 _tokenId) external view returns (uint256) {
        return locked[_tokenId].end;
    }

    /// @notice Record global and per-user data to checkpoint
    /// @param _tokenId NFT token ID. No user checkpoint if 0
    /// @param old_locked Pevious locked amount / end lock time for the user
    /// @param new_locked New locked amount / end lock time for the user
    function _checkpoint(uint256 _tokenId, LockedBalance memory old_locked, LockedBalance memory new_locked) internal {
        Point memory u_old;
        Point memory u_new;
        int128 old_dslope = 0;
        int128 new_dslope = 0;
        uint256 _epoch = epoch;

        uint256 oldVirtualEnd =
            old_locked.perpetuallyLocked ? (block.timestamp + MAXTIME) / EPOCH * EPOCH : old_locked.end;
        uint256 newVirtualEnd =
            new_locked.perpetuallyLocked ? (block.timestamp + MAXTIME) / EPOCH * EPOCH : new_locked.end;

        uint256 currentUserEpoch = user_point_epoch[_tokenId];

        if (_tokenId != 0) {
            if (new_locked.perpetuallyLocked) {
                u_new.perpetualBalance = uint256(uint128(new_locked.amount));
            }

            Point memory lastUserPoint = user_point_history[_tokenId][currentUserEpoch];

            // Calculate slopes and biases
            // Kept at zero when they have to
            if (oldVirtualEnd > block.timestamp && old_locked.amount > 0) {
                if (old_locked.perpetuallyLocked) {
                    // remove perpetual lock or increase locked amount
                    // Since the old lock is perpetual, the old slope should be
                    // 0 and the old bias
                    // should be constant (so we reuse the last point bias)
                    u_old.slope = 0;
                    u_old.bias = lastUserPoint.bias;
                } else {
                    u_old.slope = old_locked.amount / iMAXTIME;
                    u_old.bias = u_old.slope * int128(int256(oldVirtualEnd - block.timestamp));
                }
            }
            if (newVirtualEnd > block.timestamp && new_locked.amount > 0) {
                if (new_locked.perpetuallyLocked) {
                    // create perpetual lock or increase amount
                    u_new.slope = 0;
                    // increase amount
                    if (new_locked.amount > old_locked.amount) {
                        int128 delta = new_locked.amount - old_locked.amount;
                        u_new.bias =
                            lastUserPoint.bias + ((delta / iMAXTIME) * int128(int256(newVirtualEnd - block.timestamp)));
                    } else {
                        u_new.bias = (new_locked.amount / iMAXTIME) * int128(int256(newVirtualEnd - block.timestamp));
                    }
                } else {
                    u_new.slope = new_locked.amount / iMAXTIME;
                    u_new.bias = u_new.slope * int128(int256(newVirtualEnd - block.timestamp));
                }
            }

            // Read values of scheduled changes in the slope
            // old_locked.end can be in the past and in the future
            // new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros
            old_dslope = slope_changes[oldVirtualEnd];
            if (newVirtualEnd != 0) {
                if (newVirtualEnd == oldVirtualEnd) {
                    new_dslope = old_dslope;
                } else {
                    new_dslope = slope_changes[newVirtualEnd];
                }
            }
        }

        Point memory last_point =
            Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number, perpetualBalance: 0});

        if (_epoch > 0) {
            last_point = point_history[_epoch];
        }
        uint256 last_checkpoint = last_point.ts;
        // initial_last_point is used for extrapolation to calculate block number
        // (approximately, for *At methods) and save them
        // as we cannot figure that out exactly from inside the contract
        Point memory initial_last_point;
        initial_last_point.bias = last_point.bias;
        initial_last_point.slope = last_point.slope;
        initial_last_point.ts = last_point.ts;
        initial_last_point.blk = last_point.blk;
        initial_last_point.perpetualBalance = last_point.perpetualBalance;

        uint256 block_slope = 0; // dblock/dt
        if (block.timestamp > last_point.ts) {
            block_slope = (MULTIPLIER * (block.number - last_point.blk)) / (block.timestamp - last_point.ts);
        }
        // If last point is already recorded in this block, slope=0
        // But that's ok b/c we know the block in such case

        // Go over weeks to fill history and calculate what the current point is
        {
            uint256 t_i = (last_checkpoint / EPOCH) * EPOCH;
            for (uint256 i = 0; i < 255; ++i) {
                // Hopefully it won't happen that this won't get used in 5 years!
                // If it does, users will be able to withdraw but vote weight will be broken
                t_i += EPOCH;
                int128 d_slope = 0;
                if (t_i > block.timestamp) {
                    t_i = block.timestamp;
                } else {
                    d_slope = slope_changes[t_i];
                }
                last_point.bias -= last_point.slope * int128(int256(t_i - last_checkpoint));
                last_point.slope += d_slope;
                if (last_point.bias < 0) {
                    // This can happen
                    last_point.bias = 0;
                }
                if (last_point.slope < 0) {
                    // This cannot happen - just in case
                    last_point.slope = 0;
                }
                last_checkpoint = t_i;
                last_point.ts = t_i;
                last_point.blk = initial_last_point.blk + (block_slope * (t_i - initial_last_point.ts)) / MULTIPLIER;
                _epoch += 1;
                if (t_i == block.timestamp) {
                    last_point.blk = block.number;
                    break;
                } else {
                    point_history[_epoch] = last_point;
                }
            }
        }

        epoch = _epoch;
        // Now point_history is filled until t=now

        if (_tokenId != 0) {
            // If last point was in this block, the slope change has been applied already
            // But in such case we have 0 slope(s)
            last_point.slope += (u_new.slope - u_old.slope);
            last_point.bias += (u_new.bias - u_old.bias);
            if (last_point.slope < 0) {
                last_point.slope = 0;
            }
            if (last_point.bias < 0) {
                last_point.bias = 0;
            }

            last_point.perpetualBalance = perpetuallyLockedBalance;
        }

        // Record the changed point into history
        point_history[_epoch] = last_point;

        if (_tokenId != 0) {
            // Schedule the slope changes (slope is going down)
            // We subtract new_user_slope from [new_locked.end]
            // and add old_user_slope to [old_locked.end]
            if (oldVirtualEnd > block.timestamp) {
                // old_dslope was <something> - u_old.slope, so we cancel that
                old_dslope += u_old.slope;
                if (newVirtualEnd == oldVirtualEnd) {
                    old_dslope -= u_new.slope; // It was a new deposit, not extension
                }
                slope_changes[oldVirtualEnd] = old_dslope;
            }

            if (newVirtualEnd > block.timestamp) {
                if (newVirtualEnd > oldVirtualEnd) {
                    new_dslope -= u_new.slope; // old slope disappeared at this point
                    slope_changes[newVirtualEnd] = new_dslope;
                }
                // else: we recorded it already in old_dslope
            }
            // Now handle user history
            uint256 user_epoch = currentUserEpoch + 1;

            user_point_epoch[_tokenId] = user_epoch;
            u_new.ts = block.timestamp;
            u_new.blk = block.number;
            user_point_history[_tokenId][user_epoch] = u_new;
        }
    }

    /// @notice Deposit and lock tokens for a user
    /// @param _tokenId NFT that holds lock
    /// @param _value Amount to deposit
    /// @param unlock_time New time when to unlock the tokens, or 0 if unchanged
    /// @param locked_balance Previous locked amount / timestamp
    /// @param deposit_type The type of deposit
    function _deposit_for(
        uint256 _tokenId,
        uint256 _value,
        uint256 unlock_time,
        LockedBalance memory locked_balance,
        DepositType deposit_type
    ) internal {
        LockedBalance memory _locked = locked_balance;
        uint256 supply_before = supply;

        if (deposit_type != DepositType.MERGE_TYPE) {
            supply = supply_before + _value;
        }

        // Copy values of previous lock
        LockedBalance memory old_locked;
        (old_locked.amount, old_locked.end, old_locked.perpetuallyLocked) =
            (_locked.amount, _locked.end, _locked.perpetuallyLocked);

        // Update previous lock to make it the new lock
        // Adding to existing lock, or if a lock is expired - creating a new one
        _locked.amount += int128(int256(_value));
        if (unlock_time != 0) {
            _locked.end = unlock_time;
        }
        // Store the updated lock
        locked[_tokenId] = _locked;

        // Possibilities:
        // Both old_locked.end could be current or expired (>/< block.timestamp)
        // value == 0 (extend lock) or value > 0 (add to lock or extend lock)
        // _locked.end > block.timestamp (always)
        _checkpoint(_tokenId, old_locked, _locked);

        address from = msg.sender;
        if (_value != 0 && deposit_type != DepositType.MERGE_TYPE) {
            assert(IERC20(token).transferFrom(from, address(this), _value));
        }

        emit Deposit(from, _tokenId, _value, _locked.end, deposit_type, block.timestamp);
        emit Supply(supply_before, supply_before + _value);
    }

    function block_number() external view returns (uint256) {
        return block.number;
    }

    /// @notice Record global data to checkpoint
    function checkpoint() external nonreentrant {
        _checkpoint(0, LockedBalance(0, 0, false), LockedBalance(0, 0, false));
    }

    /// @notice Deposit `_value` tokens for `_tokenId` and add to the lock
    /// @dev Anyone (even a smart contract) can deposit for someone else, but
    ///      cannot extend their locktime and deposit for a brand new user
    /// @param _tokenId lock NFT
    /// @param _value Amount to add to user's lock
    function deposit_for(uint256 _tokenId, uint256 _value) external nonreentrant {
        LockedBalance memory _locked = locked[_tokenId];

        require(_value > 0); // dev: need non-zero value
        require(_locked.amount > 0, "No existing lock found");
        require(_locked.end > block.timestamp || _locked.perpetuallyLocked, "Cannot add to expiredlock");

        if (_locked.perpetuallyLocked) {
            perpetuallyLockedBalance += _value;
        }

        _deposit_for(_tokenId, _value, 0, _locked, DepositType.DEPOSIT_FOR_TYPE);
    }

    /// @notice Deposit `_value` tokens for `_to` and lock for `_lock_duration`
    /// @param _value Amount to deposit
    /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
    /// @param _to Address to deposit
    function _create_lock(uint256 _value, uint256 _lock_duration, address _to) internal returns (uint256) {
        uint256 unlock_time = (block.timestamp + _lock_duration) / EPOCH * EPOCH; // Locktime is rounded down to weeks

        require(_value > 0); // dev: need non-zero value
        require(unlock_time > block.timestamp, "Can only lock until time in the future");
        require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 1 year max");

        ++tokenId;
        uint256 _tokenId = tokenId;
        _mint(_to, _tokenId);

        _deposit_for(_tokenId, _value, unlock_time, locked[_tokenId], DepositType.CREATE_LOCK_TYPE);
        return _tokenId;
    }

    /// @notice Deposit `_value` tokens for `msg.sender` and lock for `_lock_duration`
    /// @param _value Amount to deposit
    /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
    function create_lock(uint256 _value, uint256 _lock_duration) external nonreentrant returns (uint256) {
        return _create_lock(_value, _lock_duration, msg.sender);
    }

    /// @notice Deposit `_value` tokens for `_to` and lock for `_lock_duration`
    /// @param _value Amount to deposit
    /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
    /// @param _to Address to deposit
    function create_lock_for(uint256 _value, uint256 _lock_duration, address _to)
        external
        nonreentrant
        returns (uint256)
    {
        return _create_lock(_value, _lock_duration, _to);
    }

    /// @notice Deposit `_value` additional tokens for `_tokenId` without modifying the unlock time
    /// @param _value Amount of tokens to deposit and add to the lock
    function increase_amount(uint256 _tokenId, uint256 _value) external nonreentrant {
        assert(_isApprovedOrOwner(msg.sender, _tokenId));

        LockedBalance memory _locked = locked[_tokenId];

        assert(_value > 0); // dev: need non-zero value
        require(_locked.amount > 0, "No existing lock found");
        require(_locked.end > block.timestamp || _locked.perpetuallyLocked, "Cannot add to expired lock. Withdraw");

        if (_locked.perpetuallyLocked) {
            perpetuallyLockedBalance += _value;
        }

        _deposit_for(_tokenId, _value, 0, _locked, DepositType.INCREASE_LOCK_AMOUNT);
    }

    /// @notice Extend the unlock time for `_tokenId`
    /// @param _lock_duration New number of seconds until tokens unlock
    function increase_unlock_time(uint256 _tokenId, uint256 _lock_duration) external nonreentrant {
        assert(_isApprovedOrOwner(msg.sender, _tokenId));

        LockedBalance memory _locked = locked[_tokenId];
        uint256 unlock_time = (block.timestamp + _lock_duration) / EPOCH * EPOCH; // Locktime is rounded down to weeks

        require(_locked.perpetuallyLocked == false, "Lock is perpetual");
        require(_locked.end > block.timestamp, "Lock expired");
        require(_locked.amount > 0, "Nothing is locked");
        require(unlock_time > _locked.end, "Can only increase lock duration");
        require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 1 year max");

        _deposit_for(_tokenId, 0, unlock_time, _locked, DepositType.INCREASE_UNLOCK_TIME);
    }

    /// @notice Withdraw all tokens for `_tokenId`
    /// @dev Only possible if the lock has expired
    function withdraw(uint256 _tokenId) external nonreentrant {
        assert(_isApprovedOrOwner(msg.sender, _tokenId));
        require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");

        LockedBalance memory _locked = locked[_tokenId];
        require(_locked.perpetuallyLocked == false, "Lock is perpetual");
        require(block.timestamp >= _locked.end, "The lock didn't expire");
        uint256 value = uint256(int256(_locked.amount));

        locked[_tokenId] = LockedBalance(0, 0, false);
        uint256 supply_before = supply;
        supply = supply_before - value;

        // old_locked can have either expired <= timestamp or zero end
        // _locked has only 0 end
        // Both can have >= 0 amount
        _checkpoint(_tokenId, _locked, LockedBalance(0, 0, false));

        assert(IERC20(token).transfer(msg.sender, value));

        // Burn the NFT
        _burn(_tokenId);

        emit Withdraw(msg.sender, _tokenId, value, block.timestamp);
        emit Supply(supply_before, supply_before - value);
    }

    function lock_perpetually(uint256 _tokenId) external nonreentrant {
        assert(_isApprovedOrOwner(msg.sender, _tokenId));

        LockedBalance memory currentLock = locked[_tokenId];
        require(currentLock.perpetuallyLocked == false, "Lock is perpetual");
        require(currentLock.end > block.timestamp, "Lock expired");
        require(currentLock.amount > 0, "Nothing is locked");

        uint256 amount = uint256(int256(currentLock.amount));

        _checkpoint(_tokenId, currentLock, LockedBalance(0, 0, false));

        LockedBalance memory newLock;
        newLock.end = 0;
        newLock.perpetuallyLocked = true;
        newLock.amount = int128(int256(amount));

        perpetuallyLockedBalance += amount;

        _checkpoint(_tokenId, LockedBalance(0, 0, false), newLock);

        locked[_tokenId] = newLock;
    }

    function unlock_perpetual(uint256 _tokenId) external nonreentrant {
        assert(_isApprovedOrOwner(msg.sender, _tokenId));
        LockedBalance memory currentLock = locked[_tokenId];

        require(currentLock.perpetuallyLocked == true, "Lock is not perpetual");

        uint256 amount = uint256(int256(currentLock.amount));

        LockedBalance memory newLock;
        newLock.end = (block.timestamp + MAXTIME) / EPOCH * EPOCH;
        newLock.amount = currentLock.amount;

        perpetuallyLockedBalance -= amount;

        _checkpoint(_tokenId, currentLock, newLock);

        locked[_tokenId] = newLock;
    }

    /*///////////////////////////////////////////////////////////////
                           GAUGE VOTING STORAGE
    //////////////////////////////////////////////////////////////*/

    // The following ERC20/minime-compatible methods are not real balanceOf and supply!
    // They measure the weights for the purpose of voting, so they don't represent
    // real coins.

    /// @notice Binary search to estimate timestamp for block number
    /// @param _block Block to find
    /// @param max_epoch Don't go beyond this epoch
    /// @return Approximate timestamp for block
    function _find_block_epoch(uint256 _block, uint256 max_epoch) internal view returns (uint256) {
        // Binary search
        uint256 _min = 0;
        uint256 _max = max_epoch;
        for (uint256 i = 0; i < 128; ++i) {
            // Will be always enough for 128-bit numbers
            if (_min >= _max) {
                break;
            }
            uint256 _mid = (_min + _max + 1) / 2;
            if (point_history[_mid].blk <= _block) {
                _min = _mid;
            } else {
                _max = _mid - 1;
            }
        }
        return _min;
    }

    /// @notice Get the current voting power for `_tokenId`
    /// @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
    /// @param _tokenId NFT for lock
    /// @param _t Epoch time to return voting power at
    /// @return User voting power
    function _balanceOfNFT(uint256 _tokenId, uint256 _t) internal view returns (uint256) {
        if (ownership_change[_tokenId] == block.number) return 0;
        uint256 _epoch = user_point_epoch[_tokenId];
        if (_epoch == 0) {
            return 0;
        } else {
            Point memory last_point = user_point_history[_tokenId][_epoch];

            last_point.bias -= last_point.slope * int128(int256(_t) - int256(last_point.ts));
            if (last_point.bias < 0) {
                last_point.bias = 0;
            }
            return uint256(int256(last_point.bias));
        }
    }

    function balanceOfNFT(uint256 _tokenId) external view returns (uint256) {
        return _balanceOfNFT(_tokenId, block.timestamp);
    }

    function balanceOfNFTAt(uint256 _tokenId, uint256 _t) external view returns (uint256) {
        return _balanceOfNFT(_tokenId, _t);
    }

    /// @notice Measure voting power of `_tokenId` at block height `_block`
    /// @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
    /// @param _tokenId User's wallet NFT
    /// @param _block Block to calculate the voting power at
    /// @return Voting power
    function _balanceOfAtNFT(uint256 _tokenId, uint256 _block) internal view returns (uint256) {
        if (ownership_change[_tokenId] == block.number) return 0;
        // Copying and pasting totalSupply code because Vyper cannot pass by
        // reference yet
        assert(_block <= block.number);

        // Binary search
        uint256 _min = 0;
        uint256 _max = user_point_epoch[_tokenId];
        for (uint256 i = 0; i < 128; ++i) {
            // Will be always enough for 128-bit numbers
            if (_min >= _max) {
                break;
            }
            uint256 _mid = (_min + _max + 1) / 2;
            if (user_point_history[_tokenId][_mid].blk <= _block) {
                _min = _mid;
            } else {
                _max = _mid - 1;
            }
        }

        Point memory upoint = user_point_history[_tokenId][_min];

        uint256 max_epoch = epoch;
        uint256 _epoch = _find_block_epoch(_block, max_epoch);
        Point memory point_0 = point_history[_epoch];
        uint256 d_block = 0;
        uint256 d_t = 0;
        if (_epoch < max_epoch) {
            Point memory point_1 = point_history[_epoch + 1];
            d_block = point_1.blk - point_0.blk;
            d_t = point_1.ts - point_0.ts;
        } else {
            d_block = block.number - point_0.blk;
            d_t = block.timestamp - point_0.ts;
        }
        uint256 block_time = point_0.ts;
        if (d_block != 0) {
            block_time += (d_t * (_block - point_0.blk)) / d_block;
        }

        upoint.bias -= upoint.slope * int128(int256(block_time - upoint.ts));
        if (upoint.bias >= 0) {
            return uint256(uint128(upoint.bias));
        } else {
            return 0;
        }
    }

    function balanceOfAtNFT(uint256 _tokenId, uint256 _block) external view returns (uint256) {
        return _balanceOfAtNFT(_tokenId, _block);
    }

    /// @notice Calculate total voting power at some point in the past
    /// @param _block Block to calculate the total voting power at
    /// @return Total voting power at `_block`
    function totalSupplyAt(uint256 _block) external view returns (uint256) {
        assert(_block <= block.number);
        uint256 _epoch = epoch;
        uint256 target_epoch = _find_block_epoch(_block, _epoch);

        Point memory point = point_history[target_epoch];
        uint256 dt = 0;
        if (target_epoch < _epoch) {
            Point memory point_next = point_history[target_epoch + 1];
            if (point.blk != point_next.blk) {
                dt = ((_block - point.blk) * (point_next.ts - point.ts)) / (point_next.blk - point.blk);
            }
        } else {
            if (point.blk != block.number) {
                dt = ((_block - point.blk) * (block.timestamp - point.ts)) / (block.number - point.blk);
            }
        }
        // Now dt contains info on how far are we beyond point
        return _supply_at(point, point.ts + dt);
    }
    /// @notice Calculate total voting power at some point in the past
    /// @param point The point (bias/slope) to start search from
    /// @param t Time to calculate the total voting power at
    /// @return Total voting power at that time

    function _supply_at(Point memory point, uint256 t) internal view returns (uint256) {
        Point memory last_point = point;
        uint256 t_i = (last_point.ts / EPOCH) * EPOCH;
        for (uint256 i = 0; i < 255; ++i) {
            t_i += EPOCH;
            int128 d_slope = 0;
            if (t_i > t) {
                t_i = t;
            } else {
                d_slope = slope_changes[t_i];
            }
            last_point.bias -= last_point.slope * int128(int256(t_i - last_point.ts));
            if (t_i == t) {
                break;
            }
            last_point.slope += d_slope;
            last_point.ts = t_i;
        }

        if (last_point.bias < 0) {
            last_point.bias = 0;
        }
        return uint256(uint128(last_point.bias));
    }

    function totalSupply() external view returns (uint256) {
        return totalSupplyAtT(block.timestamp);
    }

    /// @notice Calculate total voting power
    /// @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
    /// @return Total voting power
    function totalSupplyAtT(uint256 t) public view returns (uint256) {
        uint256 _epoch = epoch;
        Point memory last_point = point_history[_epoch];
        return _supply_at(last_point, t);
    }

    /*///////////////////////////////////////////////////////////////
                            GAUGE VOTING LOGIC
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => uint256) public attachments;
    mapping(uint256 => bool) public voted;

    function setVoter(address _voter) external {
        require(msg.sender == voter);
        voter = _voter;
    }

    function voting(uint256 _tokenId) external {
        require(msg.sender == voter);
        voted[_tokenId] = true;
    }

    function abstain(uint256 _tokenId) external {
        require(msg.sender == voter);
        voted[_tokenId] = false;
    }

    function attach(uint256 _tokenId) external {
        require(msg.sender == voter);
        attachments[_tokenId] = attachments[_tokenId] + 1;
    }

    function detach(uint256 _tokenId) external {
        require(msg.sender == voter);
        attachments[_tokenId] = attachments[_tokenId] - 1;
    }

    function merge(uint256 _from, uint256 _to) external {
        require(attachments[_from] == 0 && !voted[_from], "attached");
        require(_from != _to);
        require(_isApprovedOrOwner(msg.sender, _from));
        require(_isApprovedOrOwner(msg.sender, _to));

        LockedBalance memory _locked0 = locked[_from];
        LockedBalance memory _locked1 = locked[_to];
        require(_locked0.perpetuallyLocked == _locked1.perpetuallyLocked, "Incompatible locks");
        uint256 value0 = uint256(int256(_locked0.amount));
        uint256 end = _locked0.end >= _locked1.end ? _locked0.end : _locked1.end;

        locked[_from] = LockedBalance(0, 0, false);
        _checkpoint(_from, _locked0, LockedBalance(0, 0, false));
        _burn(_from);
        _deposit_for(_to, value0, end, _locked1, DepositType.MERGE_TYPE);
    }

    /*///////////////////////////////////////////////////////////////
                            DAO VOTING STORAGE
    //////////////////////////////////////////////////////////////*/
    /// @notice A record of delegated token checkpoints for each account, by index
    mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;

    /// @notice The number of checkpoints for each account
    mapping(address => uint32) public numCheckpoints;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (governance/utils/IVotes.sol)
pragma solidity ^0.8.20;

/**
 * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts.
 */
interface IVotes {
    /**
     * @dev The signature used has expired.
     */
    error VotesExpiredSignature(uint256 expiry);

    /**
     * @dev Emitted when an account changes their delegate.
     */
    event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

    /**
     * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units.
     */
    event DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes);

    /**
     * @dev Returns the current amount of votes that `account` has.
     */
    function getVotes(address account) external view returns (uint256);

    /**
     * @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is
     * configured to use block numbers, this will return the value at the end of the corresponding block.
     */
    function getPastVotes(address account, uint256 timepoint) external view returns (uint256);

    /**
     * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is
     * configured to use block numbers, this will return the value at the end of the corresponding block.
     *
     * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes.
     * Votes that have not been delegated are still part of total supply, even though they would not participate in a
     * vote.
     */
    function getPastTotalSupply(uint256 timepoint) external view returns (uint256);

    /**
     * @dev Returns the delegate that `account` has chosen.
     */
    function delegates(address account) external view returns (address);

    /**
     * @dev Delegates votes from the sender to `delegatee`.
     */
    function delegate(address delegatee) external;

    /**
     * @dev Delegates votes from signer to `delegatee`.
     */
    function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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.1.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

File 5 of 10 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.20;

/**
 * @title ERC-721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC-721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be
     * reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.20;

import {IERC721} from "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 8 of 10 : SystemEpoch.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

abstract contract SystemEpoch {
    uint256 public constant EPOCH = 7 days;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

interface IVeArtProxy {
    function _tokenURI(uint256 _tokenId, uint256 _balanceOf, uint256 _locked_end, uint256 _value)
        external
        pure
        returns (string memory output);
}

//// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

interface IVotingEscrow {
    struct Point {
        int128 bias;
        int128 slope; // # -dweight / dt
        uint256 ts;
        uint256 blk; // block
    }

    struct LockedBalance {
        int128 amount;
        uint256 end;
        bool perpetuallyLocked;
    }

    function token() external view returns (address);
    function team() external returns (address);
    function epoch() external view returns (uint256);
    function point_history(uint256 loc) external view returns (Point memory);
    function user_point_history(uint256 tokenId, uint256 loc) external view returns (Point memory);
    function user_point_epoch(uint256 tokenId) external view returns (uint256);

    function ownerOf(uint256) external view returns (address);
    function isApprovedOrOwner(address, uint256) external view returns (bool);
    function transferFrom(address, address, uint256) external;

    function voting(uint256 tokenId) external;
    function abstain(uint256 tokenId) external;
    function attach(uint256 tokenId) external;
    function detach(uint256 tokenId) external;

    function checkpoint() external;
    function deposit_for(uint256 tokenId, uint256 value) external;
    function create_lock_for(uint256, uint256, address) external returns (uint256);

    function balanceOfNFT(uint256) external view returns (uint256);
    function totalSupply() external view returns (uint256);

    function locked(uint256 id) external view returns (LockedBalance memory);
}

Settings
{
  "evmVersion": "cancun",
  "libraries": {},
  "metadata": {
    "appendCBOR": true,
    "bytecodeHash": "ipfs",
    "useLiteralContent": false
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "remappings": [
    "@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
    "@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/",
    "solady/=node_modules/solady/src/",
    "forge-std/=node_modules/forge-std/src/"
  ],
  "viaIR": true
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"token_addr","type":"address"},{"internalType":"address","name":"art_proxy","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"source","type":"address"},{"indexed":false,"internalType":"address","name":"dest","type":"address"}],"name":"DelegateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"locktime","type":"uint256"},{"indexed":false,"internalType":"enum VotingEscrow.DepositType","name":"deposit_type","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"prevSupply","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"supply","type":"uint256"}],"name":"Supply","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"EPOCH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"abstain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_approved","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"artProxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"attach","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"attachments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_block","type":"uint256"}],"name":"balanceOfAtNFT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"balanceOfNFT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_t","type":"uint256"}],"name":"balanceOfNFTAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"block_number","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"checkpoint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint32","name":"","type":"uint32"}],"name":"checkpoints","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_lock_duration","type":"uint256"}],"name":"create_lock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_lock_duration","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"create_lock_for","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"deposit_for","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"detach","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"epoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"get_last_user_slope","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"increase_amount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_lock_duration","type":"uint256"}],"name":"increase_unlock_time","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"isApprovedOrOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"lock_perpetually","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"locked","outputs":[{"internalType":"int128","name":"amount","type":"int128"},{"internalType":"uint256","name":"end","type":"uint256"},{"internalType":"bool","name":"perpetuallyLocked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"locked__end","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_from","type":"uint256"},{"internalType":"uint256","name":"_to","type":"uint256"}],"name":"merge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"numCheckpoints","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownership_change","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"perpetuallyLockedBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"point_history","outputs":[{"internalType":"int128","name":"bias","type":"int128"},{"internalType":"int128","name":"slope","type":"int128"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"uint256","name":"blk","type":"uint256"},{"internalType":"uint256","name":"perpetualBalance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_proxy","type":"address"}],"name":"setArtProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_team","type":"address"}],"name":"setTeam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_voter","type":"address"}],"name":"setVoter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"slope_changes","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"supply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"team","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_tokenIndex","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_block","type":"uint256"}],"name":"totalSupplyAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"t","type":"uint256"}],"name":"totalSupplyAtT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"unlock_perpetual","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"user_point_epoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"user_point_history","outputs":[{"internalType":"int128","name":"bias","type":"int128"},{"internalType":"int128","name":"slope","type":"int128"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"uint256","name":"blk","type":"uint256"},{"internalType":"uint256","name":"perpetualBalance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_idx","type":"uint256"}],"name":"user_point_history__ts","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"voted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"voter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"voting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60a0346101d657601f61428d38819003918201601f19168301916001600160401b038311848410176101da5780849260409485528339810103126101d657610052602061004b836101ee565b92016101ee565b6007805460ff1990811660019081179092556080939093525f80546001600160a01b031990811633908117835583548216178355600280549091166001600160a01b0390941693909317909255437f3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92f0155427f3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92f005560046020527f9fe05126d2d9ecf60592e254dead906a4b2e492f36cca727682c38e9008c6ac180548416821790557f4267c0a6fd96b7a87f183ee8744f24d011423cd0e0142b3f563f183d8d9a456b8054841682179055635b5e139f60e01b82527e24030bcf4927897dffe721c2d8dda4bfd8910861687c42b03a463b43b0414780549093161790915560055460405191819030905f51602061426d5f395f51905f528180a45f305f51602061426d5f395f51905f528280a461406a90816102038239608051818181610b7d01528181611c770152818161387901528181613aa101528181613bb00152613cb10152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036101d65756fe60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a7146103ef578063047fc9aa146103ea57806306fdde0314610340578063081812fc146103e5578063095cf5c6146103e0578063095ea7b3146103db5780630d6a2033146103d657806312aeebf7146103d15780631376f3da146103cc57806318160ddd146103c75780631c984bc3146103c257806323b872dd146103bd57806325a58b56146103b85780632e1a7d4d146103b35780632e720f7d146103ae5780632f745c59146103a9578063313ce567146103a45780633e012fa81461039f57806342842e0e1461039a578063430c208114610395578063461f711c1461039057806346c96aac1461038b5780634bc2a6571461038657806354fd4d50146103815780635594a0451461037c5780636352211e1461037757806365fc3873146103725780636f5488371461036d5780636fcfff451461036857806370a08231146103635780637116c60c1461035e578063711974841461035957806385f2aef2146103545780638c2c9baf1461034f5780638fbb38ff1461034a578063900cf0cf1461034557806395d89b4114610340578063981b24d01461033b578063986b7d8a14610336578063a0dc275814610331578063a183af521461032c578063a22cb46514610327578063a4d855df14610322578063b45a3c0e1461031d578063b88d4fde14610318578063c1f0fb9f14610313578063c2c4c5c11461030e578063c87b56dd14610309578063d1c2babb14610304578063d1febfb9146102ff578063d4e54c3b146102fa578063e0514aba146102f5578063e441135c146102f0578063e7e242d4146102eb578063e93e3b1f146102e6578063e985e9c5146102e1578063ee99fe28146102dc578063f1127ed8146102d7578063f8a05763146102d2578063fbd3a29d146102cd578063fc0c546a146102c85763fd4a77f1146102c3575f80fd5b611ca6565b611c62565b611c13565b611be6565b611b8f565b611ae4565b611a88565b611a6b565b611a4c565b611a22565b611a01565b6119bc565b61194e565b6117fe565b6116ed565b611685565b611648565b6115c0565b611577565b6114bf565b611417565b611351565b611334565b6112e0565b611197565b61054f565b61117a565b61114b565b61112a565b611102565b6110d5565b6110b7565b61107f565b611041565b611017565b610fbb565b610f89565b610f61565b610f1a565b610ed3565b610eac565b610e67565b610e36565b610e0d565b610d3e565b610d23565b610cde565b610c91565b610a64565b610a4a565b610a31565b6109be565b61099b565b61091d565b6107a8565b61077e565b610657565b61060e565b61059a565b61044b565b61040a565b6001600160e01b031981160361040657565b5f80fd5b3461040657602036600319011261040657600435610427816103f4565b63ffffffff60e01b165f526004602052602060ff60405f2054166040519015158152f35b34610406575f366003190112610406576020601454604051908152f35b634e487b7160e01b5f52604160045260245ffd5b6060810190811067ffffffffffffffff82111761049857604052565b610468565b60a0810190811067ffffffffffffffff82111761049857604052565b90601f8019910116810190811067ffffffffffffffff82111761049857604052565b604051906104ea6060836104b9565b565b604051906104ea60a0836104b9565b67ffffffffffffffff811161049857601f01601f191660200190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90602061054c928181520190610517565b90565b34610406575f366003190112610406576105966040516105706040826104b9565b60058152641d9953919560da1b6020820152604051918291602083526020830190610517565b0390f35b34610406576020366003190112610406576004355f52600a602052602060018060a01b0360405f205416604051908152f35b600435906001600160a01b038216820361040657565b602435906001600160a01b038216820361040657565b604435906001600160a01b038216820361040657565b34610406576020366003190112610406576106276105cc565b600154906001600160a01b0382163303610406576001600160a01b03166001600160a01b03199190911617600155005b34610406576040366003190112610406576106706105cc565b6024355f818152600860205260409020549091906001600160a01b03168015610406576001600160a01b03821691818314610406576107509061071f6106ce6106c1875f52600860205260405f2090565b546001600160a01b031690565b6001600160a01b038581165f908152600b60205260409020911633908114916107129161070b915b9060018060a01b03165f5260205260405f2090565b5460ff1690565b8115610776575b50611ce6565b610731855f52600a60205260405f2090565b80546001600160a01b0319166001600160a01b03909216919091179055565b7f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9255f80a4005b90505f610719565b34610406576020366003190112610406576004355f526015602052602060405f2054604051908152f35b34610406576020366003190112610406576007546004355f1960ff8316016104065760026108df9260ff1916176007556107ea6107e582336124cf565b611ced565b6108da610807610802835f52601160205260405f2090565b611d08565b9161081e6108186040850151151590565b15611d38565b61082d60208401514210611d78565b6108455f61083c8551600f0b90565b600f0b13611db3565b61087b61085c6108568551600f0b90565b600f0b90565b936108656104db565b905f82525f60208301525f604083015283612978565b6108ab6108a6610889611df3565b5f60208201526001604082015294600f81900b8652600654611e54565b600655565b6108cc836108b76104db565b5f81525f60208201525f604082015283612978565b5f52601160205260405f2090565b611e61565b6108f1600160ff196007541617600755565b005b90633b9aca008110156109095760021b01905f90565b634e487b7160e01b5f52603260045260245ffd5b3461040657604036600319011261040657600435602435905f52601060205260405f2090633b9aca0081101561040657610956916108f3565b5080546001820154600283015460039093015460408051600f85810b8252608095861d900b60208201529081019290925260608201939093529081019190915260a090f35b34610406575f3660031901126104065760206109b642612054565b604051908152f35b3461040657604036600319011261040657602060016109ec600435602435905f526010845260405f206108f3565b500154604051908152f35b6060906003190112610406576004356001600160a01b038116810361040657906024356001600160a01b0381168103610406579060443590565b34610406576108f1610a42366109f7565b903392613068565b34610406575f366003190112610406576020604051438152f35b3461040657602036600319011261040657600435600754600160ff8216036104065760ff1916600217600755610a9d6107e582336124cf565b610aaf815f52601560205260405f2090565b541580610c6a575b610ac090611eaf565b610ad5610802825f52601160205260405f2090565b610ae56108186040830151151590565b610af56020820151421015611ee6565b610b036108568251600f0b90565b91610b30610b0f6104db565b5f81525f60208201525f60408201526108da835f52601160205260405f2090565b610b5160145492610b49610b448686611f39565b601455565b6108656104db565b60405163a9059cbb60e01b815233600482015260248101849052926020846044815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1918215610c6557610bc9610c13935f5160206140155f395f51905f52965f91610c36575b50611ced565b610bd28161316a565b6040805191825260208201839052429082015233907f02f25270a4d87bea75db541cdfe559334a275b4a233520ed6c0a2429667cca9490606090a282611f39565b60408051928352602083019190915290a16108f1600160ff196007541617600755565b610c58915060203d602011610c5e575b610c5081836104b9565b810190611f46565b5f610bc3565b503d610c46565b611f5b565b50610ac0610c8a610c8661070b845f52601660205260405f2090565b1590565b9050610ab7565b3461040657602036600319011261040657610caa6105cc565b6001546001600160a01b0316330361040657600280546001600160a01b0319166001600160a01b0392909216919091179055005b3461040657604036600319011261040657610cf76105cc565b6024359060018060a01b03165f52600d60205260405f20905f52602052602060405f2054604051908152f35b34610406575f36600319011261040657602060405160128152f35b34610406576020366003190112610406576007546004355f1960ff831601610406576108da6108a69160026108df9460ff191617600755610d826107e582336124cf565b805f5260116020526108cc610d9960405f20611d08565b610dab60016040830151151514611f66565b610db96108568251600f0b90565b90610e07610dc5611df3565b968793610de5610de0610dd742611e25565b62093a80900490565b611fc8565b6020860152610dff610df88551600f0b90565b600f0b8652565b600654611f39565b83612978565b34610406576108f1610e1e366109f7565b9060405192610e2e6020856104b9565b5f845261221d565b34610406576040366003190112610406576020610e5d610e546105cc565b602435906124cf565b6040519015158152f35b34610406576020366003190112610406576020610e9c600435805f52600f835260405f2054905f526010835260405f206108f3565b505460801d60405190600f0b8152f35b34610406575f366003190112610406575f546040516001600160a01b039091168152602090f35b3461040657602036600319011261040657610eec6105cc565b5f54906001600160a01b0382163303610406576001600160a01b03166001600160a01b031991909116175f55005b34610406575f36600319011261040657610596604051610f3b6040826104b9565b60058152640312e302e360dc1b6020820152604051918291602083526020830190610517565b34610406575f366003190112610406576002546040516001600160a01b039091168152602090f35b34610406576020366003190112610406576004355f526008602052602060018060a01b0360405f205416604051908152f35b3461040657604036600319011261040657600754602435906004355f1960ff83160161040657610596926002610ffa9360ff19161760075533916132ca565b6007805460ff191660011790556040519081529081906020820190565b34610406576020366003190112610406576004355f52600c602052602060405f2054604051908152f35b34610406576020366003190112610406576001600160a01b036110626105cc565b165f526018602052602063ffffffff60405f205416604051908152f35b34610406576020366003190112610406576001600160a01b036110a06105cc565b165f526009602052602060405f2054604051908152f35b346104065760203660031901126104065760206109b6600435612054565b34610406576020366003190112610406576004355f526013602052602060405f2054600f0b604051908152f35b34610406575f366003190112610406576001546040516001600160a01b039091168152602090f35b346104065760403660031901126104065760206109b6602435600435613490565b34610406576020366003190112610406576004355f526016602052602060ff60405f2054166040519015158152f35b34610406575f366003190112610406576020601254604051908152f35b346104065760203660031901126104065761059661123a6004356111bd43821115611ced565b6112346012546111cd8184613686565b926111e86111e3855f52600360205260405f2090565b612014565b935f92811015611294576111e361120161120f92611e36565b5f52600360205260405f2090565b6060850190815160608201938451820361124a575b50505050505b6040830151611e54565b906133a9565b6040519081529081906020820190565b61128a955091611274604061126661127a946112849796611f39565b92015160408a015190611f39565b90612001565b9251905190611f39565b90611faa565b5f80808080611224565b5060608401908151904382036112ad575b50505061122a565b6112d893506112c2611284926112d092611f39565b611274604088015142611f39565b915143611f39565b5f80806112a5565b34610406576020366003190112610406575f54600435906001600160a01b0316330361040657805f52601560205260405f2054905f19820191821161132f575f52601560205260405f20555f80f35b611e11565b34610406575f36600319011261040657602060405162093a808152f35b34610406576040366003190112610406576007546004356024355f1960ff8416016104065760026108df9360ff1916176007556113916107e583336124cf565b815f5260116020526113a560405f20611d08565b916113b1821515611ced565b6113c05f8451600f0b13612073565b6020830151421080156113f5575b6113d7906120b8565b604083015115613746576113f06108a683600654611e54565b613746565b506113d76114066040850151151590565b90506113ce565b8015150361040657565b34610406576040366003190112610406576114306105cc565b60243561143c8161140d565b6001600160a01b0382169161148f90829061147e9061145d33871415611ced565b335f52600b60205260405f209060018060a01b03165f5260205260405f2090565b9060ff801983541691151516179055565b60405190151581527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160203392a3005b34610406576040366003190112610406576007546024356004355f1960ff8416016104065760026108df9360ff1916176007556114ff6107e582336124cf565b805f526011602052611523610de0610dd761151c60405f20611d08565b9442611e54565b906115346108186040850151151590565b61155e6020840161154742825111611d78565b6115565f61083c8751600f0b90565b518311612110565b61157261156a42611e25565b83111561215c565b6138e0565b34610406576020366003190112610406576004355f526011602052606060405f208054600f0b9060ff600260018301549201541690604051928352602083015215156040820152f35b34610406576080366003190112610406576115d96105cc565b6115e16105e2565b906044356064359267ffffffffffffffff8411610406573660238501121561040657836004013592611612846104fb565b9361162060405195866104b9565b8085523660248288010111610406576020815f9260246108f19901838901378601015261221d565b34610406576020366003190112610406575f54600435906001600160a01b03163303610406575f908152601660205260409020805460ff19169055005b34610406575f36600319011261040657600754600160ff8216036104065760ff19166002176007556040516108df906116bd8161047c565b5f81525f60208201525f6040820152604051906116d98261047c565b5f82525f60208301525f604083015261265f565b34610406576020366003190112610406576004355f81815260086020526040902061172c906001600160a01b0390611724906106c1565b161515612393565b5f611742610802835f52601160205260405f2090565b60025461176590611759906001600160a01b031681565b6001600160a01b031690565b61176f4285613d30565b61178261085660208501519451600f0b90565b946117ba6040519687958694859463dd9ec14960e01b8652600486019094939260609260808301968352602083015260408201520152565b03915afa8015610c6557610596915f916117dc575b506040519182918261053b565b6117f891503d805f833e6117f081836104b9565b8101906123df565b5f6117cf565b34610406576040366003190112610406576108f16004356024359061182b815f52601560205260405f2090565b54158061192b575b61183c90611eaf565b61184882821415611ce6565b61185a61185582336124cf565b611ce6565b61186761185583336124cf565b61187c610802825f52601160205260405f2090565b90611892610802845f52601160205260405f2090565b926118c16118a36040850151151590565b6118b86118b36040880151151590565b151590565b90151514612442565b61191e6118d26108568551600f0b90565b9261191960208601516020880151808210155f146119235750955b610b496118f86104db565b5f81525f60208201525f60408201526108da855f52601160205260405f2090565b61316a565b6139b9565b9050956118ed565b5061183c611947610c8661070b845f52601660205260405f2090565b9050611833565b34610406576020366003190112610406576004355f52600360205260405f20805461059660018301549260036002820154910154906040519484869560801d90600f0b869192608093969594919660a0840197600f0b8452600f0b6020840152604083015260608201520152565b34610406576060366003190112610406576004356024356119db6105f8565b60075491600160ff84160361040657610596936002610ffa9460ff1916176007556132ca565b346104065760403660031901126104065760206109b6602435600435613d30565b34610406576020366003190112610406576004355f52600f602052602060405f2054604051908152f35b346104065760203660031901126104065760206109b642600435613d30565b34610406575f366003190112610406576020600654604051908152f35b3461040657604036600319011261040657602060ff611ad8611aa86105cc565b611ab06105e2565b6001600160a01b039182165f908152600b865260408082209290931681526020919091522090565b54166040519015158152f35b3461040657604036600319011261040657600435602435600754600160ff8216036104065760029060ff191617600755815f526011602052611b2860405f20611d08565b908015610406576108df92611b425f8451600f0b13612073565b602083015142108015611b77575b611b5990612483565b604083015115613b0f57611b726108a683600654611e54565b613b0f565b50611b59611b886040850151151590565b9050611b50565b3461040657604036600319011261040657611ba86105cc565b6024359063ffffffff8216809203610406576001600160a01b03165f9081526017602090815260408083209383529281529082902054915191825290f35b34610406576020366003190112610406576004355f5260116020526020600160405f200154604051908152f35b34610406576020366003190112610406575f54600435906001600160a01b0316330361040657805f52601560205260405f2054906001820180921161132f575f52601560205260405f20555f80f35b34610406575f366003190112610406576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610406576020366003190112610406575f54600435906001600160a01b03163303610406575f908152601660205260409020805460ff19166001179055005b1561040657565b15611cf457565b634e487b7160e01b5f52600160045260245ffd5b90604051611d158161047c565b604060ff600283958054600f0b8552600181015460208601520154161515910152565b15611d3f57565b60405162461bcd60e51b8152602060048201526011602482015270131bd8dac81a5cc81c195c9c195d1d585b607a1b6044820152606490fd5b15611d7f57565b60405162461bcd60e51b815260206004820152600c60248201526b131bd8dac8195e1c1a5c995960a21b6044820152606490fd5b15611dba57565b60405162461bcd60e51b8152602060048201526011602482015270139bdd1a1a5b99c81a5cc81b1bd8dad959607a1b6044820152606490fd5b60405190611e008261047c565b5f6040838281528260208201520152565b634e487b7160e01b5f52601160045260245ffd5b906301e13380820180921161132f57565b906001820180921161132f57565b9062093a80820180921161132f57565b9190820180921161132f57565b600260406104ea93611e8e8151600f0b85906001600160801b0319825416906001600160801b0316179055565b602081015160018501550151151591019060ff801983541691151516179055565b15611eb657565b60405162461bcd60e51b8152602060048201526008602482015267185d1d1858da195960c21b6044820152606490fd5b15611eed57565b60405162461bcd60e51b8152602060048201526016602482015275546865206c6f636b206469646e27742065787069726560501b6044820152606490fd5b5f1981019190821161132f57565b9190820391821161132f57565b90816020910312610406575161054c8161140d565b6040513d5f823e3d90fd5b15611f6d57565b60405162461bcd60e51b8152602060048201526015602482015274131bd8dac81a5cc81b9bdd081c195c9c195d1d585b605a1b6044820152606490fd5b8115611fb4570490565b634e487b7160e01b5f52601260045260245ffd5b9062093a8082029180830462093a80149015171561132f57565b9081670de0b6b3a76400000291670de0b6b3a764000083040361132f57565b8181029291811591840414171561132f57565b906040516120218161049d565b608060038294805480600f0b8552831d600f0b602085015260018101546040850152600281015460608501520154910152565b61054c906012545f52600360205261206e60405f20612014565b6133a9565b1561207a57565b60405162461bcd60e51b8152602060048201526016602482015275139bc8195e1a5cdd1a5b99c81b1bd8dac8199bdd5b9960521b6044820152606490fd5b156120bf57565b60405162461bcd60e51b8152602060048201526024808201527f43616e6e6f742061646420746f2065787069726564206c6f636b2e20576974686044820152636472617760e01b6064820152608490fd5b1561211757565b60405162461bcd60e51b815260206004820152601f60248201527f43616e206f6e6c7920696e637265617365206c6f636b206475726174696f6e006044820152606490fd5b1561216357565b60405162461bcd60e51b815260206004820152601d60248201527f566f74696e67206c6f636b2063616e20626520312079656172206d61780000006044820152606490fd5b90816020910312610406575161054c816103f4565b6001600160a01b03918216815291166020820152604081019190915260806060820181905261054c92910190610517565b3d15612218573d906121ff826104fb565b9161220d60405193846104b9565b82523d5f602084013e565b606090565b91929061222c33858386613068565b803b612239575b50505050565b61225f935f60209460405196879586948593630a85bd0160e11b855233600486016121bd565b03926001600160a01b03165af15f9181612362575b506122ee576122816121ee565b805190816122e95760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b6064820152608490fd5b602001fd5b6001600160e01b03191663757a42ff60e11b0161230e575f808080612233565b60405162461bcd60e51b815260206004820152602660248201527f4552433732313a2045524337323152656365697665722072656a656374656420604482015265746f6b656e7360d01b6064820152608490fd5b61238591925060203d60201161238c575b61237d81836104b9565b8101906121a8565b905f612274565b503d612373565b1561239a57565b60405162461bcd60e51b815260206004820152601b60248201527f517565727920666f72206e6f6e6578697374656e7420746f6b656e00000000006044820152606490fd5b6020818303126104065780519067ffffffffffffffff8211610406570181601f8201121561040657805190612413826104fb565b9261242160405194856104b9565b8284526020838301011161040657815f9260208093018386015e8301015290565b1561244957565b60405162461bcd60e51b8152602060048201526012602482015271496e636f6d70617469626c65206c6f636b7360701b6044820152606490fd5b1561248a57565b60405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742061646420746f20657870697265646c6f636b000000000000006044820152606490fd5b5f91825260086020908152604080842054600a8352818520546001600160a01b03918216808752600b8552838720958316808852959094529190942091831493169091149060ff905416908215612531575b50811561252c575090565b905090565b91505f612521565b604051906125468261049d565b5f6080838281528260208201528260408201528260608201520152565b90600f0b90600f0b029081600f0b91820361132f57565b600f91820b910b03906f7fffffffffffffffffffffffffffffff19821260016001607f1b0383131761132f57565b90600f0b90600f0b019060016001607f1b0319821260016001607f1b0383131761132f57565b9060806003916125f98151600f0b85906001600160801b0319825416906001600160801b0316179055565b602081015184546001600160801b031690831b6fffffffffffffffffffffffffffffffff191617845560408101516001850155606081015160028501550151910155565b919061264c576104ea916125ce565b634e487b7160e01b5f525f60045260245ffd5b9190612669612539565b50612672612539565b50601254926126846040820151151590565b156129725750612699610de0610dd742611e25565b505b60408101511561296c57506126b5610de0610dd742611e25565b505b5f8052600f6020526126c76104ec565b5f81525f60208201524260408201524360608201525f608082015282612951575b604081018051906126f7612539565b9161270d6127068551600f0b90565b600f0b8452565b602084019161272a6127208451600f0b90565b600f0b6020860152565b805192604085019384526060860193845195606081019687526080808901519101525f9383514211612928575b939593929061276a62093a808504611fc8565b965f975b60ff8910612796575b505050505050505050612791836112016104ea9495601255565b6125ce565b6127a98a919c939495969798999c611e44565b965f428911156128e35787985096959642986127fb612818946127f66127ee8d6127e8610856612811996127e2612802995b51600f0b90565b93611f39565b90612563565b9151600f0b90565b61257a565b600f0b8d52565b8651600f0b6125a8565b6125a8565b600f0b8552565b5f6128276108568b51600f0b90565b126128db575b5f61283c6108568651600f0b90565b126128d3575b612881869287875261287a895161287461286661286089518d611f39565b87612001565b670de0b6b3a7640000900490565b90611e54565b8a52611e36565b9a4287036128ab5750505050505050506104ea92936127919143905293925f808080808080612777565b6001906128c78b6127918f9c9b9c5f52600360205260405f2090565b0197959492919461276e565b5f8452612842565b5f895261282d565b50866128026127fb612818946127f66127ee8d6127e86108569d9e9d612811996127e261292261291b865f52601360205260405f2090565b54600f0b90565b9a6127db565b935061294b61294061293b875143611f39565b611fe2565b611284855142611f39565b93612757565b506129676111e3835f52600360205260405f2090565b6126e8565b506126b7565b5061269b565b90612981612539565b9161298a612539565b925f905f93601254946129a06040830151151590565b1561305d576129b4610de0610dd742611e25565b955b604089015115613052576129cf610de0610dd742611e25565b985b6129e3875f52600f60205260405f2090565b549387612e1e575b50506129f56104ec565b5f81525f60208201524260408201524360608201525f608082015281612e03575b60408101805190612a25612539565b91612a346127068551600f0b90565b6020840191612a476127208451600f0b90565b805191604085019283526060860191825193606087019485526080880196608088519101525f9282514211612ddf575b9492612a8762093a808704611fc8565b955f965b60ff8810612c95575b50505050505050509261279191612ac894612aae83601255565b8c898c612bf4575b505050505f52600360205260405f2090565b84612ad8575b5050505050505050565b612b3c97612b3696612b3195612b0295428311612ba6575b5050428211612b49575b505050611e36565b9182612b16825f52600f60205260405f2090565b554260408601524360608601525f52601060205260405f2090565b6108f3565b9061263d565b5f80808080808080612ace565b8111612b56575b80612afa565b612b83612b74612b9f93612b6e60208c0151600f0b90565b9061257a565b915f52601360205260405f2090565b906001600160801b0319825416906001600160801b0316179055565b5f80612b50565b60200151612bd691612bbc91600f0b5b906125a8565b828414612bdd575b612b83835f52601360205260405f2090565b5f80612af0565b612bef90612b6e60208d0151600f0b90565b612bc4565b612c41612c37836127db612c30612c26610df896612b6e6020612c1d81612c4b9c0151600f0b90565b920151600f0b90565b8851600f0b6125a8565b600f0b8752565b8c51600f0b612b6e565b8751600f0b6125a8565b5f612c5a6108568351600f0b90565b12612c8c575b505f612c706108568651600f0b90565b12612c84575b60065490525f808c89612ab6565b5f8452612c76565b5f90525f612c60565b889596979b9291612ca68c92611e44565b5f9042811115612da657504297612cf893612ce0612cd68b6127e8610856612cf1986127e2612ce79851600f0b90565b8251600f0b61257a565b600f0b9052565b8a51600f0b6125a8565b600f0b8952565b5f612d076108568c51600f0b90565b12612d9e575b5f612d1c6108568a51600f0b90565b12612d96575b612d478592868652612d40885161287461286661286089518c611f39565b8952611e36565b9a428603612d6f5750504390955250859350612ac8925061279191505f905080808080612a94565b600190612d8b8c6127918f9b9a9b5f52600360205260405f2090565b019694919091612a8b565b5f8852612d22565b5f8a52612d0d565b979050612ce7612cf893612ce0612cd68b6127e8610856612cf1986127e2612dd961291b865f52601360205260405f2090565b996127db565b9250612dfd612df261293b865143611f39565b611284845142611f39565b92612a77565b50612e196111e3825f52600360205260405f2090565b612a16565b90919550612e2f6040830151151590565b61302b575b89612e54612e4e86612b318b5f52601060205260405f2090565b50612014565b91428a1180613015575b612fc4575b42821180612fae575b612ec3575b50505050612e8a61291b875f52601360205260405f2090565b9388612e98575b5f806129eb565b9050878603612ea8578390612e91565b612ebd61291b895f52601360205260405f2090565b90612e91565b604084015115612f78575f60208c01528351600f0b93612ee76108568351600f0b90565b85600f0b135f14612f4b57612f429450916127e8610856612f33612f25612f1d612bb696612b6e6127ee612f3b9b9a51600f0b90565b9651600f0b90565b956301e1338090600f0b0590565b924290611f39565b600f0b8852565b5f808981612e71565b50505050612f3b612f66612f73926301e1338090600f0b0590565b6127e8610856428d611f39565b612f42565b505050612f3b612f66612f9f612f92612f739451600f0b90565b6301e1338090600f0b0590565b600f0b60208b019081526127db565b505f612fbe6108568651600f0b90565b13612e6c565b604081015115612fe7575f6020880152612fe2612f3b8451600f0b90565b612e63565b612fe2612f3b8b6127e8610856612f338c6127db602061300b612f928b51600f0b90565b600f0b9201918252565b505f6130256108568351600f0b90565b13612e5e565b61304861303c61303c8451600f0b90565b6001600160801b031690565b60808a0152612e34565b6020890151986129d1565b6020820151956129b6565b91928361309191815f52601560205260405f20541580613152575b61308c90611eaf565b6124cf565b15610406575f838152600860205260409020546130bb906001600160a01b03848116911614611ced565b5f838152600a60205260409020546001600160a01b0316613132575b6130e18383613dce565b6130eb8382613f17565b436130fe845f52600c60205260405f2090565b556001600160a01b0390811691167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a4565b5f838152600a6020526040902080546001600160a01b03191690556130d7565b505f8281526016602052604090205460ff1615613083565b61317481336124cf565b15613278575f818152600860205260409020546001600160a01b0316808015610406576131a9835f52600860205260405f2090565b546001600160a01b038381165f908152600b602052604090209116339081149160ff916131d5916106f6565b54168115613270575b501561040657825f80936131fa835f52600a60205260405f2090565b80546001600160a01b0319166001600160a01b0384161790557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258280a46132418382613dce565b6001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8280a4565b90505f6131de565b606460405162461bcd60e51b815260206004820152602060248201527f63616c6c6572206973206e6f74206f776e6572206e6f7220617070726f7665646044820152fd5b5f19811461132f5760010190565b90420180421161132f5762093a8090049062093a8082029180830462093a80149015171561132f57801561040657428211156133555761054c9161331861331042611e25565b82111561215c565b61332b6133266005546132bc565b600555565b6133386005548095613fa9565b5061334e610802855f52601160205260405f2090565b9184613c16565b60405162461bcd60e51b815260206004820152602660248201527f43616e206f6e6c79206c6f636b20756e74696c2074696d6520696e207468652060448201526566757475726560d01b6064820152608490fd5b90604082016133bf610de0825162093a80900490565b905f915b60ff83106133fd575b5050505061303c61303c825f6133e961085661054c9651600f0b90565b126133f55751600f0b90565b5f81526127db565b61340690611e44565b5f929084811115613474575083925b6020860190613446612f3b61343c61342e8551600f0b90565b6127e861085689518b611f39565b8951600f0b61257a565b85851461346d5781612ce06134639261280c6001969551600f0b90565b83835201916133c3565b50506133cc565b925061348b61291b845f52601360205260405f2090565b613415565b6134a2815f52600c60205260405f2090565b544314613680576134b543831115611ced565b5f906134c9815f52600f60205260405f2090565b545f905b60808210613612575b505061359c6135926134fe612e4e6135a39695612b316127db965f52601060205260405f2090565b936012549061350d8282613686565b6135226111e3825f52600360205260405f2090565b928110156135ee576111e361120161353992611e36565b9061356060406135526060850151606087015190611f39565b930151604085015190611f39565b905b604084015193836135c5575b505050506127e86108566135866020880151600f0b90565b92604088015190611f39565b8351600f0b61257a565b600f0b8252565b5f600f82900b126135c05761054c906001600160801b031661303c565b505f90565b612874926112746135e596959360606135e094015190611f39565b611faa565b5f80808061356e565b506135fd606083015143611f39565b9061360c604084015142611f39565b90613562565b90928181101561367a5761363761363161362c8484611e54565b611e36565b60011c90565b9085600261365184612b31885f52601060205260405f2090565b500154116136665750600190935b01906134cd565b939150613674600191611f2b565b9161365f565b926134d6565b50505f90565b5f915f915b6080831061369a575b50505090565b909192828110156136fc5782810180821161132f576001810180911161132f5760011c908260026136d3845f52600360205260405f2090565b0154116136e85750600190935b01919061368b565b9392506136f6600191611f2b565b926136e0565b92613694565b6005111561370c57565b634e487b7160e01b5f52602160045260245ffd5b9094939291608082019582526020820152600582101561370c5760609160408201520152565b9190601454916137566002613702565b613763610b448385611e54565b6137e261376e611df3565b9161377a8151600f0b90565b926137aa6020830194855161379e6137956040870151151590565b15156040860152565b6020840152600f0b8252565b6137c66137bf86600f0b61280c8551600f0b90565b600f0b8352565b6137dc826108da895f52601160205260405f2090565b86612978565b811515806138ce575b613844575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f52604051806138283394600288429285613720565b0390a382611e54565b60408051928352602083019190915290a1565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1928315610c65576138c4613831945f5160206140155f395f51905f52975f91610c365750611ced565b91925093506137f0565b506138d96002613702565b60016137eb565b5f5160206140155f395f51905f529291613980601454936139016003613702565b61390e610b445f87611e54565b613916611df3565b8151600f0b9361394b6020840195865161393f6139366040880151151590565b15156040870152565b6020850152600f0b8352565b61395d6127065f61280c8651600f0b90565b806139b1575b5061397a826108da875f52601160205260405f2090565b84612978565b51905f516020613ff55f395f51905f52604051806139a4339460035f429285613720565b0390a36138315f82611e54565b84525f613963565b9291613a26601454936139cc6004613702565b6139d4611df3565b8151600f0b936139f46020840195865161393f6139366040880151151590565b613a0961270687600f0b61280c8651600f0b90565b80613b07575b506137dc826108da895f52601160205260405f2090565b81151580613af6575b613a6c575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f52604051806138283394600488429285613720565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1928315610c6557613aec613831945f5160206140155f395f51905f52975f91610c365750611ced565b9192509350613a34565b50613b016004613702565b5f613a2f565b84525f613a0f565b919060145491613b1e5f613702565b613b2b610b448385611e54565b613b3661376e611df3565b81151580613c05575b613b7b575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f526040518061382833945f88429285613720565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1928315610c6557613bfb613831945f5160206140155f395f51905f52975f91610c365750611ced565b9192509350613b44565b50613c0f5f613702565b6001613b3f565b9291613c3660145493613c296001613702565b6139cc610b448587611e54565b81151580613d06575b613c7c575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f52604051806138283394600188429285613720565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1928315610c6557613cfc613831945f5160206140155f395f51905f52975f91610c365750611ced565b9192509350613c44565b50613d116001613702565b6001613c3f565b81810392915f13801582851316918412161761132f57565b90613d43825f52600c60205260405f2090565b54431461368057613d5c825f52600f60205260405f2090565b549081613d6a575050505f90565b613db361359c613592613d92612e4e61085696612b3161054c995f52601060205260405f2090565b936127e8610856613da76020880151600f0b90565b92604088015190613d18565b5f613dbf8251600f0b90565b600f0b126133f55751600f0b90565b5f828152600860205260409020546001600160a01b0382811691613df491168214611ced565b5f83815260086020526040902080546001600160a01b03191690555f52600960205260405f2054915f19830192831161132f57613ea29281613e3f5f935f52600e60205260405f2090565b548083148414613eb0575082613e79613e8893613e6c8760018060a01b03165f52600d60205260405f2090565b905f5260205260405f2090565b555f52600e60205260405f2090565b556001600160a01b03165f90815260096020526040902090565b613eac8154611f2b565b9055565b613e79838592613efa613eda613e8897613e6c8b60018060a01b03165f52600d60205260405f2090565b5480613e7984613e6c8d60018060a01b03165f52600d60205260405f2090565b556001600160a01b0387165f908152600d60205260409020613e6c565b5f82815260086020526040902054613f9f9290613f3d906001600160a01b031615611ced565b5f81815260086020908152604080832080546001600160a01b0319166001600160a01b0396909616958617905584835260098083528184208054600d85528386208187528552838620879055958552600e845291842094909455939091525290565b613eac8154611e36565b6001600160a01b03811690613fca908390613fc5841515611ced565b613f17565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8180a460019056feff04ccafc360e16b67d682d17bd9503c4c6b9a131f6be6325762dc9ffc7de6245e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5ca2646970667358221220daa586e07654f3175941eb716fb0e4c9cce2054d302fba0e9594ceff4cd0329664736f6c634300081c0033ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db000000000000000000000000aa170b0f4f07d9eb34a0a7d0870a8f4f04663ca8

Deployed Bytecode

0x60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a7146103ef578063047fc9aa146103ea57806306fdde0314610340578063081812fc146103e5578063095cf5c6146103e0578063095ea7b3146103db5780630d6a2033146103d657806312aeebf7146103d15780631376f3da146103cc57806318160ddd146103c75780631c984bc3146103c257806323b872dd146103bd57806325a58b56146103b85780632e1a7d4d146103b35780632e720f7d146103ae5780632f745c59146103a9578063313ce567146103a45780633e012fa81461039f57806342842e0e1461039a578063430c208114610395578063461f711c1461039057806346c96aac1461038b5780634bc2a6571461038657806354fd4d50146103815780635594a0451461037c5780636352211e1461037757806365fc3873146103725780636f5488371461036d5780636fcfff451461036857806370a08231146103635780637116c60c1461035e578063711974841461035957806385f2aef2146103545780638c2c9baf1461034f5780638fbb38ff1461034a578063900cf0cf1461034557806395d89b4114610340578063981b24d01461033b578063986b7d8a14610336578063a0dc275814610331578063a183af521461032c578063a22cb46514610327578063a4d855df14610322578063b45a3c0e1461031d578063b88d4fde14610318578063c1f0fb9f14610313578063c2c4c5c11461030e578063c87b56dd14610309578063d1c2babb14610304578063d1febfb9146102ff578063d4e54c3b146102fa578063e0514aba146102f5578063e441135c146102f0578063e7e242d4146102eb578063e93e3b1f146102e6578063e985e9c5146102e1578063ee99fe28146102dc578063f1127ed8146102d7578063f8a05763146102d2578063fbd3a29d146102cd578063fc0c546a146102c85763fd4a77f1146102c3575f80fd5b611ca6565b611c62565b611c13565b611be6565b611b8f565b611ae4565b611a88565b611a6b565b611a4c565b611a22565b611a01565b6119bc565b61194e565b6117fe565b6116ed565b611685565b611648565b6115c0565b611577565b6114bf565b611417565b611351565b611334565b6112e0565b611197565b61054f565b61117a565b61114b565b61112a565b611102565b6110d5565b6110b7565b61107f565b611041565b611017565b610fbb565b610f89565b610f61565b610f1a565b610ed3565b610eac565b610e67565b610e36565b610e0d565b610d3e565b610d23565b610cde565b610c91565b610a64565b610a4a565b610a31565b6109be565b61099b565b61091d565b6107a8565b61077e565b610657565b61060e565b61059a565b61044b565b61040a565b6001600160e01b031981160361040657565b5f80fd5b3461040657602036600319011261040657600435610427816103f4565b63ffffffff60e01b165f526004602052602060ff60405f2054166040519015158152f35b34610406575f366003190112610406576020601454604051908152f35b634e487b7160e01b5f52604160045260245ffd5b6060810190811067ffffffffffffffff82111761049857604052565b610468565b60a0810190811067ffffffffffffffff82111761049857604052565b90601f8019910116810190811067ffffffffffffffff82111761049857604052565b604051906104ea6060836104b9565b565b604051906104ea60a0836104b9565b67ffffffffffffffff811161049857601f01601f191660200190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90602061054c928181520190610517565b90565b34610406575f366003190112610406576105966040516105706040826104b9565b60058152641d9953919560da1b6020820152604051918291602083526020830190610517565b0390f35b34610406576020366003190112610406576004355f52600a602052602060018060a01b0360405f205416604051908152f35b600435906001600160a01b038216820361040657565b602435906001600160a01b038216820361040657565b604435906001600160a01b038216820361040657565b34610406576020366003190112610406576106276105cc565b600154906001600160a01b0382163303610406576001600160a01b03166001600160a01b03199190911617600155005b34610406576040366003190112610406576106706105cc565b6024355f818152600860205260409020549091906001600160a01b03168015610406576001600160a01b03821691818314610406576107509061071f6106ce6106c1875f52600860205260405f2090565b546001600160a01b031690565b6001600160a01b038581165f908152600b60205260409020911633908114916107129161070b915b9060018060a01b03165f5260205260405f2090565b5460ff1690565b8115610776575b50611ce6565b610731855f52600a60205260405f2090565b80546001600160a01b0319166001600160a01b03909216919091179055565b7f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9255f80a4005b90505f610719565b34610406576020366003190112610406576004355f526015602052602060405f2054604051908152f35b34610406576020366003190112610406576007546004355f1960ff8316016104065760026108df9260ff1916176007556107ea6107e582336124cf565b611ced565b6108da610807610802835f52601160205260405f2090565b611d08565b9161081e6108186040850151151590565b15611d38565b61082d60208401514210611d78565b6108455f61083c8551600f0b90565b600f0b13611db3565b61087b61085c6108568551600f0b90565b600f0b90565b936108656104db565b905f82525f60208301525f604083015283612978565b6108ab6108a6610889611df3565b5f60208201526001604082015294600f81900b8652600654611e54565b600655565b6108cc836108b76104db565b5f81525f60208201525f604082015283612978565b5f52601160205260405f2090565b611e61565b6108f1600160ff196007541617600755565b005b90633b9aca008110156109095760021b01905f90565b634e487b7160e01b5f52603260045260245ffd5b3461040657604036600319011261040657600435602435905f52601060205260405f2090633b9aca0081101561040657610956916108f3565b5080546001820154600283015460039093015460408051600f85810b8252608095861d900b60208201529081019290925260608201939093529081019190915260a090f35b34610406575f3660031901126104065760206109b642612054565b604051908152f35b3461040657604036600319011261040657602060016109ec600435602435905f526010845260405f206108f3565b500154604051908152f35b6060906003190112610406576004356001600160a01b038116810361040657906024356001600160a01b0381168103610406579060443590565b34610406576108f1610a42366109f7565b903392613068565b34610406575f366003190112610406576020604051438152f35b3461040657602036600319011261040657600435600754600160ff8216036104065760ff1916600217600755610a9d6107e582336124cf565b610aaf815f52601560205260405f2090565b541580610c6a575b610ac090611eaf565b610ad5610802825f52601160205260405f2090565b610ae56108186040830151151590565b610af56020820151421015611ee6565b610b036108568251600f0b90565b91610b30610b0f6104db565b5f81525f60208201525f60408201526108da835f52601160205260405f2090565b610b5160145492610b49610b448686611f39565b601455565b6108656104db565b60405163a9059cbb60e01b815233600482015260248101849052926020846044815f6001600160a01b037f00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db165af1918215610c6557610bc9610c13935f5160206140155f395f51905f52965f91610c36575b50611ced565b610bd28161316a565b6040805191825260208201839052429082015233907f02f25270a4d87bea75db541cdfe559334a275b4a233520ed6c0a2429667cca9490606090a282611f39565b60408051928352602083019190915290a16108f1600160ff196007541617600755565b610c58915060203d602011610c5e575b610c5081836104b9565b810190611f46565b5f610bc3565b503d610c46565b611f5b565b50610ac0610c8a610c8661070b845f52601660205260405f2090565b1590565b9050610ab7565b3461040657602036600319011261040657610caa6105cc565b6001546001600160a01b0316330361040657600280546001600160a01b0319166001600160a01b0392909216919091179055005b3461040657604036600319011261040657610cf76105cc565b6024359060018060a01b03165f52600d60205260405f20905f52602052602060405f2054604051908152f35b34610406575f36600319011261040657602060405160128152f35b34610406576020366003190112610406576007546004355f1960ff831601610406576108da6108a69160026108df9460ff191617600755610d826107e582336124cf565b805f5260116020526108cc610d9960405f20611d08565b610dab60016040830151151514611f66565b610db96108568251600f0b90565b90610e07610dc5611df3565b968793610de5610de0610dd742611e25565b62093a80900490565b611fc8565b6020860152610dff610df88551600f0b90565b600f0b8652565b600654611f39565b83612978565b34610406576108f1610e1e366109f7565b9060405192610e2e6020856104b9565b5f845261221d565b34610406576040366003190112610406576020610e5d610e546105cc565b602435906124cf565b6040519015158152f35b34610406576020366003190112610406576020610e9c600435805f52600f835260405f2054905f526010835260405f206108f3565b505460801d60405190600f0b8152f35b34610406575f366003190112610406575f546040516001600160a01b039091168152602090f35b3461040657602036600319011261040657610eec6105cc565b5f54906001600160a01b0382163303610406576001600160a01b03166001600160a01b031991909116175f55005b34610406575f36600319011261040657610596604051610f3b6040826104b9565b60058152640312e302e360dc1b6020820152604051918291602083526020830190610517565b34610406575f366003190112610406576002546040516001600160a01b039091168152602090f35b34610406576020366003190112610406576004355f526008602052602060018060a01b0360405f205416604051908152f35b3461040657604036600319011261040657600754602435906004355f1960ff83160161040657610596926002610ffa9360ff19161760075533916132ca565b6007805460ff191660011790556040519081529081906020820190565b34610406576020366003190112610406576004355f52600c602052602060405f2054604051908152f35b34610406576020366003190112610406576001600160a01b036110626105cc565b165f526018602052602063ffffffff60405f205416604051908152f35b34610406576020366003190112610406576001600160a01b036110a06105cc565b165f526009602052602060405f2054604051908152f35b346104065760203660031901126104065760206109b6600435612054565b34610406576020366003190112610406576004355f526013602052602060405f2054600f0b604051908152f35b34610406575f366003190112610406576001546040516001600160a01b039091168152602090f35b346104065760403660031901126104065760206109b6602435600435613490565b34610406576020366003190112610406576004355f526016602052602060ff60405f2054166040519015158152f35b34610406575f366003190112610406576020601254604051908152f35b346104065760203660031901126104065761059661123a6004356111bd43821115611ced565b6112346012546111cd8184613686565b926111e86111e3855f52600360205260405f2090565b612014565b935f92811015611294576111e361120161120f92611e36565b5f52600360205260405f2090565b6060850190815160608201938451820361124a575b50505050505b6040830151611e54565b906133a9565b6040519081529081906020820190565b61128a955091611274604061126661127a946112849796611f39565b92015160408a015190611f39565b90612001565b9251905190611f39565b90611faa565b5f80808080611224565b5060608401908151904382036112ad575b50505061122a565b6112d893506112c2611284926112d092611f39565b611274604088015142611f39565b915143611f39565b5f80806112a5565b34610406576020366003190112610406575f54600435906001600160a01b0316330361040657805f52601560205260405f2054905f19820191821161132f575f52601560205260405f20555f80f35b611e11565b34610406575f36600319011261040657602060405162093a808152f35b34610406576040366003190112610406576007546004356024355f1960ff8416016104065760026108df9360ff1916176007556113916107e583336124cf565b815f5260116020526113a560405f20611d08565b916113b1821515611ced565b6113c05f8451600f0b13612073565b6020830151421080156113f5575b6113d7906120b8565b604083015115613746576113f06108a683600654611e54565b613746565b506113d76114066040850151151590565b90506113ce565b8015150361040657565b34610406576040366003190112610406576114306105cc565b60243561143c8161140d565b6001600160a01b0382169161148f90829061147e9061145d33871415611ced565b335f52600b60205260405f209060018060a01b03165f5260205260405f2090565b9060ff801983541691151516179055565b60405190151581527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160203392a3005b34610406576040366003190112610406576007546024356004355f1960ff8416016104065760026108df9360ff1916176007556114ff6107e582336124cf565b805f526011602052611523610de0610dd761151c60405f20611d08565b9442611e54565b906115346108186040850151151590565b61155e6020840161154742825111611d78565b6115565f61083c8751600f0b90565b518311612110565b61157261156a42611e25565b83111561215c565b6138e0565b34610406576020366003190112610406576004355f526011602052606060405f208054600f0b9060ff600260018301549201541690604051928352602083015215156040820152f35b34610406576080366003190112610406576115d96105cc565b6115e16105e2565b906044356064359267ffffffffffffffff8411610406573660238501121561040657836004013592611612846104fb565b9361162060405195866104b9565b8085523660248288010111610406576020815f9260246108f19901838901378601015261221d565b34610406576020366003190112610406575f54600435906001600160a01b03163303610406575f908152601660205260409020805460ff19169055005b34610406575f36600319011261040657600754600160ff8216036104065760ff19166002176007556040516108df906116bd8161047c565b5f81525f60208201525f6040820152604051906116d98261047c565b5f82525f60208301525f604083015261265f565b34610406576020366003190112610406576004355f81815260086020526040902061172c906001600160a01b0390611724906106c1565b161515612393565b5f611742610802835f52601160205260405f2090565b60025461176590611759906001600160a01b031681565b6001600160a01b031690565b61176f4285613d30565b61178261085660208501519451600f0b90565b946117ba6040519687958694859463dd9ec14960e01b8652600486019094939260609260808301968352602083015260408201520152565b03915afa8015610c6557610596915f916117dc575b506040519182918261053b565b6117f891503d805f833e6117f081836104b9565b8101906123df565b5f6117cf565b34610406576040366003190112610406576108f16004356024359061182b815f52601560205260405f2090565b54158061192b575b61183c90611eaf565b61184882821415611ce6565b61185a61185582336124cf565b611ce6565b61186761185583336124cf565b61187c610802825f52601160205260405f2090565b90611892610802845f52601160205260405f2090565b926118c16118a36040850151151590565b6118b86118b36040880151151590565b151590565b90151514612442565b61191e6118d26108568551600f0b90565b9261191960208601516020880151808210155f146119235750955b610b496118f86104db565b5f81525f60208201525f60408201526108da855f52601160205260405f2090565b61316a565b6139b9565b9050956118ed565b5061183c611947610c8661070b845f52601660205260405f2090565b9050611833565b34610406576020366003190112610406576004355f52600360205260405f20805461059660018301549260036002820154910154906040519484869560801d90600f0b869192608093969594919660a0840197600f0b8452600f0b6020840152604083015260608201520152565b34610406576060366003190112610406576004356024356119db6105f8565b60075491600160ff84160361040657610596936002610ffa9460ff1916176007556132ca565b346104065760403660031901126104065760206109b6602435600435613d30565b34610406576020366003190112610406576004355f52600f602052602060405f2054604051908152f35b346104065760203660031901126104065760206109b642600435613d30565b34610406575f366003190112610406576020600654604051908152f35b3461040657604036600319011261040657602060ff611ad8611aa86105cc565b611ab06105e2565b6001600160a01b039182165f908152600b865260408082209290931681526020919091522090565b54166040519015158152f35b3461040657604036600319011261040657600435602435600754600160ff8216036104065760029060ff191617600755815f526011602052611b2860405f20611d08565b908015610406576108df92611b425f8451600f0b13612073565b602083015142108015611b77575b611b5990612483565b604083015115613b0f57611b726108a683600654611e54565b613b0f565b50611b59611b886040850151151590565b9050611b50565b3461040657604036600319011261040657611ba86105cc565b6024359063ffffffff8216809203610406576001600160a01b03165f9081526017602090815260408083209383529281529082902054915191825290f35b34610406576020366003190112610406576004355f5260116020526020600160405f200154604051908152f35b34610406576020366003190112610406575f54600435906001600160a01b0316330361040657805f52601560205260405f2054906001820180921161132f575f52601560205260405f20555f80f35b34610406575f366003190112610406576040517f00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db6001600160a01b03168152602090f35b34610406576020366003190112610406575f54600435906001600160a01b03163303610406575f908152601660205260409020805460ff19166001179055005b1561040657565b15611cf457565b634e487b7160e01b5f52600160045260245ffd5b90604051611d158161047c565b604060ff600283958054600f0b8552600181015460208601520154161515910152565b15611d3f57565b60405162461bcd60e51b8152602060048201526011602482015270131bd8dac81a5cc81c195c9c195d1d585b607a1b6044820152606490fd5b15611d7f57565b60405162461bcd60e51b815260206004820152600c60248201526b131bd8dac8195e1c1a5c995960a21b6044820152606490fd5b15611dba57565b60405162461bcd60e51b8152602060048201526011602482015270139bdd1a1a5b99c81a5cc81b1bd8dad959607a1b6044820152606490fd5b60405190611e008261047c565b5f6040838281528260208201520152565b634e487b7160e01b5f52601160045260245ffd5b906301e13380820180921161132f57565b906001820180921161132f57565b9062093a80820180921161132f57565b9190820180921161132f57565b600260406104ea93611e8e8151600f0b85906001600160801b0319825416906001600160801b0316179055565b602081015160018501550151151591019060ff801983541691151516179055565b15611eb657565b60405162461bcd60e51b8152602060048201526008602482015267185d1d1858da195960c21b6044820152606490fd5b15611eed57565b60405162461bcd60e51b8152602060048201526016602482015275546865206c6f636b206469646e27742065787069726560501b6044820152606490fd5b5f1981019190821161132f57565b9190820391821161132f57565b90816020910312610406575161054c8161140d565b6040513d5f823e3d90fd5b15611f6d57565b60405162461bcd60e51b8152602060048201526015602482015274131bd8dac81a5cc81b9bdd081c195c9c195d1d585b605a1b6044820152606490fd5b8115611fb4570490565b634e487b7160e01b5f52601260045260245ffd5b9062093a8082029180830462093a80149015171561132f57565b9081670de0b6b3a76400000291670de0b6b3a764000083040361132f57565b8181029291811591840414171561132f57565b906040516120218161049d565b608060038294805480600f0b8552831d600f0b602085015260018101546040850152600281015460608501520154910152565b61054c906012545f52600360205261206e60405f20612014565b6133a9565b1561207a57565b60405162461bcd60e51b8152602060048201526016602482015275139bc8195e1a5cdd1a5b99c81b1bd8dac8199bdd5b9960521b6044820152606490fd5b156120bf57565b60405162461bcd60e51b8152602060048201526024808201527f43616e6e6f742061646420746f2065787069726564206c6f636b2e20576974686044820152636472617760e01b6064820152608490fd5b1561211757565b60405162461bcd60e51b815260206004820152601f60248201527f43616e206f6e6c7920696e637265617365206c6f636b206475726174696f6e006044820152606490fd5b1561216357565b60405162461bcd60e51b815260206004820152601d60248201527f566f74696e67206c6f636b2063616e20626520312079656172206d61780000006044820152606490fd5b90816020910312610406575161054c816103f4565b6001600160a01b03918216815291166020820152604081019190915260806060820181905261054c92910190610517565b3d15612218573d906121ff826104fb565b9161220d60405193846104b9565b82523d5f602084013e565b606090565b91929061222c33858386613068565b803b612239575b50505050565b61225f935f60209460405196879586948593630a85bd0160e11b855233600486016121bd565b03926001600160a01b03165af15f9181612362575b506122ee576122816121ee565b805190816122e95760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b6064820152608490fd5b602001fd5b6001600160e01b03191663757a42ff60e11b0161230e575f808080612233565b60405162461bcd60e51b815260206004820152602660248201527f4552433732313a2045524337323152656365697665722072656a656374656420604482015265746f6b656e7360d01b6064820152608490fd5b61238591925060203d60201161238c575b61237d81836104b9565b8101906121a8565b905f612274565b503d612373565b1561239a57565b60405162461bcd60e51b815260206004820152601b60248201527f517565727920666f72206e6f6e6578697374656e7420746f6b656e00000000006044820152606490fd5b6020818303126104065780519067ffffffffffffffff8211610406570181601f8201121561040657805190612413826104fb565b9261242160405194856104b9565b8284526020838301011161040657815f9260208093018386015e8301015290565b1561244957565b60405162461bcd60e51b8152602060048201526012602482015271496e636f6d70617469626c65206c6f636b7360701b6044820152606490fd5b1561248a57565b60405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742061646420746f20657870697265646c6f636b000000000000006044820152606490fd5b5f91825260086020908152604080842054600a8352818520546001600160a01b03918216808752600b8552838720958316808852959094529190942091831493169091149060ff905416908215612531575b50811561252c575090565b905090565b91505f612521565b604051906125468261049d565b5f6080838281528260208201528260408201528260608201520152565b90600f0b90600f0b029081600f0b91820361132f57565b600f91820b910b03906f7fffffffffffffffffffffffffffffff19821260016001607f1b0383131761132f57565b90600f0b90600f0b019060016001607f1b0319821260016001607f1b0383131761132f57565b9060806003916125f98151600f0b85906001600160801b0319825416906001600160801b0316179055565b602081015184546001600160801b031690831b6fffffffffffffffffffffffffffffffff191617845560408101516001850155606081015160028501550151910155565b919061264c576104ea916125ce565b634e487b7160e01b5f525f60045260245ffd5b9190612669612539565b50612672612539565b50601254926126846040820151151590565b156129725750612699610de0610dd742611e25565b505b60408101511561296c57506126b5610de0610dd742611e25565b505b5f8052600f6020526126c76104ec565b5f81525f60208201524260408201524360608201525f608082015282612951575b604081018051906126f7612539565b9161270d6127068551600f0b90565b600f0b8452565b602084019161272a6127208451600f0b90565b600f0b6020860152565b805192604085019384526060860193845195606081019687526080808901519101525f9383514211612928575b939593929061276a62093a808504611fc8565b965f975b60ff8910612796575b505050505050505050612791836112016104ea9495601255565b6125ce565b6127a98a919c939495969798999c611e44565b965f428911156128e35787985096959642986127fb612818946127f66127ee8d6127e8610856612811996127e2612802995b51600f0b90565b93611f39565b90612563565b9151600f0b90565b61257a565b600f0b8d52565b8651600f0b6125a8565b6125a8565b600f0b8552565b5f6128276108568b51600f0b90565b126128db575b5f61283c6108568651600f0b90565b126128d3575b612881869287875261287a895161287461286661286089518d611f39565b87612001565b670de0b6b3a7640000900490565b90611e54565b8a52611e36565b9a4287036128ab5750505050505050506104ea92936127919143905293925f808080808080612777565b6001906128c78b6127918f9c9b9c5f52600360205260405f2090565b0197959492919461276e565b5f8452612842565b5f895261282d565b50866128026127fb612818946127f66127ee8d6127e86108569d9e9d612811996127e261292261291b865f52601360205260405f2090565b54600f0b90565b9a6127db565b935061294b61294061293b875143611f39565b611fe2565b611284855142611f39565b93612757565b506129676111e3835f52600360205260405f2090565b6126e8565b506126b7565b5061269b565b90612981612539565b9161298a612539565b925f905f93601254946129a06040830151151590565b1561305d576129b4610de0610dd742611e25565b955b604089015115613052576129cf610de0610dd742611e25565b985b6129e3875f52600f60205260405f2090565b549387612e1e575b50506129f56104ec565b5f81525f60208201524260408201524360608201525f608082015281612e03575b60408101805190612a25612539565b91612a346127068551600f0b90565b6020840191612a476127208451600f0b90565b805191604085019283526060860191825193606087019485526080880196608088519101525f9282514211612ddf575b9492612a8762093a808704611fc8565b955f965b60ff8810612c95575b50505050505050509261279191612ac894612aae83601255565b8c898c612bf4575b505050505f52600360205260405f2090565b84612ad8575b5050505050505050565b612b3c97612b3696612b3195612b0295428311612ba6575b5050428211612b49575b505050611e36565b9182612b16825f52600f60205260405f2090565b554260408601524360608601525f52601060205260405f2090565b6108f3565b9061263d565b5f80808080808080612ace565b8111612b56575b80612afa565b612b83612b74612b9f93612b6e60208c0151600f0b90565b9061257a565b915f52601360205260405f2090565b906001600160801b0319825416906001600160801b0316179055565b5f80612b50565b60200151612bd691612bbc91600f0b5b906125a8565b828414612bdd575b612b83835f52601360205260405f2090565b5f80612af0565b612bef90612b6e60208d0151600f0b90565b612bc4565b612c41612c37836127db612c30612c26610df896612b6e6020612c1d81612c4b9c0151600f0b90565b920151600f0b90565b8851600f0b6125a8565b600f0b8752565b8c51600f0b612b6e565b8751600f0b6125a8565b5f612c5a6108568351600f0b90565b12612c8c575b505f612c706108568651600f0b90565b12612c84575b60065490525f808c89612ab6565b5f8452612c76565b5f90525f612c60565b889596979b9291612ca68c92611e44565b5f9042811115612da657504297612cf893612ce0612cd68b6127e8610856612cf1986127e2612ce79851600f0b90565b8251600f0b61257a565b600f0b9052565b8a51600f0b6125a8565b600f0b8952565b5f612d076108568c51600f0b90565b12612d9e575b5f612d1c6108568a51600f0b90565b12612d96575b612d478592868652612d40885161287461286661286089518c611f39565b8952611e36565b9a428603612d6f5750504390955250859350612ac8925061279191505f905080808080612a94565b600190612d8b8c6127918f9b9a9b5f52600360205260405f2090565b019694919091612a8b565b5f8852612d22565b5f8a52612d0d565b979050612ce7612cf893612ce0612cd68b6127e8610856612cf1986127e2612dd961291b865f52601360205260405f2090565b996127db565b9250612dfd612df261293b865143611f39565b611284845142611f39565b92612a77565b50612e196111e3825f52600360205260405f2090565b612a16565b90919550612e2f6040830151151590565b61302b575b89612e54612e4e86612b318b5f52601060205260405f2090565b50612014565b91428a1180613015575b612fc4575b42821180612fae575b612ec3575b50505050612e8a61291b875f52601360205260405f2090565b9388612e98575b5f806129eb565b9050878603612ea8578390612e91565b612ebd61291b895f52601360205260405f2090565b90612e91565b604084015115612f78575f60208c01528351600f0b93612ee76108568351600f0b90565b85600f0b135f14612f4b57612f429450916127e8610856612f33612f25612f1d612bb696612b6e6127ee612f3b9b9a51600f0b90565b9651600f0b90565b956301e1338090600f0b0590565b924290611f39565b600f0b8852565b5f808981612e71565b50505050612f3b612f66612f73926301e1338090600f0b0590565b6127e8610856428d611f39565b612f42565b505050612f3b612f66612f9f612f92612f739451600f0b90565b6301e1338090600f0b0590565b600f0b60208b019081526127db565b505f612fbe6108568651600f0b90565b13612e6c565b604081015115612fe7575f6020880152612fe2612f3b8451600f0b90565b612e63565b612fe2612f3b8b6127e8610856612f338c6127db602061300b612f928b51600f0b90565b600f0b9201918252565b505f6130256108568351600f0b90565b13612e5e565b61304861303c61303c8451600f0b90565b6001600160801b031690565b60808a0152612e34565b6020890151986129d1565b6020820151956129b6565b91928361309191815f52601560205260405f20541580613152575b61308c90611eaf565b6124cf565b15610406575f838152600860205260409020546130bb906001600160a01b03848116911614611ced565b5f838152600a60205260409020546001600160a01b0316613132575b6130e18383613dce565b6130eb8382613f17565b436130fe845f52600c60205260405f2090565b556001600160a01b0390811691167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a4565b5f838152600a6020526040902080546001600160a01b03191690556130d7565b505f8281526016602052604090205460ff1615613083565b61317481336124cf565b15613278575f818152600860205260409020546001600160a01b0316808015610406576131a9835f52600860205260405f2090565b546001600160a01b038381165f908152600b602052604090209116339081149160ff916131d5916106f6565b54168115613270575b501561040657825f80936131fa835f52600a60205260405f2090565b80546001600160a01b0319166001600160a01b0384161790557f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258280a46132418382613dce565b6001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8280a4565b90505f6131de565b606460405162461bcd60e51b815260206004820152602060248201527f63616c6c6572206973206e6f74206f776e6572206e6f7220617070726f7665646044820152fd5b5f19811461132f5760010190565b90420180421161132f5762093a8090049062093a8082029180830462093a80149015171561132f57801561040657428211156133555761054c9161331861331042611e25565b82111561215c565b61332b6133266005546132bc565b600555565b6133386005548095613fa9565b5061334e610802855f52601160205260405f2090565b9184613c16565b60405162461bcd60e51b815260206004820152602660248201527f43616e206f6e6c79206c6f636b20756e74696c2074696d6520696e207468652060448201526566757475726560d01b6064820152608490fd5b90604082016133bf610de0825162093a80900490565b905f915b60ff83106133fd575b5050505061303c61303c825f6133e961085661054c9651600f0b90565b126133f55751600f0b90565b5f81526127db565b61340690611e44565b5f929084811115613474575083925b6020860190613446612f3b61343c61342e8551600f0b90565b6127e861085689518b611f39565b8951600f0b61257a565b85851461346d5781612ce06134639261280c6001969551600f0b90565b83835201916133c3565b50506133cc565b925061348b61291b845f52601360205260405f2090565b613415565b6134a2815f52600c60205260405f2090565b544314613680576134b543831115611ced565b5f906134c9815f52600f60205260405f2090565b545f905b60808210613612575b505061359c6135926134fe612e4e6135a39695612b316127db965f52601060205260405f2090565b936012549061350d8282613686565b6135226111e3825f52600360205260405f2090565b928110156135ee576111e361120161353992611e36565b9061356060406135526060850151606087015190611f39565b930151604085015190611f39565b905b604084015193836135c5575b505050506127e86108566135866020880151600f0b90565b92604088015190611f39565b8351600f0b61257a565b600f0b8252565b5f600f82900b126135c05761054c906001600160801b031661303c565b505f90565b612874926112746135e596959360606135e094015190611f39565b611faa565b5f80808061356e565b506135fd606083015143611f39565b9061360c604084015142611f39565b90613562565b90928181101561367a5761363761363161362c8484611e54565b611e36565b60011c90565b9085600261365184612b31885f52601060205260405f2090565b500154116136665750600190935b01906134cd565b939150613674600191611f2b565b9161365f565b926134d6565b50505f90565b5f915f915b6080831061369a575b50505090565b909192828110156136fc5782810180821161132f576001810180911161132f5760011c908260026136d3845f52600360205260405f2090565b0154116136e85750600190935b01919061368b565b9392506136f6600191611f2b565b926136e0565b92613694565b6005111561370c57565b634e487b7160e01b5f52602160045260245ffd5b9094939291608082019582526020820152600582101561370c5760609160408201520152565b9190601454916137566002613702565b613763610b448385611e54565b6137e261376e611df3565b9161377a8151600f0b90565b926137aa6020830194855161379e6137956040870151151590565b15156040860152565b6020840152600f0b8252565b6137c66137bf86600f0b61280c8551600f0b90565b600f0b8352565b6137dc826108da895f52601160205260405f2090565b86612978565b811515806138ce575b613844575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f52604051806138283394600288429285613720565b0390a382611e54565b60408051928352602083019190915290a1565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db165af1928315610c65576138c4613831945f5160206140155f395f51905f52975f91610c365750611ced565b91925093506137f0565b506138d96002613702565b60016137eb565b5f5160206140155f395f51905f529291613980601454936139016003613702565b61390e610b445f87611e54565b613916611df3565b8151600f0b9361394b6020840195865161393f6139366040880151151590565b15156040870152565b6020850152600f0b8352565b61395d6127065f61280c8651600f0b90565b806139b1575b5061397a826108da875f52601160205260405f2090565b84612978565b51905f516020613ff55f395f51905f52604051806139a4339460035f429285613720565b0390a36138315f82611e54565b84525f613963565b9291613a26601454936139cc6004613702565b6139d4611df3565b8151600f0b936139f46020840195865161393f6139366040880151151590565b613a0961270687600f0b61280c8651600f0b90565b80613b07575b506137dc826108da895f52601160205260405f2090565b81151580613af6575b613a6c575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f52604051806138283394600488429285613720565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db165af1928315610c6557613aec613831945f5160206140155f395f51905f52975f91610c365750611ced565b9192509350613a34565b50613b016004613702565b5f613a2f565b84525f613a0f565b919060145491613b1e5f613702565b613b2b610b448385611e54565b613b3661376e611df3565b81151580613c05575b613b7b575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f526040518061382833945f88429285613720565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db165af1928315610c6557613bfb613831945f5160206140155f395f51905f52975f91610c365750611ced565b9192509350613b44565b50613c0f5f613702565b6001613b3f565b9291613c3660145493613c296001613702565b6139cc610b448587611e54565b81151580613d06575b613c7c575b905f5160206140155f395f51905f52936138319251905f516020613ff55f395f51905f52604051806138283394600188429285613720565b6040516323b872dd60e01b815233600482015230602482015260448101839052939091906020856064815f6001600160a01b037f00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db165af1928315610c6557613cfc613831945f5160206140155f395f51905f52975f91610c365750611ced565b9192509350613c44565b50613d116001613702565b6001613c3f565b81810392915f13801582851316918412161761132f57565b90613d43825f52600c60205260405f2090565b54431461368057613d5c825f52600f60205260405f2090565b549081613d6a575050505f90565b613db361359c613592613d92612e4e61085696612b3161054c995f52601060205260405f2090565b936127e8610856613da76020880151600f0b90565b92604088015190613d18565b5f613dbf8251600f0b90565b600f0b126133f55751600f0b90565b5f828152600860205260409020546001600160a01b0382811691613df491168214611ced565b5f83815260086020526040902080546001600160a01b03191690555f52600960205260405f2054915f19830192831161132f57613ea29281613e3f5f935f52600e60205260405f2090565b548083148414613eb0575082613e79613e8893613e6c8760018060a01b03165f52600d60205260405f2090565b905f5260205260405f2090565b555f52600e60205260405f2090565b556001600160a01b03165f90815260096020526040902090565b613eac8154611f2b565b9055565b613e79838592613efa613eda613e8897613e6c8b60018060a01b03165f52600d60205260405f2090565b5480613e7984613e6c8d60018060a01b03165f52600d60205260405f2090565b556001600160a01b0387165f908152600d60205260409020613e6c565b5f82815260086020526040902054613f9f9290613f3d906001600160a01b031615611ced565b5f81815260086020908152604080832080546001600160a01b0319166001600160a01b0396909616958617905584835260098083528184208054600d85528386208187528552838620879055958552600e845291842094909455939091525290565b613eac8154611e36565b6001600160a01b03811690613fca908390613fc5841515611ced565b613f17565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8180a460019056feff04ccafc360e16b67d682d17bd9503c4c6b9a131f6be6325762dc9ffc7de6245e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5ca2646970667358221220daa586e07654f3175941eb716fb0e4c9cce2054d302fba0e9594ceff4cd0329664736f6c634300081c0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db000000000000000000000000aa170b0f4f07d9eb34a0a7d0870a8f4f04663ca8

-----Decoded View---------------
Arg [0] : token_addr (address): 0x28245AB01298eaEf7933bC90d35Bd9DbCA5C89DB
Arg [1] : art_proxy (address): 0xaa170b0f4f07d9eb34A0A7d0870a8f4F04663ca8

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000028245ab01298eaef7933bc90d35bd9dbca5c89db
Arg [1] : 000000000000000000000000aa170b0f4f07d9eb34a0a7d0870a8f4f04663ca8


[ Download: CSV Export  ]
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.