ETH Price: $3,726.67 (+3.70%)

Token

ERC-20: EthDog (EDog)
 

Overview

Max Total Supply

1,000,000,000 EDog

Holders

293

Market

Onchain Market Cap

$0.00

Circulating Supply Market Cap

-

Other Info

Token Contract (WITH 18 Decimals)

Balance
13,861,319.385785989907643997 EDog

Value
$0.00
0xf4Cd56Eba96905c5aa56267e33DfbD6b3aEEA6aC
Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information
# Exchange Pair Price  24H Volume % Volume

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0xeE953a21...9e4f5ae6C
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
EmToken

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 1000000 runs

Other Settings:
paris EvmVersion
File 1 of 11 : EmToken.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {EmCore} from "./EmCore.sol";

error NotEmCore();
error Forbidden();

/// @title The Em protocol ERC20 token template.
/// @notice Until graduation, the token allowance is restricted to only the EmCore, and transfers to certain external entities are not
///         allowed (eg. Uniswap pairs). This makes sure the token is transferable but not tradable before graduation.
contract EmToken is ERC20 {
    struct Metadata {
        EmToken token;
        string name;
        string symbol;
        string description;
        string extended;
        address creator;
        bool isGraduated;
        uint256 mcap;
    }

    string public description;
    string public extended;
    EmCore public immutable emCore;
    address public immutable creator;

    address[] public holders;
    mapping(address => bool) public isHolder;

    /// @notice Locked before graduation to restrict trading to EmCore
    bool public isUnrestricted = false;

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals,
        uint256 _supply,
        string memory _description,
        string memory _extended,
        address _emCore,
        address _creator
    ) ERC20(_name, _symbol, _decimals) {
        description = _description;
        extended = _extended;
        emCore = EmCore(_emCore);
        creator = _creator;

        _mint(msg.sender, _supply);
        _addHolder(msg.sender);
    }

    function _addHolder(address holder) private {
        if (!isHolder[holder]) {
            holders.push(holder);
            isHolder[holder] = true;
        }
    }

    function getMetadata() public view returns (Metadata memory) {
        EmCore.Pool memory pool = emCore.getPool(this);
        return
            Metadata(
                EmToken(address(this)),
                this.name(),
                this.symbol(),
                description,
                extended,
                creator,
                isGraduated(),
                pool.lastMcapInEth
            );
    }

    function isGraduated() public view returns (bool) {
        EmCore.Pool memory pool = emCore.getPool(this);
        return pool.headmaster != address(0);
    }

    function setIsUnrestricted(bool _isUnrestricted) public {
        if (msg.sender != address(emCore)) revert NotEmCore();
        isUnrestricted = _isUnrestricted;
    }

    function transfer(
        address to,
        uint256 amount
    ) public override returns (bool) {
        if (!isUnrestricted) {
            bool isPregradRestricted = emCore
                .externalEntities_()
                .isPregradRestricted(address(this), address(to));
            if (isPregradRestricted) revert Forbidden();
        }
        _addHolder(to);
        return super.transfer(to, amount);
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public override returns (bool) {
        if (!isUnrestricted) {
            bool isPregradRestricted = emCore
                .externalEntities_()
                .isPregradRestricted(address(this), address(to));
            if (isPregradRestricted) revert Forbidden();
        }
        // Pre-approve EmCore for improved UX
        if (allowance[from][address(emCore)] != type(uint256).max) {
            allowance[from][address(emCore)] = type(uint256).max;
        }
        _addHolder(to);
        return super.transferFrom(from, to, amount);
    }

    function approve(
        address spender,
        uint256 amount
    ) public override returns (bool) {
        if (!isUnrestricted) revert Forbidden();

        return super.approve(spender, amount);
    }

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public override {
        if (!isUnrestricted) revert Forbidden();

        super.permit(owner, spender, value, deadline, v, r, s);
    }

    /// Get all addresses who have ever held the token with their balances
    /// @return The holders and their balances
    /// @notice Some holders may have a zero balance
    function getHoldersWithBalance(
        uint256 offset,
        uint256 limit
    ) public view returns (address[] memory, uint256[] memory) {
        uint256 length = holders.length;
        if (offset >= length) {
            return (new address[](0), new uint256[](0));
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        address[] memory resultAddresses = new address[](end - offset);
        uint256[] memory resultBalances = new uint256[](end - offset);

        for (uint256 i = offset; i < end; i++) {
            address holder = holders[i];
            resultAddresses[i - offset] = holder;
            resultBalances[i - offset] = balanceOf[holder];
        }

        return (resultAddresses, resultBalances);
    }

    /// Get all addresses who have ever held the token
    /// @return The holders
    /// @notice Some holders may have a zero balance
    function getHolders(
        uint256 offset,
        uint256 limit
    ) public view returns (address[] memory) {
        uint256 length = holders.length;
        if (offset >= length) {
            return new address[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        address[] memory result = new address[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = holders[i];
        }

        return result;
    }

    /// Get the number of all addresses who have ever held the token
    /// @return The number of holders
    /// @notice Some holders may have a zero balance
    function getHoldersLength() public view returns (uint256) {
        return holders.length;
    }
}

File 2 of 11 : ERC20.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

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

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

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

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

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

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

File 3 of 11 : EmCore.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {EmToken} from "./EmToken.sol";
import {EmHeadmaster} from "./EmHeadmaster.sol";
import {EmLedger} from "./EmLedger.sol";
import {ExternalEntities} from "./ExternalEntities.sol";

error InsufficientOutput();
error InsufficientTokenReserve();
error InsufficientEthReserve();
error InsufficientMcap();
error TooMuchMcap();
error AlreadyGraduated();
error NotEmToken();
error DeadlineExceeded();
error InvalidAmountIn();
error Forbidden();
error FeeTooHigh();
error Paused();

/// @notice Owner can pause trading, set fees, and set the graduation strategy, but cannot withdraw funds or modify the bonding curve.
contract EmCore is ReentrancyGuard {
    using FixedPointMathLib for uint256;

    struct Pool {
        EmToken token;
        uint256 tokenReserve;
        uint256 virtualTokenReserve;
        uint256 ethReserve;
        uint256 virtualEthReserve;
        uint256 lastPrice;
        uint256 lastMcapInEth;
        uint256 lastTimestamp;
        uint256 lastBlock;
        address creator;
        address headmaster;
        // poolId is not limited to address to support non-uniswap styled AMMs
        uint256 poolId;
        // K is never updated
        uint256 K;
    }

    uint8 public constant DECIMALS = 18;
    uint256 public constant FEE_DENOMINATOR = 10000;
    uint256 public constant MAX_FEE = 1000; // 10%
    uint256 public feeRate_ = 100; // 1%

    uint256 public constant INIT_VIRTUAL_TOKEN_RESERVE = 1073000000 ether;
    uint256 public constant INIT_REAL_TOKEN_RESERVE = 793100000 ether;
    uint256 public constant TOTAL_SUPPLY = 1000000000 ether;
    uint256 public initVirtualEthReserve_;
    uint256 public graduationThreshold_;
    uint256 public K_;

    mapping(EmToken => Pool) public pools_;
    EmLedger public emLedger_;

    uint256 public creationFee_ = 0;
    uint256 public graduationFeeRate_ = 200;
    address public feeTo_;
    bool public paused_;
    EmHeadmaster public headmaster_; // the contract which implements the graduation logic

    ExternalEntities public externalEntities_;

    /*//////////////////////////////////////////////////
    /////////////   PERMISSIONED METHODS   /////////////
    //////////////////////////////////////////////////*/

    address public owner_;

    modifier onlyOwner() {
        if (msg.sender != owner_) revert Forbidden();
        _;
    }

    function setFeeTo(address feeTo) external onlyOwner {
        feeTo_ = feeTo;
    }

    function setFeeRate(uint256 feeRate) external onlyOwner {
        if (feeRate > MAX_FEE) revert FeeTooHigh();
        feeRate_ = feeRate;
    }

    function setGraduationFeeRate(uint256 feeRate) external onlyOwner {
        if (feeRate > MAX_FEE) revert FeeTooHigh();
        graduationFeeRate_ = feeRate;
    }

    function setEmLedger(EmLedger _ledger) external onlyOwner {
        emLedger_ = _ledger;
    }

    function setInitVirtualEthReserve(
        uint256 initVirtualEthReserve
    ) external onlyOwner {
        initVirtualEthReserve_ = initVirtualEthReserve;
        K_ = initVirtualEthReserve_ * INIT_VIRTUAL_TOKEN_RESERVE;
        graduationThreshold_ =
            K_ /
            (INIT_VIRTUAL_TOKEN_RESERVE - INIT_REAL_TOKEN_RESERVE) -
            initVirtualEthReserve_;
    }

    function setCreationFee(uint256 fee) external onlyOwner {
        creationFee_ = fee;
    }

    function setHeadmaster(EmHeadmaster headmaster) external onlyOwner {
        headmaster_ = headmaster;
    }

    function setExternalEntities(
        ExternalEntities externalEntities
    ) external onlyOwner {
        externalEntities_ = externalEntities;
    }

    function setOwner(address owner) external onlyOwner {
        owner_ = owner;
    }

    function setPaused(bool paused) external onlyOwner {
        paused_ = paused;
    }

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

    constructor(uint256 initVirtualEthReserve) {
        feeTo_ = msg.sender;
        owner_ = msg.sender;
        paused_ = false;

        emLedger_ = new EmLedger();
        initVirtualEthReserve_ = initVirtualEthReserve;
        K_ = initVirtualEthReserve_ * INIT_VIRTUAL_TOKEN_RESERVE;
        graduationThreshold_ =
            K_ /
            (INIT_VIRTUAL_TOKEN_RESERVE - INIT_REAL_TOKEN_RESERVE) -
            initVirtualEthReserve_;
    }

    /*//////////////////////////////////////////////////
    //////////////////   ASSERTIONS   //////////////////
    //////////////////////////////////////////////////*/

    modifier checkDeadline(uint256 deadline) {
        if (block.timestamp > deadline) revert DeadlineExceeded();
        _;
    }

    modifier onlyUnpaused() {
        if (paused_) revert Paused();
        _;
    }

    modifier onlyUngraduated(EmToken token) {
        if (pools_[token].headmaster != address(0)) revert AlreadyGraduated();
        if (pools_[token].ethReserve > graduationThreshold_) {
            revert TooMuchMcap();
        }
        _;
    }

    modifier onlyEmToken(EmToken token) {
        if (token == EmToken(address(0)) || pools_[token].token != token) {
            revert NotEmToken();
        }
        _;
    }

    function _isMcapGraduable(EmToken token) private view returns (bool) {
        return pools_[token].ethReserve >= graduationThreshold_;
    }

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

    event TokenCreated(EmToken indexed token, address indexed creator);
    event TokenGraduated(
        EmToken indexed token,
        EmHeadmaster indexed headmaster,
        uint256 indexed poolId,
        uint256 amountToken,
        uint256 amountETH
    );
    event Buy(
        EmToken indexed token,
        address indexed sender,
        uint256 amountIn,
        uint256 amountOut,
        address indexed to
    );
    event Sell(
        EmToken indexed token,
        address indexed sender,
        uint256 amountIn,
        uint256 amountOut,
        address indexed to
    );
    event PriceUpdate(
        EmToken indexed token,
        address indexed sender,
        uint256 price,
        uint256 mcapInEth
    );

    /*//////////////////////////////////////////////////
    ////////////////   POOL FUNCTIONS   ////////////////
    //////////////////////////////////////////////////*/

    /// @notice Creates a new token in the EmCore.
    /// @param name The name of the token.
    /// @param symbol The symbol of the token.
    /// @param initAmountIn The initial amount of ETH to swap for the token.
    /// @param description The description of the token.
    /// @param extended The extended description of the token, typically a JSON string.
    /// @return token The newly created token.
    /// @return amountOut The output amount of token the creator received.
    function createToken(
        string memory name,
        string memory symbol,
        uint256 initAmountIn,
        string memory description,
        string memory extended
    ) external payable onlyUnpaused returns (EmToken token, uint256 amountOut) {
        if (msg.value != initAmountIn + creationFee_) revert InvalidAmountIn();
        if (creationFee_ > 0) {
            SafeTransferLib.safeTransferETH(feeTo_, creationFee_);
        }

        token = _deployToken(name, symbol, description, extended);
        if (initAmountIn > 0) {
            amountOut = _swapEthForTokens(token, initAmountIn, 0, msg.sender);
        }
    }

    function createTokenAndBurnLiquidity(
        string memory name,
        string memory symbol,
        uint256 initAmountIn,
        string memory description,
        string memory extended
    ) external payable onlyUnpaused returns (EmToken token, uint256 amountOut) {
        if (msg.value != initAmountIn + creationFee_) revert InvalidAmountIn();
        if (creationFee_ > 0) {
            SafeTransferLib.safeTransferETH(feeTo_, creationFee_);
        }

        token = _deployToken(name, symbol, description, extended);
        if (initAmountIn > 0) {
            amountOut = _swapEthForTokens(
                token,
                initAmountIn,
                0,
                0x000000000000000000000000000000000000dEaD
            );
        }
    }

    function _deployToken(
        string memory name,
        string memory symbol,
        string memory description,
        string memory extended
    ) private returns (EmToken) {
        EmToken token = new EmToken(
            name,
            symbol,
            DECIMALS,
            TOTAL_SUPPLY,
            description,
            extended,
            address(this),
            msg.sender
        );

        Pool storage pool = pools_[token];
        pool.token = token;
        pool.tokenReserve = INIT_REAL_TOKEN_RESERVE;
        pool.virtualTokenReserve = INIT_VIRTUAL_TOKEN_RESERVE;
        pool.ethReserve = 0;
        pool.virtualEthReserve = initVirtualEthReserve_;
        pool.lastPrice = initVirtualEthReserve_.divWadDown(
            INIT_VIRTUAL_TOKEN_RESERVE
        );
        pool.lastMcapInEth = TOTAL_SUPPLY.mulWadUp(pool.lastPrice);
        pool.lastTimestamp = block.timestamp;
        pool.lastBlock = block.number;
        pool.creator = msg.sender;
        pool.K = K_;

        emit TokenCreated(token, msg.sender);
        emit PriceUpdate(token, msg.sender, pool.lastPrice, pool.lastMcapInEth);
        emLedger_.addCreation(token, msg.sender);

        return token;
    }

    function _graduate(EmToken token) private {
        pools_[token].lastTimestamp = block.timestamp;
        pools_[token].lastBlock = block.number;

        uint256 fee = (pools_[token].ethReserve * graduationFeeRate_) /
            FEE_DENOMINATOR;
        SafeTransferLib.safeTransferETH(feeTo_, fee);
        uint256 _amountETH = pools_[token].ethReserve - fee;
        uint256 _amountToken = TOTAL_SUPPLY - INIT_REAL_TOKEN_RESERVE;

        EmToken(address(token)).setIsUnrestricted(true);
        token.approve(address(headmaster_), type(uint256).max);
        (uint256 poolId, uint256 amountToken, uint256 amountETH) = headmaster_
            .execute{value: _amountETH}(token, _amountToken, _amountETH);

        pools_[token].headmaster = address(headmaster_);
        pools_[token].poolId = poolId;
        pools_[token].virtualTokenReserve = 0;
        pools_[token].virtualEthReserve = 0;
        pools_[token].tokenReserve = 0;
        pools_[token].ethReserve = 0;

        emit TokenGraduated(token, headmaster_, poolId, amountToken, amountETH);
        emLedger_.addGraduation(token, amountETH);
    }

    /*//////////////////////////////////////////////////
    ////////////////   SWAP FUNCTIONS   ////////////////
    //////////////////////////////////////////////////*/

    /// @notice Swaps ETH for tokens.
    /// @param token The token to swap.
    /// @param amountIn Input amount of ETH.
    /// @param amountOutMin Minimum output amount of token.
    /// @param to Recipient of token.
    /// @param deadline Deadline for the swap.
    /// @return amountOut The actual output amount of token.
    function swapEthForTokens(
        EmToken token,
        uint256 amountIn,
        uint256 amountOutMin,
        address to,
        uint256 deadline
    )
        external
        payable
        nonReentrant
        onlyUnpaused
        onlyUngraduated(token)
        onlyEmToken(token)
        checkDeadline(deadline)
        returns (uint256 amountOut)
    {
        if (msg.value != amountIn) revert InvalidAmountIn();

        amountOut = _swapEthForTokens(token, amountIn, amountOutMin, to);

        if (_isMcapGraduable(token)) {
            _graduate(token);
        }
    }

    function _swapEthForTokens(
        EmToken token,
        uint256 amountIn,
        uint256 amountOutMin,
        address to
    ) private returns (uint256 amountOut) {
        if (amountIn == 0) revert InvalidAmountIn();

        uint256 fee = (amountIn * feeRate_) / FEE_DENOMINATOR;
        SafeTransferLib.safeTransferETH(feeTo_, fee);
        amountIn -= fee;

        uint256 newVirtualEthReserve = pools_[token].virtualEthReserve +
            amountIn;
        uint256 newVirtualTokenReserve = pools_[token].K / newVirtualEthReserve;
        amountOut = pools_[token].virtualTokenReserve - newVirtualTokenReserve;

        if (amountOut > pools_[token].tokenReserve) {
            amountOut = pools_[token].tokenReserve;
        }
        if (amountOut < amountOutMin) revert InsufficientOutput();

        pools_[token].virtualTokenReserve = newVirtualTokenReserve;
        pools_[token].virtualEthReserve = newVirtualEthReserve;

        pools_[token].lastPrice = newVirtualEthReserve.divWadDown(
            newVirtualTokenReserve
        );
        pools_[token].lastMcapInEth = TOTAL_SUPPLY.mulWadUp(
            pools_[token].lastPrice
        );
        pools_[token].lastTimestamp = block.timestamp;
        pools_[token].lastBlock = block.number;

        pools_[token].ethReserve += amountIn;
        pools_[token].tokenReserve -= amountOut;

        SafeTransferLib.safeTransfer(token, to, amountOut);

        emit Buy(token, msg.sender, amountIn + fee, amountOut, to);
        emit PriceUpdate(
            token,
            msg.sender,
            pools_[token].lastPrice,
            pools_[token].lastMcapInEth
        );
        EmLedger.Trade memory trade = EmLedger.Trade(
            token,
            true,
            to,
            amountIn + fee,
            amountOut,
            uint128(block.timestamp),
            uint128(block.number)
        );
        emLedger_.addTrade(trade);
    }

    /// @notice Swaps tokens for ETH.
    /// @param token The token to swap.
    /// @param amountIn Input amount of token.
    /// @param amountOutMin Minimum output amount of ETH.
    /// @param to Recipient of ETH.
    /// @param deadline Deadline for the swap.
    /// @return amountOut The actual output amount of ETH.
    function swapTokensForEth(
        EmToken token,
        uint256 amountIn,
        uint256 amountOutMin,
        address to,
        uint256 deadline
    )
        external
        nonReentrant
        onlyUnpaused
        onlyUngraduated(token)
        onlyEmToken(token)
        checkDeadline(deadline)
        returns (uint256 amountOut)
    {
        if (amountIn == 0) revert InvalidAmountIn();

        SafeTransferLib.safeTransferFrom(
            token,
            msg.sender,
            address(this),
            amountIn
        );

        uint256 newVirtualTokenReserve = pools_[token].virtualTokenReserve +
            amountIn;
        uint256 newVirtualEthReserve = pools_[token].K / newVirtualTokenReserve;
        amountOut = pools_[token].virtualEthReserve - newVirtualEthReserve;

        pools_[token].virtualTokenReserve = newVirtualTokenReserve;
        pools_[token].virtualEthReserve = newVirtualEthReserve;

        pools_[token].lastPrice = newVirtualEthReserve.divWadDown(
            newVirtualTokenReserve
        );
        pools_[token].lastMcapInEth = TOTAL_SUPPLY.mulWadUp(
            pools_[token].lastPrice
        );
        pools_[token].lastTimestamp = block.timestamp;
        pools_[token].lastBlock = block.number;

        pools_[token].tokenReserve += amountIn;
        pools_[token].ethReserve -= amountOut;

        uint256 fee = (amountOut * feeRate_) / FEE_DENOMINATOR;
        amountOut -= fee;

        if (amountOut < amountOutMin) revert InsufficientOutput();
        SafeTransferLib.safeTransferETH(feeTo_, fee);
        SafeTransferLib.safeTransferETH(to, amountOut);

        emit Sell(token, msg.sender, amountIn, amountOut, to);
        emit PriceUpdate(
            token,
            msg.sender,
            pools_[token].lastPrice,
            pools_[token].lastMcapInEth
        );
        EmLedger.Trade memory trade = EmLedger.Trade(
            token,
            false,
            msg.sender,
            amountIn,
            amountOut + fee,
            uint128(block.timestamp),
            uint128(block.number)
        );
        emLedger_.addTrade(trade);
    }

    /*//////////////////////////////////////////////////
    ////////////////   VIEW FUNCTIONS   ////////////////
    //////////////////////////////////////////////////*/

    /// @notice Calculates the expected output amount of ETH given an input amount of token.
    /// @param token The token to swap.
    /// @param amountIn Input amount of token.
    /// @return amountOut The expected output amount of ETH.
    function calcAmountOutFromToken(
        EmToken token,
        uint256 amountIn
    ) external view returns (uint256 amountOut) {
        if (amountIn == 0) revert InvalidAmountIn();

        uint256 newVirtualTokenReserve = pools_[token].virtualTokenReserve +
            amountIn;
        uint256 newVirtualEthReserve = pools_[token].K / newVirtualTokenReserve;
        amountOut = pools_[token].virtualEthReserve - newVirtualEthReserve;

        uint256 fee = (amountOut * feeRate_) / FEE_DENOMINATOR;
        amountOut -= fee;
    }

    /// @notice Calculates the expected output amount of token given an input amount of ETH.
    /// @param token The token to swap.
    /// @param amountIn Input amount of ETH.
    /// @return amountOut The expected output amount of token.
    function calcAmountOutFromEth(
        EmToken token,
        uint256 amountIn
    ) external view returns (uint256 amountOut) {
        if (amountIn == 0) revert InvalidAmountIn();

        uint256 fee = (amountIn * feeRate_) / FEE_DENOMINATOR;
        amountIn -= fee;

        uint256 newVirtualEthReserve = pools_[token].virtualEthReserve +
            amountIn;
        uint256 newVirtualTokenReserve = pools_[token].K / newVirtualEthReserve;
        amountOut = pools_[token].virtualTokenReserve - newVirtualTokenReserve;

        if (amountOut > pools_[token].tokenReserve) {
            amountOut = pools_[token].tokenReserve;
        }
    }

    /*///////////////////////////////////////////
    //             Storage  Getters            //
    ///////////////////////////////////////////*/

    function getPool(EmToken token) external view returns (Pool memory) {
        return pools_[token];
    }

    function getPoolsAll(
        uint256 offset,
        uint256 limit
    ) external view returns (Pool[] memory) {
        EmToken[] memory tokens = emLedger_.getTokens(offset, limit);
        Pool[] memory pools = new Pool[](tokens.length);

        for (uint256 i = 0; i < tokens.length; i++) {
            pools[i] = pools_[tokens[i]];
        }

        return pools;
    }
}

File 4 of 11 : ReentrancyGuard.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

File 5 of 11 : FixedPointMathLib.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant MAX_UINT256 = 2**256 - 1;

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // Divide x * y by the denominator.
            z := div(mul(x, y), denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // If x * y modulo the denominator is strictly greater than 0,
            // 1 is added to round up the division of x * y by the denominator.
            z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let y := x // We start y at x, which will help us make our initial estimate.

            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // We check y >= 2^(k + 8) but shift right by k bits
            // each branch to ensure that if x >= 256, then y >= 256.
            if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                y := shr(128, y)
                z := shl(64, z)
            }
            if iszero(lt(y, 0x1000000000000000000)) {
                y := shr(64, y)
                z := shl(32, z)
            }
            if iszero(lt(y, 0x10000000000)) {
                y := shr(32, y)
                z := shl(16, z)
            }
            if iszero(lt(y, 0x1000000)) {
                y := shr(16, y)
                z := shl(8, z)
            }

            // Goal was to get z*z*y within a small factor of x. More iterations could
            // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
            // We ensured y >= 256 so that the relative difference between y and y+1 is small.
            // That's not possible if x < 256 but we can just verify those cases exhaustively.

            // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
            // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
            // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.

            // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
            // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.

            // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
            // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

            // There is no overflow risk here since y < 2^136 after the first branch above.
            z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If x+1 is a perfect square, the Babylonian method cycles between
            // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Mod x by y. Note this will return
            // 0 instead of reverting if y is zero.
            z := mod(x, y)
        }
    }

    function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // Divide x by y. Note this will return
            // 0 instead of reverting if y is zero.
            r := div(x, y)
        }
    }

    function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Add 1 to x * y if x % y > 0. Note this will
            // return 0 instead of reverting if y is zero.
            z := add(gt(mod(x, y), 0), div(x, y))
        }
    }
}

File 6 of 11 : SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

File 7 of 11 : EmHeadmaster.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";
import {IUniswapV2Factory} from "./interfaces/IUniswapV2Factory.sol";
import {EmToken} from "./EmToken.sol";
import {EmCore} from "./EmCore.sol";

error Forbidden();
error InvalidAmountToken();
error InvalidAmountEth();

/// @title A Em protocol graduation strategy for bootstrapping liquidity on uni-v2 AMMs.
/// @notice This contract may be replaced by other strategies in the future.
contract EmHeadmaster {
    EmCore public immutable emCore;
    IUniswapV2Router02 public immutable uniswapV2Router02;
    IUniswapV2Factory public immutable uniswapV2Factory;

    address public constant liquidityOwner = address(0);

    EmToken[] public alumni;

    constructor(EmCore _emCore, IUniswapV2Router02 _uniswapV2Router02) {
        emCore = _emCore;
        uniswapV2Router02 = IUniswapV2Router02(payable(_uniswapV2Router02));
        uniswapV2Factory = IUniswapV2Factory(uniswapV2Router02.factory());
    }

    modifier onlyEmCore() {
        if (msg.sender != address(emCore)) revert Forbidden();
        _;
    }

    event Executed(
        EmToken token,
        uint256 indexed poolId,
        uint256 amountToken,
        uint256 amountETH,
        address indexed owner
    );

    function execute(
        EmToken token,
        uint256 amountToken,
        uint256 amountEth
    )
        external
        payable
        onlyEmCore
        returns (uint256 poolId, uint256 _amountToken, uint256 _amountETH)
    {
        if (amountToken == 0) revert InvalidAmountToken();
        if (amountEth == 0 || msg.value != amountEth) revert InvalidAmountEth();

        SafeTransferLib.safeTransferFrom(
            token,
            msg.sender,
            address(this),
            amountToken
        );
        SafeTransferLib.safeApprove(
            token,
            address(uniswapV2Router02),
            amountToken
        );

        address pair = uniswapV2Factory.getPair(
            address(token),
            uniswapV2Router02.WETH()
        );
        if (pair == address(0)) {
            pair = uniswapV2Factory.createPair(
                address(token),
                uniswapV2Router02.WETH()
            );
        }
        poolId = uint256(uint160(pair));
        uint256 amountTokenMin = (amountToken * 95) / 100;
        uint256 amountEthMin = (amountEth * 95) / 100;
        (_amountToken, _amountETH, ) = uniswapV2Router02.addLiquidityETH{
            value: amountEth
        }(
            address(token),
            amountToken,
            amountTokenMin,
            amountEthMin,
            liquidityOwner,
            block.timestamp
        );

        alumni.push(token);

        emit Executed(token, poolId, _amountToken, _amountETH, liquidityOwner);
    }

    /*///////////////////////////////////////////
    //             Storage  Getters            //
    ///////////////////////////////////////////*/

    function getAlumni(
        uint256 offset,
        uint256 limit
    ) external view returns (EmToken[] memory) {
        uint256 length = alumni.length;
        if (offset >= length) {
            return new EmToken[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        EmToken[] memory result = new EmToken[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = alumni[i];
        }
        return result;
    }
}

File 8 of 11 : EmLedger.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {EmCore} from "./EmCore.sol";
import {EmToken} from "./EmToken.sol";

error NotFoundry();

/// @title The Em protocol user activity bookkeeper.
/// @notice Since this version of the protocol is not deployed on gas-expensive networks, this contract is designed to make data more available from onchain.
contract EmLedger {
    struct Stats {
        uint256 totalVolume;
        uint256 totalLiquidityBootstrapped;
        uint256 totalTokensCreated;
        uint256 totalTokensGraduated;
        uint256 totalTrades;
    }

    struct Trade {
        EmToken token;
        bool isBuy;
        address maker;
        uint256 amountIn;
        uint256 amountOut;
        uint128 timestamp;
        uint128 blockNumber;
    }

    uint256 public totalVolume;
    uint256 public totalLiquidityBootstrapped;

    mapping(address => EmToken[]) public tokensCreatedBy;
    mapping(address => EmToken[]) public tokensTradedBy;
    mapping(EmToken => mapping(address => bool)) public hasTraded;

    EmToken[] public tokensCreated;
    EmToken[] public tokensGraduated;
    mapping(EmToken => bool) public isGraduated;

    Trade[] public trades;
    mapping(EmToken => uint256[]) public tradesByToken;
    mapping(address => uint256[]) public tradesByUser;

    EmCore public immutable emCore;

    constructor() {
        emCore = EmCore(msg.sender);
    }

    modifier onlyFoundry() {
        if (msg.sender != address(emCore)) revert NotFoundry();
        _;
    }

    /// Add a token to the list of tokens created by a user
    /// @param token The token to add
    /// @param user The user to add the token for
    /// @notice This method should only be called once per token creation
    function addCreation(EmToken token, address user) public onlyFoundry {
        tokensCreatedBy[user].push(token);
        tokensCreated.push(token);
    }

    /// Add a trade to the ledger
    /// @param trade The trade to add
    function addTrade(Trade memory trade) public onlyFoundry {
        uint256 tradeId = trades.length;
        trades.push(trade);
        tradesByToken[trade.token].push(tradeId);
        tradesByUser[trade.maker].push(tradeId);
        totalVolume += trade.isBuy ? trade.amountIn : trade.amountOut;

        if (hasTraded[trade.token][trade.maker]) return;

        tokensTradedBy[trade.maker].push(trade.token);
        hasTraded[trade.token][trade.maker] = true;
    }

    /// Add a token to the list of graduated tokens
    /// @param token The token to add
    /// @notice This method should only be called once per token graduation
    function addGraduation(
        EmToken token,
        uint256 amountEth
    ) public onlyFoundry {
        tokensGraduated.push(token);
        isGraduated[token] = true;
        totalLiquidityBootstrapped += amountEth;
    }

    /*///////////////////////////////////////////
    //             Storage  Getters            //
    ///////////////////////////////////////////*/

    function getTokensCreatedBy(
        address user,
        uint256 offset,
        uint256 limit
    ) public view returns (EmToken[] memory) {
        EmToken[] storage allTokens = tokensCreatedBy[user];
        uint256 length = allTokens.length;
        if (offset >= length) {
            return new EmToken[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        EmToken[] memory result = new EmToken[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = allTokens[i];
        }

        return result;
    }

    function getTokensTradedBy(
        address user,
        uint256 offset,
        uint256 limit
    ) public view returns (EmToken[] memory) {
        EmToken[] storage allTokens = tokensTradedBy[user];
        uint256 length = allTokens.length;
        if (offset >= length) {
            return new EmToken[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        EmToken[] memory result = new EmToken[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = allTokens[i];
        }

        return result;
    }

    function getTokens(
        uint256 offset,
        uint256 limit
    ) public view returns (EmToken[] memory) {
        uint256 length = tokensCreated.length;
        if (offset >= length) {
            return new EmToken[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        EmToken[] memory result = new EmToken[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = tokensCreated[i];
        }

        return result;
    }

    function getToken(uint256 tokenId) public view returns (EmToken) {
        return tokensCreated[tokenId];
    }

    function getTokensLength() public view returns (uint256) {
        return tokensCreated.length;
    }

    function getTokensGraduated(
        uint256 offset,
        uint256 limit
    ) public view returns (EmToken[] memory) {
        uint256 length = tokensGraduated.length;
        if (offset >= length) {
            return new EmToken[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        EmToken[] memory result = new EmToken[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = tokensGraduated[i];
        }

        return result;
    }

    function getTokenGraduated(uint256 tokenId) public view returns (EmToken) {
        return tokensGraduated[tokenId];
    }

    function getTokensGraduatedLength() public view returns (uint256) {
        return tokensGraduated.length;
    }

    function getTradesAll(
        uint256 offset,
        uint256 limit
    ) public view returns (Trade[] memory) {
        uint256 length = trades.length;
        if (offset >= length) {
            return new Trade[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        Trade[] memory result = new Trade[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = trades[i];
        }

        return result;
    }

    function getTrade(uint256 tradeId) public view returns (Trade memory) {
        return trades[tradeId];
    }

    function getTradesLength() public view returns (uint256) {
        return trades.length;
    }

    function getTradesByTokenLength(
        EmToken token
    ) public view returns (uint256) {
        return tradesByToken[token].length;
    }

    function getTradeIdsByToken(
        EmToken token,
        uint256 offset,
        uint256 limit
    ) public view returns (uint256[] memory) {
        uint256 length = tradesByToken[token].length;
        if (offset >= length) {
            return new uint256[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        uint256[] memory result = new uint256[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = tradesByToken[token][i];
        }

        return result;
    }

    function getTradesByUserLength(address user) public view returns (uint256) {
        return tradesByUser[user].length;
    }

    function getTradeIdsByUser(
        address user,
        uint256 offset,
        uint256 limit
    ) public view returns (uint256[] memory) {
        uint256 length = tradesByUser[user].length;
        if (offset >= length) {
            return new uint256[](0);
        }

        uint256 end = offset + limit;
        if (end > length) {
            end = length;
        }

        uint256[] memory result = new uint256[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            result[i - offset] = tradesByUser[user][i];
        }

        return result;
    }

    function getStats() public view returns (Stats memory) {
        return
            Stats({
                totalVolume: totalVolume,
                totalLiquidityBootstrapped: totalLiquidityBootstrapped,
                totalTokensCreated: tokensCreated.length,
                totalTokensGraduated: tokensGraduated.length,
                totalTrades: trades.length
            });
    }
}

File 9 of 11 : ExternalEntities.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {EmCore} from "./EmCore.sol";
import {IUniswapV2Factory} from "./interfaces/IUniswapV2Factory.sol";

error Forbidden();

/// @title External entities registry. Primarily used to check and restrict pre-graduation token transfers to specific entities like Uniswap V2 pairs.
/// @notice Refer to the EmToken template contract to verify that the restriction is lifted after graduation.
contract ExternalEntities {
    address public immutable weth;

    IUniswapV2Factory[] public knownFactories;
    mapping(address => bool) public pregradRestricted;
    address public owner;

    constructor(address _weth) {
        owner = msg.sender;
        weth = _weth;
    }

    function setOwner(address _owner) external {
        if (msg.sender != owner) revert Forbidden();
        owner = _owner;
    }

    function addFactory(address factory) external {
        if (msg.sender != owner) revert Forbidden();

        knownFactories.push(IUniswapV2Factory(factory));
    }

    function removeFactory(address factory) external {
        if (msg.sender != owner) revert Forbidden();

        for (uint256 i = 0; i < knownFactories.length; i++) {
            if (address(knownFactories[i]) == factory) {
                knownFactories[i] = knownFactories[knownFactories.length - 1];
                knownFactories.pop();
                break;
            }
        }
    }

    function addPregradRestricted(address to) external {
        if (msg.sender != owner) revert Forbidden();

        pregradRestricted[to] = true;
    }

    function removePregradRestricted(address to) external {
        if (msg.sender != owner) revert Forbidden();

        pregradRestricted[to] = false;
    }

    function computeUniV2Pair(
        IUniswapV2Factory factory,
        address tokenA,
        address tokenB
    ) public view returns (address pair, bool exists) {
        pair = factory.getPair(tokenA, tokenB);
        if (pair != address(0)) {
            return (pair, true);
        }

        (address token0, address token1) = tokenA < tokenB
            ? (tokenA, tokenB)
            : (tokenB, tokenA);

        // both uniswap and quickswap v2 are using the same init code hash
        pair = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
                            keccak256(abi.encodePacked(token0, token1)),
                            hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f"
                        )
                    )
                )
            )
        );

        return (pair, false);
    }

    function isPregradRestricted(
        address token,
        address to
    ) external view returns (bool) {
        for (uint256 i = 0; i < knownFactories.length; i++) {
            (address pair, ) = computeUniV2Pair(knownFactories[i], token, weth);
            if (pair == to) {
                return true;
            }
        }

        return pregradRestricted[to];
    }
}

File 10 of 11 : IUniswapV2Router02.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

interface IUniswapV2Router02 {
    function WETH() external view returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    )
        external
        payable
        returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);

    function factory() external view returns (address);

    function getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountIn);

    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountOut);

    function getAmountsIn(
        uint256 amountOut,
        address[] memory path
    ) external view returns (uint256[] memory amounts);

    function getAmountsOut(
        uint256 amountIn,
        address[] memory path
    ) external view returns (uint256[] memory amounts);

    function quote(
        uint256 amountA,
        uint256 reserveA,
        uint256 reserveB
    ) external pure returns (uint256 amountB);

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityETH(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountToken, uint256 amountETH);

    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountETH);

    function removeLiquidityETHWithPermit(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountToken, uint256 amountETH);

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountETH);

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountA, uint256 amountB);

    function swapETHForExactTokens(
        uint256 amountOut,
        address[] memory path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) external payable;

    function swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) external;

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) external;

    function swapTokensForExactETH(
        uint256 amountOut,
        uint256 amountInMax,
        address[] memory path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] memory path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    receive() external payable;
}

File 11 of 11 : IUniswapV2Factory.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

interface IUniswapV2Factory {
    event PairCreated(
        address indexed token0,
        address indexed token1,
        address pair,
        uint256
    );

    function allPairs(uint256) external view returns (address);
    function allPairsLength() external view returns (uint256);
    function createPair(
        address tokenA,
        address tokenB
    ) external returns (address pair);
    function feeTo() external view returns (address);
    function feeToSetter() external view returns (address);
    function getPair(address, address) external view returns (address);
    function setFeeTo(address _feeTo) external;
    function setFeeToSetter(address _feeToSetter) external;
}

Settings
{
  "remappings": [
    "forge-std/=lib/forge-std/src/",
    "lib/solmate/=lib/solmate/",
    "src/WenLedger.sol/=src/WenLedger.sol/",
    "src/ExternalEntities.sol/=src/ExternalEntities.sol/",
    "src/WenToken.sol/=src/WenToken.sol/",
    "src/WenFoundry.sol/=src/WenFoundry.sol/",
    "src/WenHeadmaster.sol/=src/WenHeadmaster.sol/",
    "src/interfaces/=src/interfaces/",
    "ds-test/=lib/solmate/lib/ds-test/src/",
    "solmate/=lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 1000000
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "none",
    "appendCBOR": false
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "paris",
  "viaIR": true,
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"},{"internalType":"uint256","name":"_supply","type":"uint256"},{"internalType":"string","name":"_description","type":"string"},{"internalType":"string","name":"_extended","type":"string"},{"internalType":"address","name":"_emCore","type":"address"},{"internalType":"address","name":"_creator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Forbidden","type":"error"},{"inputs":[],"name":"NotEmCore","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"creator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"description","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emCore","outputs":[{"internalType":"contract EmCore","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"extended","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"},{"internalType":"uint256","name":"limit","type":"uint256"}],"name":"getHolders","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getHoldersLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"},{"internalType":"uint256","name":"limit","type":"uint256"}],"name":"getHoldersWithBalance","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMetadata","outputs":[{"components":[{"internalType":"contract EmToken","name":"token","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"string","name":"extended","type":"string"},{"internalType":"address","name":"creator","type":"address"},{"internalType":"bool","name":"isGraduated","type":"bool"},{"internalType":"uint256","name":"mcap","type":"uint256"}],"internalType":"struct EmToken.Metadata","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"holders","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isGraduated","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isHolder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isUnrestricted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_isUnrestricted","type":"bool"}],"name":"setIsUnrestricted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","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":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]

6101206040818152346107c757612a43803803809161001e82866107cc565b843982016101009283818303126107c75780516001600160401b0391908281116107c7578361004e9183016107ef565b91602090818301518181116107c757856100699185016107ef565b93868401519360ff851685036107c75760608101519260808201518181116107c757886100979184016107ef565b9760a0830151908282116107c7576100b09184016107ef565b926100c960e06100c260c0860161085e565b940161085e565b938151978389116107b157600098806100e28b54610872565b94601f95868111610765575b508a90868311600114610702578c926106f7575b50508160011b916000199060031b1c19161789555b89518481116105b8578060019b61012e8d54610872565b8681116106a9575b508a90868311600114610640578c92610635575b5050600019600383901b1c1916908b1b178a555b6080524660a052888b5189818b549161017683610872565b928383528c808401968216918260001461061a5750506001146105e0575b6101a0925003826107cc565b5190208b51888101917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f83528d8201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a0815260c08101818110868211176105cc578d5251902060c05260ff19998a600a5416600a558051908482116105b8578190610240600654610872565b858111610569575b5089908583116001146104ff578b926104f4575b5050600019600383901b1c1916908a1b176006555b80519283116104e057908291610288600754610872565b82811161048f575b508791831160011461042e578892610423575b5050600019600383901b1c191690871b176007555b6001600160a01b031660e052875260025481810190811061040f57600255338352600382528583208181540190558551908152827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef833393a33382526009815260ff858320541615610390575b85855161219691826108ad833960805182610fc4015260a05182611a53015260c05182611a7a015260e0518281816106c2015281816109bf01528181610c08015281816110f501528181611225015281816113c40152611fb1015251818181610d0301526115d10152f35b600854680100000000000000008110156103fb57838101806008558110156103e757600883528183200180546001600160a01b03191633908117909155825260099052839020805490921617905538808080610325565b634e487b7160e01b83526032600452602483fd5b634e487b7160e01b83526041600452602483fd5b634e487b7160e01b84526011600452602484fd5b0151905038806102a3565b600789528789208a94509190601f1984168a5b8a8282106104795750508411610460575b505050811b016007556102b8565b015160001960f88460031b161c19169055388080610452565b8385015186558d97909501949384019301610441565b90919250600789528789208380860160051c8201928a87106104d7575b9186958d929594930160051c01915b8281106104c9575050610290565b8b81558695508c91016104bb565b925081926104ac565b634e487b7160e01b88526041600452602488fd5b01519050388061025c565b60068c528a8c208d94509190601f1984168d5b8d82821061054a5750508411610531575b505050811b01600655610271565b015160001960f88460031b161c19169055388080610523565b91929395968291958786015181550195019301908e9594939291610512565b90915060068b52898b208580850160051c8201928c86106105af575b918e91869594930160051c01915b8281106105a1575050610248565b8d81558594508e9101610593565b92508192610585565b634e487b7160e01b8a52604160045260248afd5b634e487b7160e01b8b52604160045260248bfd5b508a8c8e8180528282205b8583106106015750506101a09350820101610194565b8091929450548385880101520191018b908e85936105eb565b60ff191687526101a094151560051b84010191506101949050565b01519050388061014a565b8d8d528b8d208e94509190601f1984168e8e5b8282106106895750508411610670575b505050811b018a5561015e565b015160001960f88460031b161c19169055388080610663565b91929395968291958786015181550195019301908f95949392918e610653565b9091508c8c528a8c208680850160051c8201928d86106106ee575b918f91869594930160051c01915b8281106106e0575050610136565b8e81558594508f91016106d2565b925081926106c4565b015190503880610102565b8c80528b8d209250601f1984168d5b8d82821061074f575050908460019594939210610736575b505050811b018955610117565b015160001960f88460031b161c19169055388080610729565b6001859682939686015181550195019301610711565b9091508b80528a8c208680850160051c8201928d86106107a8575b9085949392910160051c01905b81811061079a57506100ee565b8d815584935060010161078d565b92508192610780565b634e487b7160e01b600052604160045260246000fd5b600080fd5b601f909101601f19168101906001600160401b038211908210176107b157604052565b919080601f840112156107c75782516001600160401b0381116107b1576020906040519261082683601f19601f85011601856107cc565b8184528282870101116107c75760005b81811061084b57508260009394955001015290565b8581018301518482018401528201610836565b51906001600160a01b03821682036107c757565b90600182811c921680156108a2575b602083101461088c57565b634e487b7160e01b600052602260045260246000fd5b91607f169161088156fe6080604081815260048036101561001557600080fd5b60009260e08435811c91826302d05d3f146115875750816306fdde03146114b8578163095ea7b3146113e85781630c4efc8e1461137857816318160ddd1461133a57816323b872dd146110555781632a11ced014610fe8578163313ce56714610f8b5781633644e51514610f485781636f3921ee14610f0b57816370a0823114610ea85781637284e41614610e585781637a5b4f5914610b545781637ecebe0014610af15781638911e26b14610aae5781638e84697214610a4157816395c996411461096a57816395d89b41146108435781639e5f2602146107fe578163a9059cbb146105b9578163b569807114610584578163d4d7b19a1461051c578163d505accf146101ef57508063db04aef4146101ae5763dd62ed3e1461013857600080fd5b346101aa57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa5760209282916101736118db565b61017b611903565b9173ffffffffffffffffffffffffffffffffffffffff8092168452865283832091168252845220549051908152f35b8280fd5b5050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020906008549051908152f35b5080fd5b84915083346101aa57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa576102296118db565b610231611903565b9160443590606435926084359460ff86168096036105185760ff600a5416156104f05742851061049357610263611a4e565b9473ffffffffffffffffffffffffffffffffffffffff80931696878a5260209660058852858b20998a549a60018c019055865193868a8601967f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c988528c8a880152169b8c606087015289608087015260a086015260c085015260c08452830167ffffffffffffffff948482108683111761046657818852845190206101008501927f19010000000000000000000000000000000000000000000000000000000000008452610102860152610122850152604281526101608401948186109086111761043a57848752519020835261018082015260a4356101a082015260c4356101c0909101528780528490889060809060015afa15610430578651169687151580610427575b156103cc5786977f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259596975283528087208688528352818188205551908152a380f35b8360649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b50848814610389565b81513d88823e3d90fd5b60248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5060248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b60648960208551917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b8883517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b8780fd5b505050346101eb5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760ff8160209373ffffffffffffffffffffffffffffffffffffffff6105716118db565b1681526009855220541690519015158152f35b505050346101eb576105b5906105a261059c3661198c565b90612019565b90519182916020835260208301906119c0565b0390f35b505091346107fb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb576105f26118db565b926024359060ff600a54161561067e575b508273ffffffffffffffffffffffffffffffffffffffff856106266020976120bd565b3385526003875282852061063b858254611e53565b90551692838152600386522081815401905582519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef843392a35160018152f35b73ffffffffffffffffffffffffffffffffffffffff84517f54028f7c00000000000000000000000000000000000000000000000000000000815260209182828581847f0000000000000000000000000000000000000000000000000000000000000000165afa9182156107f15790839187936107c2575b5087517feb56a3bd0000000000000000000000000000000000000000000000000000000081523086820190815273ffffffffffffffffffffffffffffffffffffffff8b1660208201529093849291839003604001918391165afa9182156107b857859261078b575b5050156106035783517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b6107aa9250803d106107b1575b6107a281836116b0565b810190611a36565b388061075d565b503d610798565b86513d87823e3d90fd5b6107e3919350823d84116107ea575b6107db81836116b0565b810190611a0a565b91386106f5565b503d6107d1565b87513d88823e3d90fd5b80fd5b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760209061083a611f64565b90519015158152f35b8385346107fb57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb57815191828260019360015494610888866115f5565b91828552602096876001821691826000146109255750506001146108c9575b5050506105b592916108ba9103856116b0565b51928284938452830190611898565b9190869350600183527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b82841061090d57505050820101816108ba6105b56108a7565b8054848a0186015288955087949093019281016108f4565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168782015293151560051b860190930193508492506108ba91506105b590506108a7565b5050346101aa5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa57803591821515809303610a3d5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163303610a1757505060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00600a5416911617600a5580f35b517f301d76fc000000000000000000000000000000000000000000000000000000008152fd5b8380fd5b8385346107fb5790610a5b610a553661198c565b90611e74565b9092610a7083519484869586528501906119c0565b60208482036020860152602080855193848152019401925b828110610a9757505050500390f35b835185528695509381019392810192600101610a88565b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760209060ff600a541690519015158152f35b505050346101eb5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb578060209273ffffffffffffffffffffffffffffffffffffffff610b446118db565b1681526005845220549051908152f35b848385346101aa57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa578051610b9081611648565b8381528385606092836020820152838582015283808201528360808201528260a08201528260c0820152015273ffffffffffffffffffffffffffffffffffffffff9482517fbbe4f6db00000000000000000000000000000000000000000000000000000000815230858201526101a09081816024818b7f0000000000000000000000000000000000000000000000000000000000000000165afa918215610e21578792610e2b575b50508351947f06fdde0300000000000000000000000000000000000000000000000000000000865286868281305afa958615610e21578796610e05575b5086855180927f95d89b4100000000000000000000000000000000000000000000000000000000825281305afa968715610dfa578097610dd5575b505086610cbb611f64565b9160c0015193855197610ccd89611648565b30895260208901978852868901908152610ce56117d6565b828a01908152610cf36116f1565b9160808b0192835260a08b0199857f0000000000000000000000000000000000000000000000000000000000000000168b5260c08c019615158752878c0198895289519c8d9c60208e52511660208d015251610100809a8d01526101208c01610d5b91611898565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe094858d830301908d0152610d9291611898565b905190838b82030160808c0152610da891611898565b9051918982030160a08a0152610dbd91611898565b95511660c08701525115159085015251908301520390f35b610df29297503d8091833e610dea81836116b0565b810190611d26565b948780610cb0565b8551903d90823e3d90fd5b610e1a9196503d8089833e610dea81836116b0565b9488610c75565b85513d89823e3d90fd5b610e4a9250803d10610e51575b610e4281836116b0565b810190611c4d565b8780610c38565b503d610e38565b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576105b590610e956117d6565b9051918291602083526020830190611898565b505050346101eb5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb578060209273ffffffffffffffffffffffffffffffffffffffff610efb6118db565b1681526003845220549051908152f35b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576105b590610e956116f1565b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb57602090610f84611a4e565b9051908152f35b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020905160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b5050346101aa5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa5735916008548310156107fb575073ffffffffffffffffffffffffffffffffffffffff611046602093611926565b92905490519260031b1c168152f35b505091346107fb5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb5761108f6118db565b611097611903565b9360443560ff600a5416156111e0575b907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef918573ffffffffffffffffffffffffffffffffffffffff80951694858752602098848a958652838920837f00000000000000000000000000000000000000000000000000000000000000001690818b5287527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9081868c2054036111c7575b50611152836120bd565b888a52818752848a20338b52875285858b20549182036111a4575b50505086885260038552828820611185858254611e53565b9055169586815260038452208181540190558551908152a35160018152f35b6111ad91611e53565b90888a528652838920338a5286528389205538808561116d565b898b52828852858b20908b52875280858b205538611148565b73ffffffffffffffffffffffffffffffffffffffff8551907f54028f7c00000000000000000000000000000000000000000000000000000000825260209182818681857f0000000000000000000000000000000000000000000000000000000000000000165afa9081156113305783916112b4918991611313575b5089517feb56a3bd0000000000000000000000000000000000000000000000000000000081523088820190815273ffffffffffffffffffffffffffffffffffffffff8d1660208201529094859384928391604090910190565b0392165afa9182156107f15786926112f6575b5050156110a7575083517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b61130c9250803d106107b1576107a281836116b0565b38806112c7565b61132a9150833d85116107ea576107db81836116b0565b3861125b565b88513d89823e3d90fd5b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020906002549051908152f35b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b505091346107fb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb576114216118db565b906024359060ff600a54161561148f57838291602096338252875273ffffffffffffffffffffffffffffffffffffffff8282209516948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b505050517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b8385346107fb57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb57815191828283546114f8816115f5565b908184526020956001918760018216918260001461092557505060011461152c575050506105b592916108ba9103856116b0565b91908693508280527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82841061156f57505050820101816108ba6105b56108a7565b8054848a018601528895508794909301928101611556565b8590346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760209073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b90600182811c9216801561163e575b602083101461160f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611604565b610100810190811067ffffffffffffffff82111761166557604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6020810190811067ffffffffffffffff82111761166557604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761166557604052565b6040519060008260075491611705836115f5565b808352926020906001908181169081156117935750600114611732575b5050611730925003836116b0565b565b91509260076000527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688936000925b82841061177b57506117309450505081016020013880611722565b85548885018301529485019487945092810192611760565b9050602093506117309592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201013880611722565b60405190600082600654916117ea836115f5565b808352926020906001908181169081156117935750600114611814575050611730925003836116b0565b91509260066000527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f936000925b82841061185d57506117309450505081016020013880611722565b85548885018301529485019487945092810192611842565b60005b8381106118885750506000910152565b8181015183820152602001611878565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6020936118d481518092818752878088019101611875565b0116010190565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036118fe57565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff821682036118fe57565b60085481101561195d5760086000527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60409101126118fe576004359060243590565b90815180825260208080930193019160005b8281106119e0575050505090565b835173ffffffffffffffffffffffffffffffffffffffff16855293810193928101926001016119d2565b908160209103126118fe575173ffffffffffffffffffffffffffffffffffffffff811681036118fe5790565b908160209103126118fe575180151581036118fe5790565b6000467f000000000000000000000000000000000000000000000000000000000000000003611a9c57507f000000000000000000000000000000000000000000000000000000000000000090565b60405181548291611aac826115f5565b808252816020948582019460019087600182169182600014611bf0575050600114611b97575b50611adf925003826116b0565b51902091604051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f845260408301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c082019082821067ffffffffffffffff831117611b6a575060405251902090565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526041600452fd5b87805286915087907f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b858310611bd8575050611adf935082010138611ad2565b80548388018501528694508893909201918101611bc1565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168852611adf95151560051b8501019250389150611ad29050565b519073ffffffffffffffffffffffffffffffffffffffff821682036118fe57565b80916101a092839103126118fe5760405191820182811067ffffffffffffffff82111761166557604052805173ffffffffffffffffffffffffffffffffffffffff811681036118fe5782526020810151602083015260408101516040830152606081015160608301526080810151608083015260a081015160a083015260c081015160c083015260e081015160e08301526101008082015190830152610120611cf7818301611c2c565b90830152610140611d09818301611c2c565b908301526101608082015190830152610180809101519082015290565b6020818303126118fe57805167ffffffffffffffff918282116118fe57019082601f830112156118fe5781519081116116655760405192611d8f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f85011601856116b0565b818452602082840101116118fe57611dad9160208085019101611875565b90565b67ffffffffffffffff81116116655760051b60200190565b90611dd282611db0565b611ddf60405191826116b0565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611e0d8294611db0565b0190602036910137565b91908201809211611e2457565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b91908203918211611e2457565b805182101561195d5760209160051b010190565b91906008549081841015611f3657611e8c9084611e17565b90808211611f2e575b50611ea8611ea38483611e53565b611dc8565b90611eb6611ea38583611e53565b93805b828110611ec7575050509190565b80604073ffffffffffffffffffffffffffffffffffffffff611eea600194611926565b919054600392831b1c169081611f09611f038887611e53565b8a611e60565b5260009182526020522054611f27611f218584611e53565b89611e60565b5201611eb9565b905038611e95565b50506040519150611f4682611694565b6000825260405191611f5783611694565b6000835260003681379190565b73ffffffffffffffffffffffffffffffffffffffff6040517fbbe4f6db0000000000000000000000000000000000000000000000000000000081523060048201526101a0908181602481867f0000000000000000000000000000000000000000000000000000000000000000165afa90811561200d5761014092600092611ff0575b5050015116151590565b6120069250803d10610e5157610e4281836116b0565b3880611fe6565b6040513d6000823e3d90fd5b9060085490818310156120a2576120309083611e17565b9080821161209a575b50612047611ea38383611e53565b91805b8281106120575750505090565b8073ffffffffffffffffffffffffffffffffffffffff612078600193611926565b90549060031b1c1661209361208d8584611e53565b87611e60565b520161204a565b905038612039565b5050506040516120b181611694565b60008152600036813790565b9073ffffffffffffffffffffffffffffffffffffffff809216600090808252600960205260ff604083205416156120f5575b50509050565b60085493680100000000000000008510156121695761211d8560016040969701600855611926565b819291549060031b9184831b921b1916179055815260096020522060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790558038806120ef565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000033b2e3c9fd0803ce8000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002600000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c0000000000000000000000008b27468c8d24e285b6b2ea250bb0b6d4d4f433cd000000000000000000000000000000000000000000000000000000000000000645746843617400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004454361740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ad496e74726f647563696e6720457468436174e280932074686520707572726665637420746f6b656e206c61756e6368696e67206f6e204574684d656d652e46756e21200a0a5768657468657220796f75277265206120646567656e206f72206a757374206c6f766520636174732c207468697320746f6b656e20697320666f722074686f73652077686f2077616e7420746f207269646520746865206d656f772d74617374696320776176652e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Deployed Bytecode

0x6080604081815260048036101561001557600080fd5b60009260e08435811c91826302d05d3f146115875750816306fdde03146114b8578163095ea7b3146113e85781630c4efc8e1461137857816318160ddd1461133a57816323b872dd146110555781632a11ced014610fe8578163313ce56714610f8b5781633644e51514610f485781636f3921ee14610f0b57816370a0823114610ea85781637284e41614610e585781637a5b4f5914610b545781637ecebe0014610af15781638911e26b14610aae5781638e84697214610a4157816395c996411461096a57816395d89b41146108435781639e5f2602146107fe578163a9059cbb146105b9578163b569807114610584578163d4d7b19a1461051c578163d505accf146101ef57508063db04aef4146101ae5763dd62ed3e1461013857600080fd5b346101aa57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa5760209282916101736118db565b61017b611903565b9173ffffffffffffffffffffffffffffffffffffffff8092168452865283832091168252845220549051908152f35b8280fd5b5050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020906008549051908152f35b5080fd5b84915083346101aa57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa576102296118db565b610231611903565b9160443590606435926084359460ff86168096036105185760ff600a5416156104f05742851061049357610263611a4e565b9473ffffffffffffffffffffffffffffffffffffffff80931696878a5260209660058852858b20998a549a60018c019055865193868a8601967f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c988528c8a880152169b8c606087015289608087015260a086015260c085015260c08452830167ffffffffffffffff948482108683111761046657818852845190206101008501927f19010000000000000000000000000000000000000000000000000000000000008452610102860152610122850152604281526101608401948186109086111761043a57848752519020835261018082015260a4356101a082015260c4356101c0909101528780528490889060809060015afa15610430578651169687151580610427575b156103cc5786977f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259596975283528087208688528352818188205551908152a380f35b8360649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b50848814610389565b81513d88823e3d90fd5b60248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5060248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b60648960208551917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b8883517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b8780fd5b505050346101eb5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760ff8160209373ffffffffffffffffffffffffffffffffffffffff6105716118db565b1681526009855220541690519015158152f35b505050346101eb576105b5906105a261059c3661198c565b90612019565b90519182916020835260208301906119c0565b0390f35b505091346107fb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb576105f26118db565b926024359060ff600a54161561067e575b508273ffffffffffffffffffffffffffffffffffffffff856106266020976120bd565b3385526003875282852061063b858254611e53565b90551692838152600386522081815401905582519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef843392a35160018152f35b73ffffffffffffffffffffffffffffffffffffffff84517f54028f7c00000000000000000000000000000000000000000000000000000000815260209182828581847f0000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c165afa9182156107f15790839187936107c2575b5087517feb56a3bd0000000000000000000000000000000000000000000000000000000081523086820190815273ffffffffffffffffffffffffffffffffffffffff8b1660208201529093849291839003604001918391165afa9182156107b857859261078b575b5050156106035783517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b6107aa9250803d106107b1575b6107a281836116b0565b810190611a36565b388061075d565b503d610798565b86513d87823e3d90fd5b6107e3919350823d84116107ea575b6107db81836116b0565b810190611a0a565b91386106f5565b503d6107d1565b87513d88823e3d90fd5b80fd5b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760209061083a611f64565b90519015158152f35b8385346107fb57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb57815191828260019360015494610888866115f5565b91828552602096876001821691826000146109255750506001146108c9575b5050506105b592916108ba9103856116b0565b51928284938452830190611898565b9190869350600183527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b82841061090d57505050820101816108ba6105b56108a7565b8054848a0186015288955087949093019281016108f4565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168782015293151560051b860190930193508492506108ba91506105b590506108a7565b5050346101aa5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa57803591821515809303610a3d5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c163303610a1757505060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00600a5416911617600a5580f35b517f301d76fc000000000000000000000000000000000000000000000000000000008152fd5b8380fd5b8385346107fb5790610a5b610a553661198c565b90611e74565b9092610a7083519484869586528501906119c0565b60208482036020860152602080855193848152019401925b828110610a9757505050500390f35b835185528695509381019392810192600101610a88565b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760209060ff600a541690519015158152f35b505050346101eb5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb578060209273ffffffffffffffffffffffffffffffffffffffff610b446118db565b1681526005845220549051908152f35b848385346101aa57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa578051610b9081611648565b8381528385606092836020820152838582015283808201528360808201528260a08201528260c0820152015273ffffffffffffffffffffffffffffffffffffffff9482517fbbe4f6db00000000000000000000000000000000000000000000000000000000815230858201526101a09081816024818b7f0000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c165afa918215610e21578792610e2b575b50508351947f06fdde0300000000000000000000000000000000000000000000000000000000865286868281305afa958615610e21578796610e05575b5086855180927f95d89b4100000000000000000000000000000000000000000000000000000000825281305afa968715610dfa578097610dd5575b505086610cbb611f64565b9160c0015193855197610ccd89611648565b30895260208901978852868901908152610ce56117d6565b828a01908152610cf36116f1565b9160808b0192835260a08b0199857f0000000000000000000000008b27468c8d24e285b6b2ea250bb0b6d4d4f433cd168b5260c08c019615158752878c0198895289519c8d9c60208e52511660208d015251610100809a8d01526101208c01610d5b91611898565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe094858d830301908d0152610d9291611898565b905190838b82030160808c0152610da891611898565b9051918982030160a08a0152610dbd91611898565b95511660c08701525115159085015251908301520390f35b610df29297503d8091833e610dea81836116b0565b810190611d26565b948780610cb0565b8551903d90823e3d90fd5b610e1a9196503d8089833e610dea81836116b0565b9488610c75565b85513d89823e3d90fd5b610e4a9250803d10610e51575b610e4281836116b0565b810190611c4d565b8780610c38565b503d610e38565b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576105b590610e956117d6565b9051918291602083526020830190611898565b505050346101eb5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb578060209273ffffffffffffffffffffffffffffffffffffffff610efb6118db565b1681526003845220549051908152f35b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576105b590610e956116f1565b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb57602090610f84611a4e565b9051908152f35b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020905160ff7f0000000000000000000000000000000000000000000000000000000000000012168152f35b5050346101aa5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101aa5735916008548310156107fb575073ffffffffffffffffffffffffffffffffffffffff611046602093611926565b92905490519260031b1c168152f35b505091346107fb5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb5761108f6118db565b611097611903565b9360443560ff600a5416156111e0575b907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef918573ffffffffffffffffffffffffffffffffffffffff80951694858752602098848a958652838920837f0000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c1690818b5287527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9081868c2054036111c7575b50611152836120bd565b888a52818752848a20338b52875285858b20549182036111a4575b50505086885260038552828820611185858254611e53565b9055169586815260038452208181540190558551908152a35160018152f35b6111ad91611e53565b90888a528652838920338a5286528389205538808561116d565b898b52828852858b20908b52875280858b205538611148565b73ffffffffffffffffffffffffffffffffffffffff8551907f54028f7c00000000000000000000000000000000000000000000000000000000825260209182818681857f0000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c165afa9081156113305783916112b4918991611313575b5089517feb56a3bd0000000000000000000000000000000000000000000000000000000081523088820190815273ffffffffffffffffffffffffffffffffffffffff8d1660208201529094859384928391604090910190565b0392165afa9182156107f15786926112f6575b5050156110a7575083517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b61130c9250803d106107b1576107a281836116b0565b38806112c7565b61132a9150833d85116107ea576107db81836116b0565b3861125b565b88513d89823e3d90fd5b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020906002549051908152f35b505050346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c168152f35b505091346107fb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb576114216118db565b906024359060ff600a54161561148f57838291602096338252875273ffffffffffffffffffffffffffffffffffffffff8282209516948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b505050517fee90c468000000000000000000000000000000000000000000000000000000008152fd5b8385346107fb57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fb57815191828283546114f8816115f5565b908184526020956001918760018216918260001461092557505060011461152c575050506105b592916108ba9103856116b0565b91908693508280527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82841061156f57505050820101816108ba6105b56108a7565b8054848a018601528895508794909301928101611556565b8590346101eb57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101eb5760209073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000008b27468c8d24e285b6b2ea250bb0b6d4d4f433cd168152f35b90600182811c9216801561163e575b602083101461160f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611604565b610100810190811067ffffffffffffffff82111761166557604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6020810190811067ffffffffffffffff82111761166557604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761166557604052565b6040519060008260075491611705836115f5565b808352926020906001908181169081156117935750600114611732575b5050611730925003836116b0565b565b91509260076000527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688936000925b82841061177b57506117309450505081016020013880611722565b85548885018301529485019487945092810192611760565b9050602093506117309592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201013880611722565b60405190600082600654916117ea836115f5565b808352926020906001908181169081156117935750600114611814575050611730925003836116b0565b91509260066000527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f936000925b82841061185d57506117309450505081016020013880611722565b85548885018301529485019487945092810192611842565b60005b8381106118885750506000910152565b8181015183820152602001611878565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6020936118d481518092818752878088019101611875565b0116010190565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036118fe57565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff821682036118fe57565b60085481101561195d5760086000527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60409101126118fe576004359060243590565b90815180825260208080930193019160005b8281106119e0575050505090565b835173ffffffffffffffffffffffffffffffffffffffff16855293810193928101926001016119d2565b908160209103126118fe575173ffffffffffffffffffffffffffffffffffffffff811681036118fe5790565b908160209103126118fe575180151581036118fe5790565b6000467f000000000000000000000000000000000000000000000000000000000000000103611a9c57507fc3b7cd4e7e3c99bdd2fa008066875a130e38a236f877b37aa11ef1a910e7c98a90565b60405181548291611aac826115f5565b808252816020948582019460019087600182169182600014611bf0575050600114611b97575b50611adf925003826116b0565b51902091604051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f845260408301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c082019082821067ffffffffffffffff831117611b6a575060405251902090565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526041600452fd5b87805286915087907f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b858310611bd8575050611adf935082010138611ad2565b80548388018501528694508893909201918101611bc1565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168852611adf95151560051b8501019250389150611ad29050565b519073ffffffffffffffffffffffffffffffffffffffff821682036118fe57565b80916101a092839103126118fe5760405191820182811067ffffffffffffffff82111761166557604052805173ffffffffffffffffffffffffffffffffffffffff811681036118fe5782526020810151602083015260408101516040830152606081015160608301526080810151608083015260a081015160a083015260c081015160c083015260e081015160e08301526101008082015190830152610120611cf7818301611c2c565b90830152610140611d09818301611c2c565b908301526101608082015190830152610180809101519082015290565b6020818303126118fe57805167ffffffffffffffff918282116118fe57019082601f830112156118fe5781519081116116655760405192611d8f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f85011601856116b0565b818452602082840101116118fe57611dad9160208085019101611875565b90565b67ffffffffffffffff81116116655760051b60200190565b90611dd282611db0565b611ddf60405191826116b0565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611e0d8294611db0565b0190602036910137565b91908201809211611e2457565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b91908203918211611e2457565b805182101561195d5760209160051b010190565b91906008549081841015611f3657611e8c9084611e17565b90808211611f2e575b50611ea8611ea38483611e53565b611dc8565b90611eb6611ea38583611e53565b93805b828110611ec7575050509190565b80604073ffffffffffffffffffffffffffffffffffffffff611eea600194611926565b919054600392831b1c169081611f09611f038887611e53565b8a611e60565b5260009182526020522054611f27611f218584611e53565b89611e60565b5201611eb9565b905038611e95565b50506040519150611f4682611694565b6000825260405191611f5783611694565b6000835260003681379190565b73ffffffffffffffffffffffffffffffffffffffff6040517fbbe4f6db0000000000000000000000000000000000000000000000000000000081523060048201526101a0908181602481867f0000000000000000000000005d74492c382a0646a50f374e1272339c8110cd4c165afa90811561200d5761014092600092611ff0575b5050015116151590565b6120069250803d10610e5157610e4281836116b0565b3880611fe6565b6040513d6000823e3d90fd5b9060085490818310156120a2576120309083611e17565b9080821161209a575b50612047611ea38383611e53565b91805b8281106120575750505090565b8073ffffffffffffffffffffffffffffffffffffffff612078600193611926565b90549060031b1c1661209361208d8584611e53565b87611e60565b520161204a565b905038612039565b5050506040516120b181611694565b60008152600036813790565b9073ffffffffffffffffffffffffffffffffffffffff809216600090808252600960205260ff604083205416156120f5575b50509050565b60085493680100000000000000008510156121695761211d8560016040969701600855611926565b819291549060031b9184831b921b1916179055815260096020522060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790558038806120ef565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd

Loading...
Loading
Loading...
Loading
[ 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.